Appearance
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,transitionsJsonare JSON-encoded strings, not objects.- One stage
["manager"], one human approval task, no transitions (reaching the only stage's decision ends the instance). approvalPermissionsis a real array;roleCodereferences the role by its stablecode(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
allcondition fires exactly on the transition intoPENDING_APPROVAL—status__previousis the value before the update. Without that guard the rule would re-fire on every later save. config.fieldsmaps 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: 20leaves 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-definitionBoth 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/tasksJsonauthored as objects → install fails. They're strings.- No
status__previousguard → workflow starts on every save. approvalPermissionswith a role name instead ofroleCode→ nobody can approve.