Mandate that specific PO line attributes are populated before submitting for approval
{
"enabled": true,
"title": "Purchase line items must have a need date",
"target": "PURCHASEORDERLINE",
"eventType": "UPDATE",
"ruleType": "VALIDATION",
"errorState": "ALLOW",
"context": "{ purchaseOrderLine(id: $id) { id needDate } }",
"code": "if context.get('changes', {}).get('purchaseOrderLines', {}).get('status', {}).get('new') == 'requested' and context.get('purchaseOrderLine', {}).get('needDate') is None: raise ValidationError()"
}
Mandate that specific PO header (custom) attributes are populated before submitting for approval
{
"enabled": true,
"title": "Purchase requires a justification before it can be ordered"
"target": "PURCHASEORDERLINE",
"eventType": "UPDATE",
"errorState": "ALLOW",
"context": "{ purchaseOrderLine(id: $id) { purchaseOrder {id attributes {key value}} } }",
"code": "if context.get('changes', {}).get('purchaseOrderLines', {}).get('status', {}).get('new') == 'requested' and any(attribute.get('key') == 'PO Justification' and not attribute.get('value') for attribute in context['purchaseOrderLine'].get('purchaseOrder', {}).get('attributes', [])): raise ValidationError()"
}
Purchase Approvals
How to use ION actions to enforce Purchases have proper approvals
STEP 1 Set up roles
You’ll need to add roles (or teams - see note below)and assign them to users, for as many approval levels as your organization has (e.g. Manager, Director, VP)
You can also use Teams instead of roles (see next comment below)
STEP 2 Installing your rule (test in staging first!)
If you wish to use the “teams” feature instead of "roles", toggle anywhere it says "role" to "team" in the code below.
The mutation to create a rule:
mutationCreateRule($create_rule: CreateRuleInput!) { createRule(input: $create_rule) { rule { id title context code _etag enabled target } }}
Query variables: There are two rules to install per role:
Rule 1: Prevent submission for approval (DRAFT > IN REVIEW) without a manager.
Rule 2: Prevent ordering (APPROVED > ORDERED) without a manager’s approval (this is a loophole in which the user can remove the approval request once the status is in “IN REVIEW”
Repeat this step for every role & approval level in your org by modifying the "Manager" and "$10,000" below:
Rule 1:
{"create_rule": {"enabled": true,"title": "Add a Manager for purchases over $10,000","target": "PURCHASEORDERLINE","eventType": "UPDATE","ruleType": "VALIDATION","errorState": "ALLOW", "context": "{ purchaseOrderLine(id: $id) { id purchaseOrder { estimatedCost approvalRequests { reviewer { roles { name } } } } } }",
"code": "if context.get('changes', {}).get('purchaseOrderLines', {}).get('status', {}).get('new') == 'requested' and context.get('purchaseOrderLine', {}).get('purchaseOrder', {}).get('estimatedCost', 0) > 10000 and not any(role.get('name') == 'Manager' for approvalRequest in context.get('purchaseOrderLine', {}).get('purchaseOrder', {}).get('approvalRequests', []) for role in approvalRequest.get('reviewer', {}).get('roles', [])): raise ValidationError()"
}}
Rule 2:
{"create_rule": {"enabled": true,"title": "You need a manager's approval for purchases over $10,000","target": "PURCHASEORDERLINE","eventType": "UPDATE","ruleType": "VALIDATION","errorState": "ALLOW", "context": "{ purchaseOrderLine(id: $id) { id status purchaseOrder { estimatedCost approvals { reviewer { name roles { name } } } } } }",
"code": "if context.get('changes', {}).get('purchaseOrderLines', {}).get('status', {}).get('new') == 'ordered' and context.get('purchaseOrderLine', {}).get('purchaseOrder', {}).get('estimatedCost', 0) > 10000 and not any(role.get('name') == 'Manager' for approval in context.get('purchaseOrderLine', {}).get('purchaseOrder', {}).get('approvals', []) for role in approval.get('reviewer', {}).get('roles', [])): raise ValidationError()"
}}
That's it! Be sure to test for any quirky unintended behaviors!
No permission to edit inventory
Prevent certain roles from editing inventory while still allowing them to move the location and split inventory as needed.
{"create_rule": {"title": "No Permissions to Edit Inventory","ruleType": "VALIDATION","eventType": "UPDATE","target": "PARTINVENTORY", "code": "if not context['changes'].get('partsInventory', {}).get('location_id', {}) and not context['changes'].get('abomItems', {}).get('quantity', {}) and not set(context.get('currentUser', {}).get('roles', {})).intersection(set(['Quality Engineer', 'Quality Inspector', 'Reliability Engineer'])): raise ValidationError()",
"context": "{ partInventory(id: $id) { id _etag location { id name } } }","enabled": false }
Receipt line items must have a location
{
"create_rule": {
"title": "Receipt line items must have a location",
"ruleType": "VALIDATION",
"eventType": "CREATE",
"target": "RECEIPTITEM",
"code": "if context.get('receiptItem', {}).get('partInventory', {}).get('locationId') is None: raise ValidationError()",
"context": "{ receiptItem(id: $id) { id partInventory{locationId} } }",
"enabled": true
}
}
Required fields for part creation
{
"create_rule": {
"title": "Required fields for part creation",
"ruleType": "VALIDATION",
"eventType": "CREATE",
"target": "PART",
"code": "if (context.get('part', {}).get('partType') == 'PART') and any([not context.get('part', {}).get('revision') == '', not context.get('part', {}).get('description') == '', not context.get('part', {}).get('trackingType') == '', not context.get('part', {}).get('sourcingStrategy') == '', not context.get('part', {}).get('unitOfMeasure') == '']): raise ValidationError()",
"context": "{ part(id: $id) { id revision revisionScheme description trackingType sourcingStrategy partType unitOfMeasure { id } attributes { key value } } }",
"enabled": false
}
}