Skip to main content

Production Orders

In this section, you'll create your first production order manually. In later sections, orders will come from ERP instead.

Reference Materials: Production Orders. Learn how Kanoa manages production orders

A production order represents work that needs to be completed. Depending on the plant, it may be called a work order, job, order, or manufacturing order. In Kanoa, a production order generally represents what item needs to be made, how much, when it's due, and what status it's in. A production order isn't a time-based event, it doesn't say that something happened at a particular timestamp. Instead, it's a record that events can be associated with: scheduled operation blocks, production mode events, count events, and Run Review data. That association lets Kanoa calculate how much has been produced against a given order.

Production orders answer what are we supposed to make, and how much? What item is being produced? When is it due? How much is already complete? Which assets ran this order?

Whether you call them Production Orders, Manufacturing Orders, Work Orders, or Batch Tickets, this screen provides you with the ability to view, create and edit orders to build products on your assets.

They also provide the structure for ERP integration. In real projects, production orders usually originate in ERP and are sent to Kanoa.

Schedule Production Orders

Filtering​

Orders can be filtered by due date and status. If an order does not have a due date set, it will be included regardless of the date range selected. The state filter button toggles the state selector.

The Show Actual checkbox displays the actual quantities produced for a production order, whether the order was run once or across multiple runs on multiple assets. Enabling this will slow down the query as it will look for all production runs against this production order.


Configuring Order States​

The Orders Configuration screen defines the lifecycle states that production orders (work orders) move through in Kanoa MES.

This configuration determines:

  • Which states are available in the system
  • Whether orders in a given state are considered active
  • Whether they can be scheduled
  • Whether they can be executed on assets
  • How they are visually represented throughout the application

Order States

The following order states are considered 'reserved' in our system, but you can also create your own.

  • Released: The order has been released to production.
  • Scheduled: The order has been scheduled on an asset.
  • In Progress: There are active runs in progress on this order or this order has been partially completed. The first time a production run is started against an order, MES will change the state to 'In Progress'.
  • Closed: This production order has been closed.

Creating Orders​

Watch Creating Production Orders Tutorial

Click the '+' button to add a new production order. To edit an existing order, select it in the table and click order-management.

Schedule Production Orders

  • Order #: Enter a unique identifier for this order.
  • Item: Use the item selector to select the item (product) to be made.
  • Qty: The required amount to be produced.
  • Units: Ideally, the units here will be the same as the units configured for this asset-item in the production rate configuration screen.
  • Due Date: Date this order needs to be completed by (optional).
  • Status: The state of this Order, whether it has been released to production or is held. When an operation starts for this work order, the system automatically sets the status to 'In Progress'.
  • Notes: Any notes you want to share with the folks running this order.

To delete an order, select it in the table and click order-management.


Production Order Metadata​

In addition to the standard fields required to create and schedule a Production Order, Kanoa MES supports Production Order Metadata as a flexible and extensible mechanism for associating additional business-specific information with an order.

Production Order Metadata is designed to support the reality that many manufacturers require more contextual data than what is captured in a baseline production order. This may include information such as:

  • Bill of Materials (BOM)
  • Startup or pre-flight checklists
  • Machine setpoints
  • API payloads or integration messages
  • Custom instructions or configuration data used by extensions or automation

Rather than enforcing a rigid schema, Kanoa allows any number of metadata entries to be attached to a production order.

Metadata Structure​

Each Production Order Metadata entry consists of:

  • metaDataName
    A common, human-readable identifier used to describe and retrieve the metadata
    (for example: BOM, apiBody, Startup Checklist, Machine Setpoints).

  • metaDataValue
    The data payload associated with that metadata name.
    This value is stored as a string and may contain plain text, Markdown, or structured data such as JSON.

  • workOrderId
    The work order that this metadata is associated to.

While metadata values are not limited to any specific format, JSON strings are one of the most common and recommended approaches, as they allow complex, structured data to be associated with an order in a predictable and machine-readable way.

Viewing Metadata in the Application​
productionOrderMetaData

Production Order Metadata can be viewed directly from the Production Orders screen.

  1. Click the left-hand expand arrow on a production order row to reveal additional details.
  2. The expanded view displays:
    • Standard Order Info
    • Schedule Info
    • One tab per metadata entry associated with the order
  3. Each metadata tab is labeled using the metadata name.

