@tailor-platform/erp-kit 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cli.mjs +103 -23
  3. package/package.json +1 -1
  4. package/skills/erp-kit-app-5-impl-backend/SKILL.md +7 -4
  5. package/skills/erp-kit-app-7-impl-review/SKILL.md +1 -1
  6. package/skills/erp-kit-module-6-impl-review/SKILL.md +39 -17
  7. package/src/commands/generate-doc.ts +1 -1
  8. package/src/commands/lib/discovery.test.ts +13 -3
  9. package/src/commands/lib/discovery.ts +10 -2
  10. package/src/commands/lib/paths.ts +4 -2
  11. package/src/commands/lib/sync-check-tests.test.ts +84 -6
  12. package/src/commands/lib/sync-check-tests.ts +63 -3
  13. package/src/commands/sync-check.ts +7 -3
  14. package/src/generator/generate-app-code.ts +51 -16
  15. package/src/generator/generate-stubs.ts +4 -0
  16. package/src/generator/stub-templates.test.ts +11 -0
  17. package/src/generator/stub-templates.ts +22 -1
  18. package/src/modules/inventory/docs/features/inventory-adjustment.md +2 -1
  19. package/src/modules/inventory/docs/features/scrap-management.md +39 -1
  20. package/src/modules/manufacturing/README.md +63 -0
  21. package/src/modules/manufacturing/command/.gitkeep +0 -0
  22. package/src/modules/manufacturing/command/activateBillOfMaterial.generated.ts +6 -0
  23. package/src/modules/manufacturing/command/activateBillOfMaterial.test.ts +166 -0
  24. package/src/modules/manufacturing/command/activateBillOfMaterial.ts +173 -0
  25. package/src/modules/manufacturing/command/activateRouting.generated.ts +6 -0
  26. package/src/modules/manufacturing/command/activateRouting.test.ts +152 -0
  27. package/src/modules/manufacturing/command/activateRouting.ts +92 -0
  28. package/src/modules/manufacturing/command/activateWorkCenter.generated.ts +6 -0
  29. package/src/modules/manufacturing/command/activateWorkCenter.test.ts +135 -0
  30. package/src/modules/manufacturing/command/activateWorkCenter.ts +91 -0
  31. package/src/modules/manufacturing/command/cancelProductionOrder.generated.ts +6 -0
  32. package/src/modules/manufacturing/command/cancelProductionOrder.test.ts +151 -0
  33. package/src/modules/manufacturing/command/cancelProductionOrder.ts +114 -0
  34. package/src/modules/manufacturing/command/closeProductionOrder.generated.ts +6 -0
  35. package/src/modules/manufacturing/command/closeProductionOrder.test.ts +126 -0
  36. package/src/modules/manufacturing/command/closeProductionOrder.ts +87 -0
  37. package/src/modules/manufacturing/command/completeProductionOrder.generated.ts +6 -0
  38. package/src/modules/manufacturing/command/completeProductionOrder.test.ts +132 -0
  39. package/src/modules/manufacturing/command/completeProductionOrder.ts +97 -0
  40. package/src/modules/manufacturing/command/completeWorkOrder.generated.ts +6 -0
  41. package/src/modules/manufacturing/command/completeWorkOrder.test.ts +369 -0
  42. package/src/modules/manufacturing/command/completeWorkOrder.ts +212 -0
  43. package/src/modules/manufacturing/command/createBillOfMaterial.generated.ts +6 -0
  44. package/src/modules/manufacturing/command/createBillOfMaterial.test.ts +210 -0
  45. package/src/modules/manufacturing/command/createBillOfMaterial.ts +176 -0
  46. package/src/modules/manufacturing/command/createProductionOrder.generated.ts +6 -0
  47. package/src/modules/manufacturing/command/createProductionOrder.test.ts +160 -0
  48. package/src/modules/manufacturing/command/createProductionOrder.ts +129 -0
  49. package/src/modules/manufacturing/command/createRouting.generated.ts +6 -0
  50. package/src/modules/manufacturing/command/createRouting.test.ts +168 -0
  51. package/src/modules/manufacturing/command/createRouting.ts +128 -0
  52. package/src/modules/manufacturing/command/createWorkCenter.generated.ts +6 -0
  53. package/src/modules/manufacturing/command/createWorkCenter.test.ts +148 -0
  54. package/src/modules/manufacturing/command/createWorkCenter.ts +131 -0
  55. package/src/modules/manufacturing/command/deactivateBillOfMaterial.generated.ts +6 -0
  56. package/src/modules/manufacturing/command/deactivateBillOfMaterial.test.ts +103 -0
  57. package/src/modules/manufacturing/command/deactivateBillOfMaterial.ts +78 -0
  58. package/src/modules/manufacturing/command/deactivateRouting.generated.ts +6 -0
  59. package/src/modules/manufacturing/command/deactivateRouting.test.ts +112 -0
  60. package/src/modules/manufacturing/command/deactivateRouting.ts +76 -0
  61. package/src/modules/manufacturing/command/deactivateWorkCenter.generated.ts +6 -0
  62. package/src/modules/manufacturing/command/deactivateWorkCenter.test.ts +113 -0
  63. package/src/modules/manufacturing/command/deactivateWorkCenter.ts +85 -0
  64. package/src/modules/manufacturing/command/pauseWorkOrder.generated.ts +6 -0
  65. package/src/modules/manufacturing/command/pauseWorkOrder.test.ts +118 -0
  66. package/src/modules/manufacturing/command/pauseWorkOrder.ts +82 -0
  67. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.generated.ts +6 -0
  68. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.test.ts +183 -0
  69. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.ts +139 -0
  70. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.generated.ts +6 -0
  71. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.test.ts +120 -0
  72. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.ts +110 -0
  73. package/src/modules/manufacturing/command/releaseProductionOrder.generated.ts +6 -0
  74. package/src/modules/manufacturing/command/releaseProductionOrder.test.ts +220 -0
  75. package/src/modules/manufacturing/command/releaseProductionOrder.ts +450 -0
  76. package/src/modules/manufacturing/command/reopenProductionOrder.generated.ts +6 -0
  77. package/src/modules/manufacturing/command/reopenProductionOrder.test.ts +196 -0
  78. package/src/modules/manufacturing/command/reopenProductionOrder.ts +98 -0
  79. package/src/modules/manufacturing/command/reportWorkOrderProgress.generated.ts +6 -0
  80. package/src/modules/manufacturing/command/reportWorkOrderProgress.test.ts +204 -0
  81. package/src/modules/manufacturing/command/reportWorkOrderProgress.ts +129 -0
  82. package/src/modules/manufacturing/command/rescheduleProductionOrder.generated.ts +6 -0
  83. package/src/modules/manufacturing/command/rescheduleProductionOrder.test.ts +185 -0
  84. package/src/modules/manufacturing/command/rescheduleProductionOrder.ts +95 -0
  85. package/src/modules/manufacturing/command/resumeWorkOrder.generated.ts +6 -0
  86. package/src/modules/manufacturing/command/resumeWorkOrder.test.ts +122 -0
  87. package/src/modules/manufacturing/command/resumeWorkOrder.ts +94 -0
  88. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.generated.ts +6 -0
  89. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.test.ts +231 -0
  90. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.ts +137 -0
  91. package/src/modules/manufacturing/command/startWorkOrder.generated.ts +6 -0
  92. package/src/modules/manufacturing/command/startWorkOrder.test.ts +118 -0
  93. package/src/modules/manufacturing/command/startWorkOrder.ts +126 -0
  94. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.generated.ts +6 -0
  95. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.test.ts +153 -0
  96. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.ts +106 -0
  97. package/src/modules/manufacturing/command/unreleaseProductionOrder.generated.ts +6 -0
  98. package/src/modules/manufacturing/command/unreleaseProductionOrder.test.ts +140 -0
  99. package/src/modules/manufacturing/command/unreleaseProductionOrder.ts +131 -0
  100. package/src/modules/manufacturing/command/updateBillOfMaterial.generated.ts +6 -0
  101. package/src/modules/manufacturing/command/updateBillOfMaterial.test.ts +149 -0
  102. package/src/modules/manufacturing/command/updateBillOfMaterial.ts +174 -0
  103. package/src/modules/manufacturing/command/updateProductionOrder.generated.ts +6 -0
  104. package/src/modules/manufacturing/command/updateProductionOrder.test.ts +112 -0
  105. package/src/modules/manufacturing/command/updateProductionOrder.ts +145 -0
  106. package/src/modules/manufacturing/command/updateRouting.generated.ts +6 -0
  107. package/src/modules/manufacturing/command/updateRouting.test.ts +211 -0
  108. package/src/modules/manufacturing/command/updateRouting.ts +124 -0
  109. package/src/modules/manufacturing/command/updateWorkCenter.generated.ts +6 -0
  110. package/src/modules/manufacturing/command/updateWorkCenter.test.ts +152 -0
  111. package/src/modules/manufacturing/command/updateWorkCenter.ts +137 -0
  112. package/src/modules/manufacturing/db/.gitkeep +0 -0
  113. package/src/modules/manufacturing/db/billOfMaterial.ts +70 -0
  114. package/src/modules/manufacturing/db/billOfMaterialLine.ts +49 -0
  115. package/src/modules/manufacturing/db/costVarianceLine.ts +53 -0
  116. package/src/modules/manufacturing/db/manufacturingCostLine.ts +35 -0
  117. package/src/modules/manufacturing/db/manufacturingCostSettlementRecord.ts +39 -0
  118. package/src/modules/manufacturing/db/manufacturingCostSummary.ts +59 -0
  119. package/src/modules/manufacturing/db/productionOrder.ts +83 -0
  120. package/src/modules/manufacturing/db/productionOrderBomSnapshot.ts +44 -0
  121. package/src/modules/manufacturing/db/productionOrderCostBaseline.ts +44 -0
  122. package/src/modules/manufacturing/db/productionOrderMaterialRequirement.ts +57 -0
  123. package/src/modules/manufacturing/db/productionOrderRoutingSnapshot.ts +43 -0
  124. package/src/modules/manufacturing/db/routing.ts +63 -0
  125. package/src/modules/manufacturing/db/routingOperation.ts +57 -0
  126. package/src/modules/manufacturing/db/workCenter.ts +87 -0
  127. package/src/modules/manufacturing/db/workOrder.ts +65 -0
  128. package/src/modules/manufacturing/db/workOrderExecutionEvent.ts +54 -0
  129. package/src/modules/manufacturing/docs/commands/ActivateBillOfMaterial.md +50 -0
  130. package/src/modules/manufacturing/docs/commands/ActivateRouting.md +48 -0
  131. package/src/modules/manufacturing/docs/commands/ActivateWorkCenter.md +49 -0
  132. package/src/modules/manufacturing/docs/commands/CancelProductionOrder.md +48 -0
  133. package/src/modules/manufacturing/docs/commands/CloseProductionOrder.md +46 -0
  134. package/src/modules/manufacturing/docs/commands/CompleteProductionOrder.md +48 -0
  135. package/src/modules/manufacturing/docs/commands/CompleteWorkOrder.md +66 -0
  136. package/src/modules/manufacturing/docs/commands/CreateBillOfMaterial.md +54 -0
  137. package/src/modules/manufacturing/docs/commands/CreateProductionOrder.md +49 -0
  138. package/src/modules/manufacturing/docs/commands/CreateRouting.md +50 -0
  139. package/src/modules/manufacturing/docs/commands/CreateWorkCenter.md +51 -0
  140. package/src/modules/manufacturing/docs/commands/DeactivateBillOfMaterial.md +45 -0
  141. package/src/modules/manufacturing/docs/commands/DeactivateRouting.md +45 -0
  142. package/src/modules/manufacturing/docs/commands/DeactivateWorkCenter.md +45 -0
  143. package/src/modules/manufacturing/docs/commands/PauseWorkOrder.md +44 -0
  144. package/src/modules/manufacturing/docs/commands/RecordInventoryIssueOutcome.md +59 -0
  145. package/src/modules/manufacturing/docs/commands/RecordManufacturingCostSettlementAcknowledgment.md +49 -0
  146. package/src/modules/manufacturing/docs/commands/ReleaseProductionOrder.md +57 -0
  147. package/src/modules/manufacturing/docs/commands/ReopenProductionOrder.md +54 -0
  148. package/src/modules/manufacturing/docs/commands/ReportWorkOrderProgress.md +53 -0
  149. package/src/modules/manufacturing/docs/commands/RescheduleProductionOrder.md +45 -0
  150. package/src/modules/manufacturing/docs/commands/ResumeWorkOrder.md +44 -0
  151. package/src/modules/manufacturing/docs/commands/ReviewManufacturingCostSummary.md +52 -0
  152. package/src/modules/manufacturing/docs/commands/StartWorkOrder.md +46 -0
  153. package/src/modules/manufacturing/docs/commands/TechnicallyCompleteProductionOrder.md +51 -0
  154. package/src/modules/manufacturing/docs/commands/UnreleaseProductionOrder.md +46 -0
  155. package/src/modules/manufacturing/docs/commands/UpdateBillOfMaterial.md +48 -0
  156. package/src/modules/manufacturing/docs/commands/UpdateProductionOrder.md +48 -0
  157. package/src/modules/manufacturing/docs/commands/UpdateRouting.md +52 -0
  158. package/src/modules/manufacturing/docs/commands/UpdateWorkCenter.md +48 -0
  159. package/src/modules/manufacturing/docs/features/bill-of-material-management.md +83 -0
  160. package/src/modules/manufacturing/docs/features/manufacturing-cost-and-variance.md +191 -0
  161. package/src/modules/manufacturing/docs/features/production-order-lifecycle.md +103 -0
  162. package/src/modules/manufacturing/docs/features/routing-and-work-center-definition.md +63 -0
  163. package/src/modules/manufacturing/docs/features/work-order-execution.md +115 -0
  164. package/src/modules/manufacturing/docs/models/BillOfMaterial.md +60 -0
  165. package/src/modules/manufacturing/docs/models/ManufacturingCostSummary.md +66 -0
  166. package/src/modules/manufacturing/docs/models/ProductionOrder.md +76 -0
  167. package/src/modules/manufacturing/docs/models/Routing.md +58 -0
  168. package/src/modules/manufacturing/docs/models/WorkCenter.md +56 -0
  169. package/src/modules/manufacturing/docs/models/WorkOrder.md +63 -0
  170. package/src/modules/manufacturing/docs/queries/DetectBillOfMaterialCircularReference.md +39 -0
  171. package/src/modules/manufacturing/docs/queries/ExplodeBillOfMaterial.md +56 -0
  172. package/src/modules/manufacturing/docs/queries/GetBillOfMaterial.md +37 -0
  173. package/src/modules/manufacturing/docs/queries/GetManufacturingCostSummary.md +39 -0
  174. package/src/modules/manufacturing/docs/queries/GetProductionOrder.md +37 -0
  175. package/src/modules/manufacturing/docs/queries/GetRouting.md +39 -0
  176. package/src/modules/manufacturing/docs/queries/GetWorkCenter.md +35 -0
  177. package/src/modules/manufacturing/docs/queries/GetWorkOrder.md +38 -0
  178. package/src/modules/manufacturing/docs/queries/ListBillOfMaterialsByItem.md +42 -0
  179. package/src/modules/manufacturing/docs/queries/ListManufacturingCostSummariesByStatus.md +41 -0
  180. package/src/modules/manufacturing/docs/queries/ListProductionOrdersByStatus.md +41 -0
  181. package/src/modules/manufacturing/docs/queries/ListRoutingsByItem.md +42 -0
  182. package/src/modules/manufacturing/docs/queries/ListWorkCentersBySite.md +38 -0
  183. package/src/modules/manufacturing/docs/queries/ListWorkOrdersByProductionOrder.md +39 -0
  184. package/src/modules/manufacturing/docs/queries/ListWorkOrdersByWorkCenter.md +43 -0
  185. package/src/modules/manufacturing/executor/.gitkeep +0 -0
  186. package/src/modules/manufacturing/generated/enums.ts +113 -0
  187. package/src/modules/manufacturing/generated/kysely-tailordb.ts +247 -0
  188. package/src/modules/manufacturing/index.ts +2 -0
  189. package/src/modules/manufacturing/lib/_db_deps.ts +22 -0
  190. package/src/modules/manufacturing/lib/errors.generated.ts +592 -0
  191. package/src/modules/manufacturing/lib/permissions.generated.ts +35 -0
  192. package/src/modules/manufacturing/lib/types.ts +111 -0
  193. package/src/modules/manufacturing/module.ts +226 -0
  194. package/src/modules/manufacturing/permissions.ts +3 -0
  195. package/src/modules/manufacturing/query/.gitkeep +0 -0
  196. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.generated.ts +5 -0
  197. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.test.ts +115 -0
  198. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.ts +79 -0
  199. package/src/modules/manufacturing/query/explodeBillOfMaterial.generated.ts +5 -0
  200. package/src/modules/manufacturing/query/explodeBillOfMaterial.test.ts +445 -0
  201. package/src/modules/manufacturing/query/explodeBillOfMaterial.ts +306 -0
  202. package/src/modules/manufacturing/query/getBillOfMaterial.generated.ts +5 -0
  203. package/src/modules/manufacturing/query/getBillOfMaterial.test.ts +64 -0
  204. package/src/modules/manufacturing/query/getBillOfMaterial.ts +27 -0
  205. package/src/modules/manufacturing/query/getManufacturingCostSummary.generated.ts +5 -0
  206. package/src/modules/manufacturing/query/getManufacturingCostSummary.test.ts +147 -0
  207. package/src/modules/manufacturing/query/getManufacturingCostSummary.ts +46 -0
  208. package/src/modules/manufacturing/query/getProductionOrder.generated.ts +5 -0
  209. package/src/modules/manufacturing/query/getProductionOrder.test.ts +139 -0
  210. package/src/modules/manufacturing/query/getProductionOrder.ts +84 -0
  211. package/src/modules/manufacturing/query/getRouting.generated.ts +5 -0
  212. package/src/modules/manufacturing/query/getRouting.test.ts +71 -0
  213. package/src/modules/manufacturing/query/getRouting.ts +34 -0
  214. package/src/modules/manufacturing/query/getWorkCenter.generated.ts +5 -0
  215. package/src/modules/manufacturing/query/getWorkCenter.test.ts +37 -0
  216. package/src/modules/manufacturing/query/getWorkCenter.ts +21 -0
  217. package/src/modules/manufacturing/query/getWorkOrder.generated.ts +5 -0
  218. package/src/modules/manufacturing/query/getWorkOrder.test.ts +73 -0
  219. package/src/modules/manufacturing/query/getWorkOrder.ts +28 -0
  220. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.generated.ts +5 -0
  221. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.test.ts +107 -0
  222. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.ts +58 -0
  223. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.generated.ts +5 -0
  224. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.test.ts +96 -0
  225. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.ts +77 -0
  226. package/src/modules/manufacturing/query/listProductionOrdersByStatus.generated.ts +5 -0
  227. package/src/modules/manufacturing/query/listProductionOrdersByStatus.test.ts +121 -0
  228. package/src/modules/manufacturing/query/listProductionOrdersByStatus.ts +83 -0
  229. package/src/modules/manufacturing/query/listRoutingsByItem.generated.ts +5 -0
  230. package/src/modules/manufacturing/query/listRoutingsByItem.test.ts +110 -0
  231. package/src/modules/manufacturing/query/listRoutingsByItem.ts +54 -0
  232. package/src/modules/manufacturing/query/listWorkCentersBySite.generated.ts +5 -0
  233. package/src/modules/manufacturing/query/listWorkCentersBySite.test.ts +81 -0
  234. package/src/modules/manufacturing/query/listWorkCentersBySite.ts +70 -0
  235. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.generated.ts +5 -0
  236. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.test.ts +102 -0
  237. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.ts +53 -0
  238. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.generated.ts +5 -0
  239. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.test.ts +143 -0
  240. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.ts +56 -0
  241. package/src/modules/manufacturing/seed/index.ts +19 -0
  242. package/src/modules/manufacturing/tailor.config.ts +13 -0
  243. package/src/modules/manufacturing/tailor.d.ts +13 -0
  244. package/src/modules/manufacturing/testing/commandTestUtils.ts +29 -0
  245. package/src/modules/manufacturing/testing/fixtures.ts +402 -0
  246. package/templates/scaffold/app/backend/package.json +9 -2
  247. package/templates/scaffold/app/backend/src/tests/utils/graphql-client.ts +66 -0
  248. package/templates/scaffold/app/backend/src/tests/utils/setup.ts +21 -0
  249. package/templates/scaffold/app/backend/tsconfig.json +9 -2
  250. package/templates/scaffold/app/backend/vitest.config.ts +35 -0
  251. package/templates/scaffold/app/frontend/package.json +2 -2
