Skip to content

6. The checkout approval workflow

What we're adding

When a user clicks Submit for approval on a checkout, its status becomes PENDING_APPROVAL. That should start an approval process; when the Office Equipment Manager approves, the status becomes APPROVED; if rejected, REJECTED. No approval code — a workflow definition, an entity rule, and the platform's generic callback.

1. The workflow

spk-assembly/metadata/workflow/office-equipment.checkout-approval.json (real file):

json
{
  "name": "office-equipment.checkout-approval",
  "description": "Single-stage Office Equipment Manager approval for a checkout submitted for approval.",
  "stagesJson": "[\"manager\"]",
  "tasksJson": "[{\"taskKey\":\"manager-approve\",\"stage\":\"manager\",\"taskType\":\"approval\",\"kind\":\"human\",\"payload\":{\"approvalObject\":\"office-equipment.checkout-approval\",\"amount\":0,\"dueInSeconds\":259200}}]",
  "transitionsJson": "[]",
  "approversJson": "{}",
  "slasJson": "[]",
  "escalationsJson": "[]",
  "notificationsJson": "[]",
  "metadataJson": "{}",
  "approvalPermissions": [
    { "roleCode": "OFFICE_EQUIPMENT_MANAGER", "approvalObject": "office-equipment.checkout-approval", "maxAmount": null }
  ]
}
  • stagesJson, tasksJson, transitionsJson are JSON-encoded strings, not objects.
  • One stage ["manager"], one human approval task, no transitions (reaching the only stage's decision ends the instance).
  • approvalPermissions is a real array; roleCode references the role by its stable code (the one we prefixed in chapter 1).

2. The rule

spk-assembly/metadata/rules/oeq_checkout_submit_workflow.json (real file):

json
{
  "entityType": "oeq_checkout",
  "name": "oeq_checkout_submit_workflow",
  "description": "On transition INTO PENDING_APPROVAL, start office-equipment.checkout-approval; the generic callback flips status to APPROVED/REJECTED.",
  "triggerEvent": "AFTER_UPDATE",
  "conditions": "{\"all\":[{\"field\":\"status\",\"op\":\"eq\",\"value\":\"PENDING_APPROVAL\"},{\"field\":\"status__previous\",\"op\":\"neq\",\"value\":\"PENDING_APPROVAL\"}]}",
  "actions": "[{\"type\":\"START_WORKFLOW\",\"workflowName\":\"office-equipment.checkout-approval\",\"callbackUrl\":\"http://localhost:8080/api/v1/entities/workflow-callback\",\"config\":{\"entityType\":\"oeq_checkout\",\"idField\":\"id\",\"fields\":[{\"name\":\"status\",\"approved\":\"APPROVED\",\"rejected\":\"REJECTED\"}]}}]",
  "priority": 20,
  "active": true
}
  • The all condition fires exactly on the transition intoPENDING_APPROVALstatus__previous is the value before the update. Without that guard the rule would re-fire on every later save.
  • config.fields maps the workflow decision back onto the row: approved → status = APPROVED, rejected → status = REJECTED.
  • callbackUrl — the generic entity workflow-callback endpoint. Its base (http://localhost:8080) is environment-specific; use your ERP's base URL.
  • priority: 20 leaves room below for validation rules.

3. The Submit button

On the checkout detail dialog (chapter 5), the button PUTs the status:

json
"events": { "clicked": { "source": "action-chain", "actions": [
  { "id": "a0", "order": 0, "type": "callApi",
    "config": { "connectionRef": "self", "path": "/api/v1/entities/oeq_checkout/records/${page.detailRecord.id}", "httpMethod": "PUT", "params": { "status": "PENDING_APPROVAL" } } },
  { "id": "a1", "order": 1, "type": "callApi",
    "config": { "connectionRef": "self", "path": "/api/v1/entities/oeq_checkout/records/${page.detailRecord.id}", "httpMethod": "GET", "params": {} }, "output": "refreshed" },
  { "id": "a2", "order": 2, "type": "setValue", "config": { "field": "page.detailRecord", "value": "${refreshed}" } },
  { "id": "a3", "order": 3, "type": "setValue", "config": { "field": "page.gridRefreshTick", "value": "${!page.gridRefreshTick}" } },
  { "id": "a4", "order": 4, "type": "showToast", "config": { "message": "Submitted for approval." } }
] } }

Verify

bash
erp workflow validate spk-assembly/metadata/workflow/office-equipment.checkout-approval.json
erp schema validate spk-assembly/metadata/rules/oeq_checkout_submit_workflow.json --schema entity-rule-definition

Both OK. After publish, the rule is installed:

bash
$ erp api get "/api/v1/entity-rules?entityType=oeq_checkout"
[ { "id": 388, "entityType": "oeq_checkout", "name": "oeq_checkout_submit_workflow",
    "triggerEvent": "AFTER_UPDATE", "active": true } ]

Submit a checkout and confirm it transitions and then stays pending until a human decides it:

bash
$ erp api put "/api/v1/entities/oeq_checkout/records/3" --body '{"status":"PENDING_APPROVAL"}'
{ "id": 3, "checkout_number": "CO-200", "status": "PENDING_APPROVAL" }

PENDING_APPROVAL — correct. Confirm a real workflow instance started and find its pending task:

bash
$ erp workflow list --definition oeq_checkout_submit_workflow --record 3
[ { "id": 5012, "status": "RUNNING", "currentStage": "manager-approval", "correlationId": "3" } ]
1 instance(s) for record 3 of "oeq_checkout_submit_workflow".

$ erp workflow tasks 5012
[ { "id": 88, "stage": "manager-approval", "status": "PENDING", "candidateApprovers": ["MANAGER"] } ]

(erp workflow list/instance/tasks/history reach the workflow engine through a read-proxy on engine-api's own base URL, so they work even though the workflow engine runs as a separate service here.)

The Office Equipment Manager now sees the task in the ERP's approval inbox; approving it triggers the generic callback, which sets status = APPROVED with zero code from you.

Common mistakes

  • stagesJson/tasksJson authored as objects → install fails. They're strings.
  • No status__previous guard → workflow starts on every save.
  • approvalPermissions with a role name instead of roleCode → nobody can approve.

Next: 7. The return-due reminder job