Within a metadata tab, you may toggle Show raw text:

  • When enabled, the raw metadata value is displayed exactly as stored.
  • When disabled, Kanoa assumes the value contains Markdown (or JSON-compatible Markdown) and renders it using the built-in Markdown viewer for improved readability.

This makes metadata well-suited for both machine consumption and operator-facing visualization.

Programmatic Access & Extensibility​

Production Order Metadata is commonly used when implementing custom extensions, integrations, or business logic within Kanoa MES.

Metadata can be:

  • Added when an order is created
  • Updated over the lifecycle of an order
  • Retrieved by name, order, or status
  • Deleted when no longer needed

This enables developers and integrators to:

  • Drive custom workflows based on metadata content
  • Pass structured data between systems
  • Attach configuration or instructions without modifying the core order schema

Kanoa provides system functions to add, update, retrieve, and delete production order metadata, allowing metadata to be fully managed through scripting and APIs.

Create a production order
  1. Navigate to Ops → Scheduling → Production Orders.
  2. Create a new order with the following values:
Order NumberItemModeQuantityUnitsStatusDue Date
260526-001CoreForge\WIP\Machined\SC-2024Production30000unitsReleasedEnd of the month
  1. Save the production order.
Check your work

Copy this script into the Ignition Designer Script Console (Tools → Script Console) and run it. It checks that order 260526-001 exists with the right item, mode, quantity, units, status and an end-of-month due date. Each check prints PASS or FAIL with what to fix. It doesn't change anything.

Show the check script
checkLesson-productionOrders.py
# Kanoa Ops Training - check "Production Orders" (Scheduling, lesson 2)
#
# Run in the Ignition Designer Script Console (Tools > Script Console), in the
# project that inherits KANOA_SCRIPTS. It only reads; it changes nothing.
# Every check prints PASS or FAIL with what to fix, then a summary.

from java.util import Calendar, TimeZone, Date

ORDER = '260526-001'
ITEM = 'CoreForge\\WIP\\Machined\\SC-2024'
MODE = 'Production'
QTY = 30000
UNITS = 'units'
STATUS = 'Released'
# the next lessons start this order, which moves it on from Released
LATER_STATUSES = ['In Progress', 'Completed', 'Closed']
SITE = 'CoreForge\\Philadelphia'
WINDOWS_TZ = {
'eastern standard time': 'America/New_York', 'central standard time': 'America/Chicago',
'mountain standard time': 'America/Denver', 'pacific standard time': 'America/Los_Angeles',
}

results = []

def check(ok, what, fix=None):
results.append(bool(ok))
if ok:
print 'PASS %s' % what
else:
print 'FAIL %s' % what
if fix:
print ' -> %s' % fix

def field(row, *names):
for n in names:
if n in row and row[n] is not None:
return row[n]
return None

def text(v):
return '' if v is None else unicode(v).strip()

def squash(v):
return ' '.join(text(v).lower().split())

def norm(path):
return text(path).replace('/', '\\').strip('\\')

def rows(fn, params):
# Kanoa returns a dataset, a list, or None when there's nothing to return
data = fn(params)
if data is None:
return []
if hasattr(data, 'getRowCount'):
cols = list(data.getColumnNames())
return [dict((c, data.getValueAt(i, c)) for c in cols) for i in range(data.getRowCount())]
return [dict(r) for r in data]

def showColumns(label, data, key):
if data and key not in data[0]:
print 'NOTE %s has no %s column. Columns: %s' % (label, key, ', '.join(sorted(data[0].keys())))

def toDate(v):
if v is None:
return None
if isinstance(v, Date):
return v
if hasattr(v, 'getTime'):
return Date(v.getTime())
if isinstance(v, (int, long, float)) or text(v).isdigit():
return Date(long(float(text(v))))
s = text(v).replace('T', ' ')
for value, fmt in ((s[:19], 'yyyy-MM-dd HH:mm:ss'), (s[:16], 'yyyy-MM-dd HH:mm'), (s[:10], 'yyyy-MM-dd')):
try:
return system.date.parse(value, fmt)
except:
pass
return None

print '=' * 70
print 'Kanoa Ops Training - Production Orders check'
print '=' * 70