@@ -0,0 +1,97 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ProductionOrderNotFoundError,
4
+ ProductionOrderNotCompletableError,
5
+ OpenWorkOrderRemainsError,
6
+ FinalOutputRequiredError,
7
+ FinalReceiptRequiredError,
8
+ } from "../lib/errors.generated";
9
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
10
+
11
+ export interface CompleteProductionOrderInput {
12
+ id: string;
13
+ from?: string[];
14
+ }
15
+
16
+ /**
17
+ * Function: completeProductionOrder
18
+ *
19
+ * Marks physical production complete once required work orders are finished
20
+ * and final receipt obligations are satisfied. The command freezes production
21
+ * execution while still allowing later technical completion and review.
22
+ */
23
+ export async function run<CF extends Record<string, unknown>>(
24
+ db: Transaction,
25
+ input: CompleteProductionOrderInput & CF,
26
+ _ctx: CommandContext,
27
+ ) {
28
+ const { id, from, ...customFields } = input;
29
+ void customFields;
30
+
31
+ const allowedStatuses = from ?? ["IN_PROGRESS"];
32
+
33
+ // 1. Fetch production order with lock
34
+ const order = await db
35
+ .selectFrom("ProductionOrder")
36
+ .selectAll()
37
+ .where("id", "=", id)
38
+ .forUpdate()
39
+ .executeTakeFirst();
40
+
41
+ if (!order) {
42
+ return err(new ProductionOrderNotFoundError(id));
43
+ }
44
+
45
+ // 2. Validate status is completable
46
+ if (!allowedStatuses.includes(order.status)) {
47
+ return err(new ProductionOrderNotCompletableError(id));
48
+ }
49
+
50
+ // 3. Check all required work orders are COMPLETE or CANCELLED
51
+ const workOrders = await db
52
+ .selectFrom("WorkOrder")
53
+ .selectAll()
54
+ .where("productionOrderId", "=", id)
55
+ .execute();
56
+
57
+ const hasOpenWorkOrders = workOrders.some(
58
+ (wo) => wo.status !== "COMPLETE" && wo.status !== "CANCELLED",
59
+ );
60
+
61
+ if (hasOpenWorkOrders) {
62
+ return err(new OpenWorkOrderRemainsError(id));
63
+ }
64
+
65
+ // 4. Check final output has been reported
66
+ const hasCompletedOutput = workOrders.some(
67
+ (wo) => wo.status === "COMPLETE" && wo.completedQuantity > 0,
68
+ );
69
+
70
+ if (!hasCompletedOutput) {
71
+ return err(new FinalOutputRequiredError(id));
72
+ }
73
+
74
+ // 5. Check receipt handoff evidence exists
75
+ const costSummary = await db
76
+ .selectFrom("ManufacturingCostSummary")
77
+ .selectAll()
78
+ .where("productionOrderId", "=", id)
79
+ .executeTakeFirst();
80
+
81
+ if (!costSummary || costSummary.actualMaterialCost <= 0) {
82
+ return err(new FinalReceiptRequiredError(id));
83
+ }
84
+
85
+ // 6. Set status to COMPLETED
86
+ const completedOrder = await db
87
+ .updateTable("ProductionOrder")
88
+ .set({
89
+ status: "COMPLETED",
90
+ updatedAt: new Date(),
91
+ })
92
+ .where("id", "=", id)
93
+ .returningAll()
94
+ .executeTakeFirstOrThrow();
95
+
96
+ return ok({ productionOrder: completedOrder });
97
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./completeWorkOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const completeWorkOrder = defineCommand(permissions.completeWorkOrder, run);
@@ -0,0 +1,369 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ WorkOrderNotFoundError,
6
+ WorkOrderNotCompletableError,
7
+ WorkOrderNotStartedError,
8
+ InvalidCompletionQuantityError,
9
+ DuplicateBackflushRiskError,
10
+ ReceiptHandoffRequiredError,
11
+ LotReferenceRequiredError,
12
+ SerialReferenceRequiredError,
13
+ } from "../lib/errors.generated";
14
+ import {
15
+ baseInProgressWorkOrder,
16
+ basePendingWorkOrder,
17
+ baseCompleteWorkOrder,
18
+ } from "../testing/fixtures";
19
+ import { run } from "./completeWorkOrder";
20
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
21
+
22
+ describe("completeWorkOrder", () => {
23
+ const ctx: CommandContext = {
24
+ actorId: "test-actor",
25
+ permissions: ["manufacturing:completeWorkOrder"],
26
+ };
27
+
28
+ const baseInput = {
29
+ id: baseInProgressWorkOrder.id,
30
+ completedQuantity: 50,
31
+ backflushRequired: false,
32
+ receiptRequired: false,
33
+ notes: "Final completion",
34
+ };
35
+
36
+ it("completes an in-progress work order with final quantity reporting", async () => {
37
+ const { db, spies } = createMockDb<Transaction>();
38
+ const completedWorkOrder = {
39
+ ...baseInProgressWorkOrder,
40
+ status: "COMPLETE" as const,
41
+ completedQuantity: 100,
42
+ };
43
+
44
+ // work order lookup
45
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
46
+ // update work order
47
+ spies.update.mockReturnValueOnce(completedWorkOrder);
48
+ // insert execution event
49
+ spies.insert.mockReturnValueOnce(undefined);
50
+ // sibling work orders (all complete after this one)
51
+ spies.select.mockReturnValueOnce([
52
+ { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
53
+ ]);
54
+ // update parent production order
55
+ spies.update.mockReturnValueOnce(undefined);
56
+
57
+ const result = await run(db, baseInput, ctx);
58
+
59
+ expect(result.ok).toBe(true);
60
+ if (result.ok) {
61
+ expect(result.value.workOrder.status).toBe("COMPLETE");
62
+ expect(result.value.workOrder.completedQuantity).toBe(100);
63
+ }
64
+ expect(spies.update).toHaveBeenCalled();
65
+ expect(spies.insert).toHaveBeenCalled();
66
+ });
67
+
68
+ it("returns error when the work order does not exist", async () => {
69
+ const { db, spies } = createMockDb<Transaction>();
70
+ spies.select.mockReturnValueOnce(undefined);
71
+
72
+ const result = await run(db, { ...baseInput, id: "nonexistent" }, ctx);
73
+
74
+ expect(result.ok).toBe(false);
75
+ if (!result.ok) {
76
+ expect(result.error).toBeInstanceOf(WorkOrderNotFoundError);
77
+ }
78
+ });
79
+
80
+ it("returns error when the work order is not in progress", async () => {
81
+ const { db, spies } = createMockDb<Transaction>();
82
+ spies.select.mockReturnValueOnce(basePendingWorkOrder);
83
+
84
+ const result = await run(db, { ...baseInput, id: basePendingWorkOrder.id }, ctx);
85
+
86
+ expect(result.ok).toBe(false);
87
+ if (!result.ok) {
88
+ expect(result.error).toBeInstanceOf(WorkOrderNotCompletableError);
89
+ }
90
+ });
91
+
92
+ it("returns error when the work order was never started", async () => {
93
+ const { db, spies } = createMockDb<Transaction>();
94
+ const unstartedWorkOrder = {
95
+ ...baseInProgressWorkOrder,
96
+ actualStartDate: null,
97
+ };
98
+ spies.select.mockReturnValueOnce(unstartedWorkOrder);
99
+
100
+ const result = await run(db, baseInput, ctx);
101
+
102
+ expect(result.ok).toBe(false);
103
+ if (!result.ok) {
104
+ expect(result.error).toBeInstanceOf(WorkOrderNotStartedError);
105
+ }
106
+ });
107
+
108
+ it("returns error when completion quantity is invalid", async () => {
109
+ const { db, spies } = createMockDb<Transaction>();
110
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
111
+
112
+ const result = await run(db, { ...baseInput, completedQuantity: 0 }, ctx);
113
+
114
+ expect(result.ok).toBe(false);
115
+ if (!result.ok) {
116
+ expect(result.error).toBeInstanceOf(InvalidCompletionQuantityError);
117
+ }
118
+ });
119
+
120
+ it("returns error when backflush would duplicate manual issue", async () => {
121
+ const { db, spies } = createMockDb<Transaction>();
122
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
123
+
124
+ const result = await run(
125
+ db,
126
+ {
127
+ ...baseInput,
128
+ backflushRequired: true,
129
+ manuallyIssuedQuantity: 10,
130
+ },
131
+ ctx,
132
+ );
133
+
134
+ expect(result.ok).toBe(false);
135
+ if (!result.ok) {
136
+ expect(result.error).toBeInstanceOf(DuplicateBackflushRiskError);
137
+ }
138
+ });
139
+
140
+ it("emits receipt handoff when output receipt is required", async () => {
141
+ const { db, spies } = createMockDb<Transaction>();
142
+ const completedWorkOrder = {
143
+ ...baseInProgressWorkOrder,
144
+ status: "COMPLETE" as const,
145
+ completedQuantity: 100,
146
+ };
147
+
148
+ // work order lookup
149
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
150
+ // update work order
151
+ spies.update.mockReturnValueOnce(completedWorkOrder);
152
+ // insert execution event
153
+ spies.insert.mockReturnValueOnce(undefined);
154
+ // sibling work orders
155
+ spies.select.mockReturnValueOnce([
156
+ { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
157
+ ]);
158
+ // update parent production order
159
+ spies.update.mockReturnValueOnce(undefined);
160
+
161
+ const result = await run(
162
+ db,
163
+ {
164
+ ...baseInput,
165
+ receiptRequired: true,
166
+ receiptData: {
167
+ itemReference: "item-fg-1",
168
+ unitOfMeasure: "EA",
169
+ siteReference: "site-1",
170
+ postingDate: new Date("2024-03-01T12:00:00.000Z"),
171
+ lotTracked: false,
172
+ serialTracked: false,
173
+ },
174
+ },
175
+ ctx,
176
+ );
177
+
178
+ expect(result.ok).toBe(true);
179
+ if (result.ok) {
180
+ expect(result.value.workOrder.status).toBe("COMPLETE");
181
+ expect(result.value.receiptHandoff?.itemReference).toBe("item-fg-1");
182
+ }
183
+ });
184
+
185
+ it("returns error when receipt is required but receipt data is missing", async () => {
186
+ const { db, spies } = createMockDb<Transaction>();
187
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
188
+
189
+ const result = await run(
190
+ db,
191
+ {
192
+ ...baseInput,
193
+ receiptRequired: true,
194
+ receiptData: null,
195
+ },
196
+ ctx,
197
+ );
198
+
199
+ expect(result.ok).toBe(false);
200
+ if (!result.ok) {
201
+ expect(result.error).toBeInstanceOf(ReceiptHandoffRequiredError);
202
+ }
203
+ });
204
+
205
+ it("returns error when a lot-tracked receipt omits finishedGoodLotReference", async () => {
206
+ const { db, spies } = createMockDb<Transaction>();
207
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
208
+
209
+ const result = await run(
210
+ db,
211
+ {
212
+ ...baseInput,
213
+ receiptRequired: true,
214
+ receiptData: {
215
+ itemReference: "item-fg-1",
216
+ unitOfMeasure: "EA",
217
+ siteReference: "site-1",
218
+ postingDate: new Date("2024-03-01T12:00:00.000Z"),
219
+ lotTracked: true,
220
+ serialTracked: false,
221
+ finishedGoodLotReference: null,
222
+ },
223
+ },
224
+ ctx,
225
+ );
226
+
227
+ expect(result.ok).toBe(false);
228
+ if (!result.ok) {
229
+ expect(result.error).toBeInstanceOf(LotReferenceRequiredError);
230
+ }
231
+ });
232
+
233
+ it("returns error when a serial-tracked receipt omits serialReferences", async () => {
234
+ const { db, spies } = createMockDb<Transaction>();
235
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
236
+
237
+ const result = await run(
238
+ db,
239
+ {
240
+ ...baseInput,
241
+ receiptRequired: true,
242
+ receiptData: {
243
+ itemReference: "item-fg-1",
244
+ unitOfMeasure: "EA",
245
+ siteReference: "site-1",
246
+ postingDate: new Date("2024-03-01T12:00:00.000Z"),
247
+ lotTracked: false,
248
+ serialTracked: true,
249
+ serialReferences: null,
250
+ },
251
+ },
252
+ ctx,
253
+ );
254
+
255
+ expect(result.ok).toBe(false);
256
+ if (!result.ok) {
257
+ expect(result.error).toBeInstanceOf(SerialReferenceRequiredError);
258
+ }
259
+ });
260
+
261
+ it("rolls up completion to the parent order", async () => {
262
+ const { db, spies } = createMockDb<Transaction>();
263
+ const completedWorkOrder = {
264
+ ...baseInProgressWorkOrder,
265
+ status: "COMPLETE" as const,
266
+ completedQuantity: 100,
267
+ };
268
+
269
+ // work order lookup
270
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
271
+ // update work order
272
+ spies.update.mockReturnValueOnce(completedWorkOrder);
273
+ // insert execution event
274
+ spies.insert.mockReturnValueOnce(undefined);
275
+ // sibling work orders - all siblings already complete/cancelled
276
+ spies.select.mockReturnValueOnce([
277
+ { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
278
+ { ...baseCompleteWorkOrder, id: "work-order-sibling", status: "COMPLETE" },
279
+ ]);
280
+ // update parent production order to COMPLETED
281
+ spies.update.mockReturnValueOnce(undefined);
282
+
283
+ const result = await run(db, baseInput, ctx);
284
+
285
+ expect(result.ok).toBe(true);
286
+ // The update spy should have been called twice: once for the work order, once for the parent order
287
+ expect(spies.update).toHaveBeenCalledTimes(2);
288
+ });
289
+
290
+ it("allows zero-quantity completion only under an explicit bypass policy", async () => {
291
+ const { db, spies } = createMockDb<Transaction>();
292
+ const completedWorkOrder = {
293
+ ...baseInProgressWorkOrder,
294
+ status: "COMPLETE" as const,
295
+ completedQuantity: baseInProgressWorkOrder.completedQuantity,
296
+ };
297
+
298
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
299
+ spies.update.mockReturnValueOnce(completedWorkOrder);
300
+ spies.insert.mockReturnValueOnce(undefined);
301
+ spies.select.mockReturnValueOnce([
302
+ { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
303
+ ]);
304
+ spies.update.mockReturnValueOnce(undefined);
305
+
306
+ const result = await run(
307
+ db,
308
+ {
309
+ ...baseInput,
310
+ completedQuantity: 0,
311
+ zeroQuantityBypassPolicy: {
312
+ allowZeroCompletion: true,
313
+ reasonCode: "QUALITY_HOLD",
314
+ },
315
+ receiptRequired: true,
316
+ receiptData: {
317
+ itemReference: "item-fg-1",
318
+ unitOfMeasure: "EA",
319
+ siteReference: "site-1",
320
+ postingDate: new Date("2024-03-01T12:00:00.000Z"),
321
+ lotTracked: false,
322
+ serialTracked: false,
323
+ },
324
+ },
325
+ ctx,
326
+ );
327
+
328
+ expect(result.ok).toBe(true);
329
+ if (result.ok) {
330
+ expect(result.value.backflushHandoff).toBeNull();
331
+ expect(result.value.receiptHandoff?.quantity).toBe(0);
332
+ }
333
+ });
334
+
335
+ it("emits a backflush handoff when completion requires backflush consumption", async () => {
336
+ const { db, spies } = createMockDb<Transaction>();
337
+ const completedWorkOrder = {
338
+ ...baseInProgressWorkOrder,
339
+ status: "COMPLETE" as const,
340
+ completedQuantity: 100,
341
+ };
342
+
343
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
344
+ spies.update.mockReturnValueOnce(completedWorkOrder);
345
+ spies.insert.mockReturnValueOnce(undefined);
346
+ spies.select.mockReturnValueOnce([
347
+ { ...baseInProgressWorkOrder, id: baseInProgressWorkOrder.id, status: "IN_PROGRESS" },
348
+ ]);
349
+ spies.update.mockReturnValueOnce(undefined);
350
+
351
+ const result = await run(
352
+ db,
353
+ {
354
+ ...baseInput,
355
+ backflushRequired: true,
356
+ manuallyIssuedQuantity: 0,
357
+ },
358
+ ctx,
359
+ );
360
+
361
+ expect(result.ok).toBe(true);
362
+ if (result.ok) {
363
+ expect(result.value.backflushHandoff?.productionOrderReference).toBe(
364
+ baseInProgressWorkOrder.productionOrderId,
365
+ );
366
+ expect(result.value.backflushHandoff?.completedQuantity).toBe(50);
367
+ }
368
+ });
369
+ });
@@ -0,0 +1,212 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ WorkOrderNotFoundError,
4
+ WorkOrderNotCompletableError,
5
+ WorkOrderNotStartedError,
6
+ InvalidCompletionQuantityError,
7
+ DuplicateBackflushRiskError,
8
+ ReceiptHandoffRequiredError,
9
+ LotReferenceRequiredError,
10
+ SerialReferenceRequiredError,
11
+ } from "../lib/errors.generated";
12
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
13
+
14
+ export interface ReceiptData {
15
+ itemReference?: string | null;
16
+ unitOfMeasure?: string | null;
17
+ siteReference?: string | null;
18
+ postingDate?: Date | null;
19
+ storageLocationReference?: string | null;
20
+ lotTracked: boolean;
21
+ serialTracked: boolean;
22
+ finishedGoodLotReference?: string | null;
23
+ serialReferences?: string[] | null;
24
+ }
25
+
26
+ export interface ZeroQuantityBypassPolicy {
27
+ allowZeroCompletion: boolean;
28
+ reasonCode?: string | null;
29
+ }
30
+
31
+ export interface CompleteWorkOrderInput {
32
+ id: string;
33
+ completedQuantity: number;
34
+ zeroQuantityBypassPolicy?: ZeroQuantityBypassPolicy | null;
35
+ backflushRequired: boolean;
36
+ manuallyIssuedQuantity?: number;
37
+ receiptRequired: boolean;
38
+ receiptData?: ReceiptData | null;
39
+ notes?: string | null;
40
+ }
41
+
42
+ /**
43
+ * Function: completeWorkOrder
44
+ *
45
+ * Finishes execution on an in-progress work order. Records the final completed
46
+ * quantity, validates backflush and receipt-handoff obligations, creates a
47
+ * COMPLETED execution event, and rolls up completion to the parent production
48
+ * order when all sibling work orders are finished.
49
+ */
50
+ export async function run<CF extends Record<string, unknown>>(
51
+ db: Transaction,
52
+ input: CompleteWorkOrderInput & CF,
53
+ _ctx: CommandContext,
54
+ ) {
55
+ const {
56
+ id,
57
+ completedQuantity,
58
+ zeroQuantityBypassPolicy,
59
+ backflushRequired,
60
+ manuallyIssuedQuantity,
61
+ receiptRequired,
62
+ receiptData,
63
+ notes,
64
+ ...customFields
65
+ } = input;
66
+ void customFields;
67
+
68
+ // 1. Fetch work order with lock
69
+ const workOrder = await db
70
+ .selectFrom("WorkOrder")
71
+ .selectAll()
72
+ .where("id", "=", id)
73
+ .forUpdate()
74
+ .executeTakeFirst();
75
+
76
+ if (!workOrder) {
77
+ return err(new WorkOrderNotFoundError(id));
78
+ }
79
+
80
+ // 2. Validate status is IN_PROGRESS
81
+ if (workOrder.status !== "IN_PROGRESS") {
82
+ return err(new WorkOrderNotCompletableError(id));
83
+ }
84
+
85
+ // 3. Validate actual start evidence exists
86
+ if (!workOrder.actualStartDate) {
87
+ return err(new WorkOrderNotStartedError(id));
88
+ }
89
+
90
+ // 4. Validate completed quantity, allowing explicit zero-quantity bypass.
91
+ const zeroQuantityBypassAllowed =
92
+ completedQuantity === 0 && zeroQuantityBypassPolicy?.allowZeroCompletion === true;
93
+ if (completedQuantity < 0 || (completedQuantity === 0 && !zeroQuantityBypassAllowed)) {
94
+ return err(new InvalidCompletionQuantityError(id));
95
+ }
96
+
97
+ // 5. Validate backflush does not duplicate manual issue
98
+ if (backflushRequired && manuallyIssuedQuantity != null && manuallyIssuedQuantity > 0) {
99
+ return err(new DuplicateBackflushRiskError(id));
100
+ }
101
+
102
+ // 6. Validate receipt handoff data when receipt is required
103
+ if (receiptRequired) {
104
+ if (!receiptData) {
105
+ return err(new ReceiptHandoffRequiredError(id));
106
+ }
107
+
108
+ if (
109
+ !receiptData.itemReference ||
110
+ !receiptData.unitOfMeasure ||
111
+ !receiptData.siteReference ||
112
+ !receiptData.postingDate
113
+ ) {
114
+ return err(new ReceiptHandoffRequiredError(id));
115
+ }
116
+
117
+ if (receiptData.lotTracked && !receiptData.finishedGoodLotReference) {
118
+ return err(new LotReferenceRequiredError(id));
119
+ }
120
+
121
+ if (
122
+ receiptData.serialTracked &&
123
+ (!receiptData.serialReferences || receiptData.serialReferences.length === 0)
124
+ ) {
125
+ return err(new SerialReferenceRequiredError(id));
126
+ }
127
+ }
128
+
129
+ // 7. Update work order to COMPLETE
130
+ const now = new Date();
131
+ const updatedWorkOrder = await db
132
+ .updateTable("WorkOrder")
133
+ .set({
134
+ status: "COMPLETE",
135
+ completedQuantity: workOrder.completedQuantity + completedQuantity,
136
+ executionNotes: notes ?? workOrder.executionNotes,
137
+ updatedAt: now,
138
+ })
139
+ .where("id", "=", id)
140
+ .returningAll()
141
+ .executeTakeFirstOrThrow();
142
+
143
+ // 8. Create COMPLETED execution event
144
+ await db
145
+ .insertInto("WorkOrderExecutionEvent")
146
+ .values({
147
+ workOrderId: id,
148
+ eventType: "COMPLETED",
149
+ timestamp: now,
150
+ quantity: completedQuantity,
151
+ timeValue: null,
152
+ scrapValue: null,
153
+ notes: notes ?? null,
154
+ createdAt: now,
155
+ updatedAt: null,
156
+ })
157
+ .execute();
158
+
159
+ const backflushHandoff = backflushRequired
160
+ ? {
161
+ productionOrderReference: workOrder.productionOrderId,
162
+ workOrderReference: id,
163
+ completedQuantity,
164
+ manuallyIssuedQuantity: manuallyIssuedQuantity ?? 0,
165
+ postingDate: receiptData?.postingDate ?? now,
166
+ bypassReason: zeroQuantityBypassAllowed
167
+ ? (zeroQuantityBypassPolicy?.reasonCode ?? null)
168
+ : null,
169
+ }
170
+ : null;
171
+
172
+ const receiptHandoff =
173
+ receiptRequired && receiptData
174
+ ? {
175
+ productionOrderReference: workOrder.productionOrderId,
176
+ workOrderReference: id,
177
+ itemReference: receiptData.itemReference,
178
+ quantity: completedQuantity,
179
+ unitOfMeasure: receiptData.unitOfMeasure,
180
+ siteReference: receiptData.siteReference,
181
+ postingDate: receiptData.postingDate,
182
+ storageLocationReference: receiptData.storageLocationReference ?? null,
183
+ finishedGoodLotReference: receiptData.finishedGoodLotReference ?? null,
184
+ serialReferences: receiptData.serialReferences ?? null,
185
+ }
186
+ : null;
187
+
188
+ // 9. Roll up to parent production order
189
+ const siblingWorkOrders = await db
190
+ .selectFrom("WorkOrder")
191
+ .selectAll()
192
+ .where("productionOrderId", "=", workOrder.productionOrderId)
193
+ .execute();
194
+
195
+ const allComplete = (siblingWorkOrders as { id: string; status: string }[]).every((wo) => {
196
+ if (wo.id === id) return true; // this one was just completed
197
+ return wo.status === "COMPLETE" || wo.status === "CANCELLED";
198
+ });
199
+
200
+ if (allComplete) {
201
+ await db
202
+ .updateTable("ProductionOrder")
203
+ .set({
204
+ status: "COMPLETED",
205
+ updatedAt: now,
206
+ })
207
+ .where("id", "=", workOrder.productionOrderId)
208
+ .execute();
209
+ }
210
+
211
+ return ok({ workOrder: updatedWorkOrder, backflushHandoff, receiptHandoff });
212
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./createBillOfMaterial";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const createBillOfMaterial = defineCommand(permissions.createBillOfMaterial, run);