Use the current purchase order approval as the canonical code-first example.
forge-server/forge-business/forge-business-core/src/main/java/com/mdframe/forge/business/core/purchase/domain/SamplePurchaseOrder.java.../purchase/controller/SamplePurchaseOrderController.java.../purchase/service/impl/SamplePurchaseOrderServiceImpl.java.../purchase/support/SamplePurchaseOrderFlowDefinition.java.../purchase/support/SamplePurchaseOrderFlowBpmn.java.../purchase/provider/SamplePurchaseOrderCodeFormProvider.javaforge-server/forge-business/forge-business-core/src/main/resources/mapper/business/SamplePurchaseOrderMapper.xmlforge-admin-ui/src/api/business/purchase-order-test.jsforge-admin-ui/src/views/business/purchase-order-test.vueforge-server/db/migration/V1.0.81__add_sample_purchase_order_flow_test.sqlforge-server/db/migration/V1.0.82__seed_sample_purchase_order_flow_binding.sqlforge-server/db/migration/V1.0.83__seed_sample_purchase_order_app_center_entry.sqlforge-server/db/migration/V1.0.84__extend_sample_purchase_order_form_asset_fields.sqlsample_purchase_ordersample_purchase_order_approvalsamplePurchaseOrdersample_purchase_order_approval_formBUSINESS_CODE_FORMsample_purchase_order:{id}dept_leader_approve: department leader approval.engineering_manager_approve: engineering manager approval.purchase_countersign: parallel multi-instance countersign.applicant_modify: applicant modifies and resubmits or terminates.Start variables include:
businessKeyobjectCoderecordIdpurchaseOrderIdorderNotitleamountCentinitiatordeptLeaderIdengineeringManagerIdcountersignUserListccRoleKeysTask completion variables used by gateways/status:
approvalResult: approve or rejectapproved: boolean; countersign completion uses false to stop early on rejectionDRAFT.business_key/process_instance_id, set IN_PROCESS.NEED_MODIFY.IN_PROCESS.REJECTED.APPROVED.CANCELED.The sample also repairs drift:
TASK_CREATED for applicant_modify repairs IN_PROCESS -> NEED_MODIFY.TASK_CREATED for approval nodes repairs NEED_MODIFY -> IN_PROCESS.sys_flow_task.task_def_key.SamplePurchaseOrderCodeFormProvider registers a code form asset with:
formKeyformNameformMode=BUSINESS_CODE_FORMproviderKeyformUrlfields / fieldCatalogsupportsSave=truebuildContext loads business record detail and returns BusinessTaskFormContextVO. saveContext converts platform payload into SamplePurchaseOrderTaskSaveDTO, then calls saveTaskFields.
The BPMN user tasks set:
flowable:assignee="${deptLeaderId}" style variable expressions.flowable:formKey="sample_purchase_order_approval_form".flowable:formFieldPermissions='[...]'.flowable:allowApprove, flowable:allowReject, flowable:allowDelegate, flowable:requireComment.Gateways use reject conditions:
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression"><![CDATA[${approvalResult == 'reject'}]]></bpmn:conditionExpression>
Countersign uses:
<bpmn:multiInstanceLoopCharacteristics isSequential="false"
flowable:collection="${countersignUserList}"
flowable:elementVariable="assignee">
<bpmn:completionCondition xsi:type="bpmn:tFormalExpression"><![CDATA[${approved == false || nrOfCompletedInstances == nrOfInstances}]]></bpmn:completionCondition>
</bpmn:multiInstanceLoopCharacteristics>
ensureFlowModel() may create a default model when absent or when existing BPMN XML is empty. It must not overwrite existing non-empty BPMN XML, because the flow designer is the owner of node form permissions and approval settings after users save the model.