# 1. The order exists
print '\n-- Production order %s' % ORDER
orders = rows(system.kanoa.order.getProductionOrders, {'workOrderName': ORDER, 'ignoreActualQty': True})
orders = [r for r in orders if text(field(r, 'workOrderName')) in ('', ORDER)]
if not orders:
# look for a near miss, e.g. extra spaces or a different dash
allOrders = rows(system.kanoa.order.getProductionOrders, {'ignoreActualQty': True})
key = ''.join(ch for ch in ORDER if ch.isalnum())
near = [text(field(r, 'workOrderName')) for r in allOrders
if ''.join(ch for ch in text(field(r, 'workOrderName')) if ch.isalnum()) == key]
if near:
check(False, 'Production order %s exists' % ORDER,
"Found '%s'. Edit it and set the Order # to %s exactly." % (near[0], ORDER))
orders = [r for r in allOrders if text(field(r, 'workOrderName')) == near[0]]
else:
check(False, 'Production order %s exists' % ORDER,
'In Ops > Scheduling > Production Orders, click + and add order %s.' % ORDER)
else:
check(True, 'Production order %s exists' % ORDER)
if len(orders) > 1:
print 'NOTE There are %d orders named %s; checking the first.' % (len(orders), ORDER)

if orders:
o = orders[0]
showColumns('getProductionOrders', orders, 'itemPath')

# 2. Item
itemPath = norm(field(o, 'itemPath'))
if not itemPath:
cls = norm(field(o, 'itemClassPath'))
name = text(field(o, 'itemName'))
itemPath = (cls + '\\' + name) if cls else name
check(itemPath == ITEM or (itemPath == ITEM.split('\\')[-1]), 'Item is %s' % ITEM,
"It's '%s'. Edit the order and use the item selector to pick %s." % (itemPath or 'blank', ITEM))

# 3. Mode
mode = text(field(o, 'modeName'))
check(mode == MODE, 'Mode is %s' % MODE,
"It's '%s'. Edit the order and set Mode to %s." % (mode or 'blank', MODE))

# 4. Quantity and units
qty = field(o, 'reqdQty', 'requiredQty', 'qty')
try:
qtyOk = abs(float(qty) - QTY) < 0.001
except:
qtyOk = False
check(qtyOk, 'Quantity is %s' % QTY,
"It's %s. Edit the order and set Qty to %s." % (text(qty) or 'blank', QTY))
units = text(field(o, 'itemUnitName', 'unitName', 'units'))
check(units.lower() == UNITS, 'Units are %s' % UNITS,
"It's '%s'. Edit the order and set Units to %s." % (units or 'blank', UNITS))

# 5. Status
status = text(field(o, 'workOrderStatusName', 'statusName'))
if status in LATER_STATUSES:
check(True, 'Status is %s (now %s, because a run has started on it)' % (STATUS, status))
else:
check(status == STATUS, 'Status is %s' % STATUS,
"It's '%s'. Edit the order and set Status to %s, so it can be scheduled." % (status or 'blank', STATUS))

# 6. Due date: the end of a month, in the site's time zone
site = rows(system.kanoa.asset.getAssets, {'assetPath': SITE})
tzName = text(field(site[0], 'timeZone')) if site else ''
tz = TimeZone.getTimeZone(WINDOWS_TZ.get(tzName.lower(), tzName)) if tzName else TimeZone.getDefault()
due = toDate(field(o, 'dueDate'))
if due is None:
check(False, 'Due date is set to the end of the month',
'The order has no due date. Edit it and set Due Date to the last day of the month.')
else:
c = Calendar.getInstance(tz)
c.setTime(due)
day, last = c.get(Calendar.DAY_OF_MONTH), c.getActualMaximum(Calendar.DAY_OF_MONTH)
shown = '%04d-%02d-%02d' % (c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, day)
check(day == last, 'Due date is set to the end of the month (%s)' % shown,
"It's %s. Edit the order and set Due Date to the last day of the month." % shown)

passed = results.count(True)
print '\n' + '=' * 70
print 'Result: %d of %d checks passed' % (passed, len(results))
if passed == len(results):
print 'All good. Your production order is set up.'
else:
print 'Fix the FAIL items above, then run this again.'
print '=' * 70

You'll schedule this order onto Machine 1 in the next section.