@tailor-platform/erp-kit 0.5.0 → 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 (262) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/cli.mjs +139 -35
  3. package/package.json +1 -1
  4. package/skills/erp-kit-app-5-impl-backend/SKILL.md +10 -5
  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/init-module.test.ts +17 -3
  9. package/src/commands/init-module.ts +0 -12
  10. package/src/commands/lib/discovery.test.ts +13 -3
  11. package/src/commands/lib/discovery.ts +10 -2
  12. package/src/commands/lib/paths.ts +4 -2
  13. package/src/commands/lib/sync-check-tests.test.ts +84 -6
  14. package/src/commands/lib/sync-check-tests.ts +63 -3
  15. package/src/commands/sync-check.ts +7 -3
  16. package/src/generator/generate-app-code.ts +51 -16
  17. package/src/generator/generate-code-boilerplate.test.ts +9 -1
  18. package/src/generator/generate-stubs.ts +4 -0
  19. package/src/generator/scaffold.ts +6 -2
  20. package/src/generator/stub-templates.test.ts +11 -0
  21. package/src/generator/stub-templates.ts +22 -1
  22. package/src/mdschema.ts +39 -3
  23. package/src/modules/inventory/docs/features/inventory-adjustment.md +2 -1
  24. package/src/modules/inventory/docs/features/scrap-management.md +39 -1
  25. package/src/modules/manufacturing/README.md +63 -0
  26. package/src/modules/manufacturing/command/activateBillOfMaterial.generated.ts +6 -0
  27. package/src/modules/manufacturing/command/activateBillOfMaterial.test.ts +166 -0
  28. package/src/modules/manufacturing/command/activateBillOfMaterial.ts +173 -0
  29. package/src/modules/manufacturing/command/activateRouting.generated.ts +6 -0
  30. package/src/modules/manufacturing/command/activateRouting.test.ts +152 -0
  31. package/src/modules/manufacturing/command/activateRouting.ts +92 -0
  32. package/src/modules/manufacturing/command/activateWorkCenter.generated.ts +6 -0
  33. package/src/modules/manufacturing/command/activateWorkCenter.test.ts +135 -0
  34. package/src/modules/manufacturing/command/activateWorkCenter.ts +91 -0
  35. package/src/modules/manufacturing/command/cancelProductionOrder.generated.ts +6 -0
  36. package/src/modules/manufacturing/command/cancelProductionOrder.test.ts +151 -0
  37. package/src/modules/manufacturing/command/cancelProductionOrder.ts +114 -0
  38. package/src/modules/manufacturing/command/closeProductionOrder.generated.ts +6 -0
  39. package/src/modules/manufacturing/command/closeProductionOrder.test.ts +126 -0
  40. package/src/modules/manufacturing/command/closeProductionOrder.ts +87 -0
  41. package/src/modules/manufacturing/command/completeProductionOrder.generated.ts +6 -0
  42. package/src/modules/manufacturing/command/completeProductionOrder.test.ts +132 -0
  43. package/src/modules/manufacturing/command/completeProductionOrder.ts +97 -0
  44. package/src/modules/manufacturing/command/completeWorkOrder.generated.ts +6 -0
  45. package/src/modules/manufacturing/command/completeWorkOrder.test.ts +369 -0
  46. package/src/modules/manufacturing/command/completeWorkOrder.ts +212 -0
  47. package/src/modules/manufacturing/command/createBillOfMaterial.generated.ts +6 -0
  48. package/src/modules/manufacturing/command/createBillOfMaterial.test.ts +210 -0
  49. package/src/modules/manufacturing/command/createBillOfMaterial.ts +176 -0
  50. package/src/modules/manufacturing/command/createProductionOrder.generated.ts +6 -0
  51. package/src/modules/manufacturing/command/createProductionOrder.test.ts +160 -0
  52. package/src/modules/manufacturing/command/createProductionOrder.ts +129 -0
  53. package/src/modules/manufacturing/command/createRouting.generated.ts +6 -0
  54. package/src/modules/manufacturing/command/createRouting.test.ts +168 -0
  55. package/src/modules/manufacturing/command/createRouting.ts +128 -0
  56. package/src/modules/manufacturing/command/createWorkCenter.generated.ts +6 -0
  57. package/src/modules/manufacturing/command/createWorkCenter.test.ts +148 -0
  58. package/src/modules/manufacturing/command/createWorkCenter.ts +131 -0
  59. package/src/modules/manufacturing/command/deactivateBillOfMaterial.generated.ts +6 -0
  60. package/src/modules/manufacturing/command/deactivateBillOfMaterial.test.ts +103 -0
  61. package/src/modules/manufacturing/command/deactivateBillOfMaterial.ts +78 -0
  62. package/src/modules/manufacturing/command/deactivateRouting.generated.ts +6 -0
  63. package/src/modules/manufacturing/command/deactivateRouting.test.ts +112 -0
  64. package/src/modules/manufacturing/command/deactivateRouting.ts +76 -0
  65. package/src/modules/manufacturing/command/deactivateWorkCenter.generated.ts +6 -0
  66. package/src/modules/manufacturing/command/deactivateWorkCenter.test.ts +113 -0
  67. package/src/modules/manufacturing/command/deactivateWorkCenter.ts +85 -0
  68. package/src/modules/manufacturing/command/pauseWorkOrder.generated.ts +6 -0
  69. package/src/modules/manufacturing/command/pauseWorkOrder.test.ts +118 -0
  70. package/src/modules/manufacturing/command/pauseWorkOrder.ts +82 -0
  71. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.generated.ts +6 -0
  72. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.test.ts +183 -0
  73. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.ts +139 -0
  74. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.generated.ts +6 -0
  75. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.test.ts +120 -0
  76. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.ts +110 -0
  77. package/src/modules/manufacturing/command/releaseProductionOrder.generated.ts +6 -0
  78. package/src/modules/manufacturing/command/releaseProductionOrder.test.ts +220 -0
  79. package/src/modules/manufacturing/command/releaseProductionOrder.ts +450 -0
  80. package/src/modules/manufacturing/command/reopenProductionOrder.generated.ts +6 -0
  81. package/src/modules/manufacturing/command/reopenProductionOrder.test.ts +196 -0
  82. package/src/modules/manufacturing/command/reopenProductionOrder.ts +98 -0
  83. package/src/modules/manufacturing/command/reportWorkOrderProgress.generated.ts +6 -0
  84. package/src/modules/manufacturing/command/reportWorkOrderProgress.test.ts +204 -0
  85. package/src/modules/manufacturing/command/reportWorkOrderProgress.ts +129 -0
  86. package/src/modules/manufacturing/command/rescheduleProductionOrder.generated.ts +6 -0
  87. package/src/modules/manufacturing/command/rescheduleProductionOrder.test.ts +185 -0
  88. package/src/modules/manufacturing/command/rescheduleProductionOrder.ts +95 -0
  89. package/src/modules/manufacturing/command/resumeWorkOrder.generated.ts +6 -0
  90. package/src/modules/manufacturing/command/resumeWorkOrder.test.ts +122 -0
  91. package/src/modules/manufacturing/command/resumeWorkOrder.ts +94 -0
  92. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.generated.ts +6 -0
  93. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.test.ts +231 -0
  94. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.ts +137 -0
  95. package/src/modules/manufacturing/command/startWorkOrder.generated.ts +6 -0
  96. package/src/modules/manufacturing/command/startWorkOrder.test.ts +118 -0
  97. package/src/modules/manufacturing/command/startWorkOrder.ts +126 -0
  98. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.generated.ts +6 -0
  99. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.test.ts +153 -0
  100. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.ts +106 -0
  101. package/src/modules/manufacturing/command/unreleaseProductionOrder.generated.ts +6 -0
  102. package/src/modules/manufacturing/command/unreleaseProductionOrder.test.ts +140 -0
  103. package/src/modules/manufacturing/command/unreleaseProductionOrder.ts +131 -0
  104. package/src/modules/manufacturing/command/updateBillOfMaterial.generated.ts +6 -0
  105. package/src/modules/manufacturing/command/updateBillOfMaterial.test.ts +149 -0
  106. package/src/modules/manufacturing/command/updateBillOfMaterial.ts +174 -0
  107. package/src/modules/manufacturing/command/updateProductionOrder.generated.ts +6 -0
  108. package/src/modules/manufacturing/command/updateProductionOrder.test.ts +112 -0
  109. package/src/modules/manufacturing/command/updateProductionOrder.ts +145 -0
  110. package/src/modules/manufacturing/command/updateRouting.generated.ts +6 -0
  111. package/src/modules/manufacturing/command/updateRouting.test.ts +211 -0
  112. package/src/modules/manufacturing/command/updateRouting.ts +124 -0
  113. package/src/modules/manufacturing/command/updateWorkCenter.generated.ts +6 -0
  114. package/src/modules/manufacturing/command/updateWorkCenter.test.ts +152 -0
  115. package/src/modules/manufacturing/command/updateWorkCenter.ts +137 -0
  116. package/src/modules/manufacturing/db/.gitkeep +0 -0
  117. package/src/modules/manufacturing/db/billOfMaterial.ts +70 -0
  118. package/src/modules/manufacturing/db/billOfMaterialLine.ts +49 -0
  119. package/src/modules/manufacturing/db/costVarianceLine.ts +53 -0
  120. package/src/modules/manufacturing/db/manufacturingCostLine.ts +35 -0
  121. package/src/modules/manufacturing/db/manufacturingCostSettlementRecord.ts +39 -0
  122. package/src/modules/manufacturing/db/manufacturingCostSummary.ts +59 -0
  123. package/src/modules/manufacturing/db/productionOrder.ts +83 -0
  124. package/src/modules/manufacturing/db/productionOrderBomSnapshot.ts +44 -0
  125. package/src/modules/manufacturing/db/productionOrderCostBaseline.ts +44 -0
  126. package/src/modules/manufacturing/db/productionOrderMaterialRequirement.ts +57 -0
  127. package/src/modules/manufacturing/db/productionOrderRoutingSnapshot.ts +43 -0
  128. package/src/modules/manufacturing/db/routing.ts +63 -0
  129. package/src/modules/manufacturing/db/routingOperation.ts +57 -0
  130. package/src/modules/manufacturing/db/workCenter.ts +87 -0
  131. package/src/modules/manufacturing/db/workOrder.ts +65 -0
  132. package/src/modules/manufacturing/db/workOrderExecutionEvent.ts +54 -0
  133. package/src/modules/manufacturing/docs/commands/ActivateBillOfMaterial.md +50 -0
  134. package/src/modules/manufacturing/docs/commands/ActivateRouting.md +48 -0
  135. package/src/modules/manufacturing/docs/commands/ActivateWorkCenter.md +49 -0
  136. package/src/modules/manufacturing/docs/commands/CancelProductionOrder.md +48 -0
  137. package/src/modules/manufacturing/docs/commands/CloseProductionOrder.md +46 -0
  138. package/src/modules/manufacturing/docs/commands/CompleteProductionOrder.md +48 -0
  139. package/src/modules/manufacturing/docs/commands/CompleteWorkOrder.md +66 -0
  140. package/src/modules/manufacturing/docs/commands/CreateBillOfMaterial.md +54 -0
  141. package/src/modules/manufacturing/docs/commands/CreateProductionOrder.md +49 -0
  142. package/src/modules/manufacturing/docs/commands/CreateRouting.md +50 -0
  143. package/src/modules/manufacturing/docs/commands/CreateWorkCenter.md +51 -0
  144. package/src/modules/manufacturing/docs/commands/DeactivateBillOfMaterial.md +45 -0
  145. package/src/modules/manufacturing/docs/commands/DeactivateRouting.md +45 -0
  146. package/src/modules/manufacturing/docs/commands/DeactivateWorkCenter.md +45 -0
  147. package/src/modules/manufacturing/docs/commands/PauseWorkOrder.md +44 -0
  148. package/src/modules/manufacturing/docs/commands/RecordInventoryIssueOutcome.md +59 -0
  149. package/src/modules/manufacturing/docs/commands/RecordManufacturingCostSettlementAcknowledgment.md +49 -0
  150. package/src/modules/manufacturing/docs/commands/ReleaseProductionOrder.md +57 -0
  151. package/src/modules/manufacturing/docs/commands/ReopenProductionOrder.md +54 -0
  152. package/src/modules/manufacturing/docs/commands/ReportWorkOrderProgress.md +53 -0
  153. package/src/modules/manufacturing/docs/commands/RescheduleProductionOrder.md +45 -0
  154. package/src/modules/manufacturing/docs/commands/ResumeWorkOrder.md +44 -0
  155. package/src/modules/manufacturing/docs/commands/ReviewManufacturingCostSummary.md +52 -0
  156. package/src/modules/manufacturing/docs/commands/StartWorkOrder.md +46 -0
  157. package/src/modules/manufacturing/docs/commands/TechnicallyCompleteProductionOrder.md +51 -0
  158. package/src/modules/manufacturing/docs/commands/UnreleaseProductionOrder.md +46 -0
  159. package/src/modules/manufacturing/docs/commands/UpdateBillOfMaterial.md +48 -0
  160. package/src/modules/manufacturing/docs/commands/UpdateProductionOrder.md +48 -0
  161. package/src/modules/manufacturing/docs/commands/UpdateRouting.md +52 -0
  162. package/src/modules/manufacturing/docs/commands/UpdateWorkCenter.md +48 -0
  163. package/src/modules/manufacturing/docs/features/bill-of-material-management.md +83 -0
  164. package/src/modules/manufacturing/docs/features/manufacturing-cost-and-variance.md +191 -0
  165. package/src/modules/manufacturing/docs/features/production-order-lifecycle.md +103 -0
  166. package/src/modules/manufacturing/docs/features/routing-and-work-center-definition.md +63 -0
  167. package/src/modules/manufacturing/docs/features/work-order-execution.md +115 -0
  168. package/src/modules/manufacturing/docs/models/BillOfMaterial.md +60 -0
  169. package/src/modules/manufacturing/docs/models/ManufacturingCostSummary.md +66 -0
  170. package/src/modules/manufacturing/docs/models/ProductionOrder.md +76 -0
  171. package/src/modules/manufacturing/docs/models/Routing.md +58 -0
  172. package/src/modules/manufacturing/docs/models/WorkCenter.md +56 -0
  173. package/src/modules/manufacturing/docs/models/WorkOrder.md +63 -0
  174. package/src/modules/manufacturing/docs/queries/DetectBillOfMaterialCircularReference.md +39 -0
  175. package/src/modules/manufacturing/docs/queries/ExplodeBillOfMaterial.md +56 -0
  176. package/src/modules/manufacturing/docs/queries/GetBillOfMaterial.md +37 -0
  177. package/src/modules/manufacturing/docs/queries/GetManufacturingCostSummary.md +39 -0
  178. package/src/modules/manufacturing/docs/queries/GetProductionOrder.md +37 -0
  179. package/src/modules/manufacturing/docs/queries/GetRouting.md +39 -0
  180. package/src/modules/manufacturing/docs/queries/GetWorkCenter.md +35 -0
  181. package/src/modules/manufacturing/docs/queries/GetWorkOrder.md +38 -0
  182. package/src/modules/manufacturing/docs/queries/ListBillOfMaterialsByItem.md +42 -0
  183. package/src/modules/manufacturing/docs/queries/ListManufacturingCostSummariesByStatus.md +41 -0
  184. package/src/modules/manufacturing/docs/queries/ListProductionOrdersByStatus.md +41 -0
  185. package/src/modules/manufacturing/docs/queries/ListRoutingsByItem.md +42 -0
  186. package/src/modules/manufacturing/docs/queries/ListWorkCentersBySite.md +38 -0
  187. package/src/modules/manufacturing/docs/queries/ListWorkOrdersByProductionOrder.md +39 -0
  188. package/src/modules/manufacturing/docs/queries/ListWorkOrdersByWorkCenter.md +43 -0
  189. package/src/modules/manufacturing/executor/.gitkeep +0 -0
  190. package/src/modules/manufacturing/generated/enums.ts +113 -0
  191. package/src/modules/manufacturing/generated/kysely-tailordb.ts +247 -0
  192. package/src/modules/manufacturing/index.ts +2 -0
  193. package/src/modules/manufacturing/lib/_db_deps.ts +22 -0
  194. package/src/modules/manufacturing/lib/errors.generated.ts +592 -0
  195. package/src/modules/manufacturing/lib/permissions.generated.ts +35 -0
  196. package/src/modules/manufacturing/lib/types.ts +111 -0
  197. package/src/modules/manufacturing/module.ts +226 -0
  198. package/src/modules/manufacturing/permissions.ts +3 -0
  199. package/src/modules/manufacturing/query/.gitkeep +0 -0
  200. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.generated.ts +5 -0
  201. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.test.ts +115 -0
  202. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.ts +79 -0
  203. package/src/modules/manufacturing/query/explodeBillOfMaterial.generated.ts +5 -0
  204. package/src/modules/manufacturing/query/explodeBillOfMaterial.test.ts +445 -0
  205. package/src/modules/manufacturing/query/explodeBillOfMaterial.ts +306 -0
  206. package/src/modules/manufacturing/query/getBillOfMaterial.generated.ts +5 -0
  207. package/src/modules/manufacturing/query/getBillOfMaterial.test.ts +64 -0
  208. package/src/modules/manufacturing/query/getBillOfMaterial.ts +27 -0
  209. package/src/modules/manufacturing/query/getManufacturingCostSummary.generated.ts +5 -0
  210. package/src/modules/manufacturing/query/getManufacturingCostSummary.test.ts +147 -0
  211. package/src/modules/manufacturing/query/getManufacturingCostSummary.ts +46 -0
  212. package/src/modules/manufacturing/query/getProductionOrder.generated.ts +5 -0
  213. package/src/modules/manufacturing/query/getProductionOrder.test.ts +139 -0
  214. package/src/modules/manufacturing/query/getProductionOrder.ts +84 -0
  215. package/src/modules/manufacturing/query/getRouting.generated.ts +5 -0
  216. package/src/modules/manufacturing/query/getRouting.test.ts +71 -0
  217. package/src/modules/manufacturing/query/getRouting.ts +34 -0
  218. package/src/modules/manufacturing/query/getWorkCenter.generated.ts +5 -0
  219. package/src/modules/manufacturing/query/getWorkCenter.test.ts +37 -0
  220. package/src/modules/manufacturing/query/getWorkCenter.ts +21 -0
  221. package/src/modules/manufacturing/query/getWorkOrder.generated.ts +5 -0
  222. package/src/modules/manufacturing/query/getWorkOrder.test.ts +73 -0
  223. package/src/modules/manufacturing/query/getWorkOrder.ts +28 -0
  224. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.generated.ts +5 -0
  225. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.test.ts +107 -0
  226. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.ts +58 -0
  227. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.generated.ts +5 -0
  228. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.test.ts +96 -0
  229. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.ts +77 -0
  230. package/src/modules/manufacturing/query/listProductionOrdersByStatus.generated.ts +5 -0
  231. package/src/modules/manufacturing/query/listProductionOrdersByStatus.test.ts +121 -0
  232. package/src/modules/manufacturing/query/listProductionOrdersByStatus.ts +83 -0
  233. package/src/modules/manufacturing/query/listRoutingsByItem.generated.ts +5 -0
  234. package/src/modules/manufacturing/query/listRoutingsByItem.test.ts +110 -0
  235. package/src/modules/manufacturing/query/listRoutingsByItem.ts +54 -0
  236. package/src/modules/manufacturing/query/listWorkCentersBySite.generated.ts +5 -0
  237. package/src/modules/manufacturing/query/listWorkCentersBySite.test.ts +81 -0
  238. package/src/modules/manufacturing/query/listWorkCentersBySite.ts +70 -0
  239. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.generated.ts +5 -0
  240. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.test.ts +102 -0
  241. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.ts +53 -0
  242. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.generated.ts +5 -0
  243. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.test.ts +143 -0
  244. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.ts +56 -0
  245. package/src/modules/manufacturing/seed/index.ts +19 -0
  246. package/src/modules/manufacturing/tailor.config.ts +13 -0
  247. package/src/modules/manufacturing/tailor.d.ts +13 -0
  248. package/src/modules/manufacturing/testing/commandTestUtils.ts +29 -0
  249. package/src/modules/manufacturing/testing/fixtures.ts +402 -0
  250. package/templates/scaffold/app/backend/package.json +9 -2
  251. package/templates/scaffold/app/backend/src/tests/utils/graphql-client.ts +66 -0
  252. package/templates/scaffold/app/backend/src/tests/utils/setup.ts +21 -0
  253. package/templates/scaffold/app/backend/tsconfig.json +9 -2
  254. package/templates/scaffold/app/backend/vitest.config.ts +35 -0
  255. package/templates/scaffold/app/frontend/package.json +2 -2
  256. package/templates/scaffold/module/__dot__gitignore +3 -0
  257. package/templates/scaffold/module/eslint.config.js +31 -0
  258. package/templates/scaffold/module/generated/kysely-tailordb.ts +3 -0
  259. package/templates/scaffold/module/lib/types.ts +1 -6
  260. package/templates/scaffold/module/package.json +26 -0
  261. package/templates/scaffold/module/tsconfig.json +16 -0
  262. /package/{templates/scaffold/module/generated → src/modules/manufacturing/command}/.gitkeep +0 -0
@@ -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);
@@ -0,0 +1,210 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ ParentItemNotFoundError,
6
+ ParentItemNotManufacturedError,
7
+ InvalidBomTypeError,
8
+ ComponentLineRequiredError,
9
+ InvalidComponentQuantityError,
10
+ ComponentItemInactiveError,
11
+ BomVersionConflictError,
12
+ } from "../lib/errors.generated";
13
+ import { baseDraftBom, baseBomLine } from "../testing/fixtures";
14
+ import { run } from "./createBillOfMaterial";
15
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
16
+
17
+ describe("createBillOfMaterial", () => {
18
+ const ctx: CommandContext = {
19
+ actorId: "test-actor",
20
+ permissions: ["manufacturing:createBillOfMaterial"],
21
+ };
22
+
23
+ const validInput = {
24
+ parentItemId: "item-1",
25
+ companyId: "company-1",
26
+ siteId: null,
27
+ bomType: "MANUFACTURE",
28
+ effectivityStartDate: null,
29
+ effectivityEndDate: null,
30
+ defaultSelection: true,
31
+ revisionNumber: "V1",
32
+ lines: [
33
+ {
34
+ itemId: "item-2",
35
+ requiredQuantity: 2.0,
36
+ unitOfMeasure: "EA",
37
+ scrapAssumption: 5.0,
38
+ isSubassembly: false,
39
+ },
40
+ ],
41
+ };
42
+
43
+ it("creates a draft BOM for an active manufactured item", async () => {
44
+ const { db, spies } = createMockDb<Transaction>();
45
+ const createdBom = {
46
+ ...baseDraftBom,
47
+ id: "new-bom-id",
48
+ };
49
+
50
+ // parent item exists
51
+ spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
52
+ // component item exists
53
+ spies.select.mockReturnValueOnce({ id: "item-2", status: "ACTIVE" });
54
+ // no existing BOM with same version identity
55
+ spies.select.mockReturnValueOnce(undefined);
56
+ // insert BOM
57
+ spies.insert.mockReturnValueOnce(createdBom);
58
+ // insert BOM line
59
+ spies.insert.mockReturnValueOnce({ ...baseBomLine, billOfMaterialId: "new-bom-id" });
60
+
61
+ const result = await run(db, validInput, ctx);
62
+
63
+ expect(result.ok).toBe(true);
64
+ if (result.ok) {
65
+ expect(result.value.billOfMaterial.status).toBe("DRAFT");
66
+ expect(result.value.billOfMaterial.id).toBe("new-bom-id");
67
+ }
68
+ expect(spies.insert).toHaveBeenCalled();
69
+ });
70
+
71
+ it("returns error when the parent item does not exist", async () => {
72
+ const { db, spies } = createMockDb<Transaction>();
73
+
74
+ // parent item not found
75
+ spies.select.mockReturnValueOnce(undefined);
76
+
77
+ const result = await run(db, validInput, ctx);
78
+
79
+ expect(result.ok).toBe(false);
80
+ if (!result.ok) {
81
+ expect(result.error).toBeInstanceOf(ParentItemNotFoundError);
82
+ }
83
+ });
84
+
85
+ it("returns error when the parent item is not manufacturable", async () => {
86
+ const { db, spies } = createMockDb<Transaction>();
87
+
88
+ // parent item exists but not manufacturable
89
+ spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: false });
90
+
91
+ const result = await run(db, validInput, ctx);
92
+
93
+ expect(result.ok).toBe(false);
94
+ if (!result.ok) {
95
+ expect(result.error).toBeInstanceOf(ParentItemNotManufacturedError);
96
+ }
97
+ });
98
+
99
+ it("returns error when bomType is invalid", async () => {
100
+ const { db, spies } = createMockDb<Transaction>();
101
+
102
+ // parent item exists
103
+ spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
104
+
105
+ const result = await run(db, { ...validInput, bomType: "UNSUPPORTED" }, ctx);
106
+
107
+ expect(result.ok).toBe(false);
108
+ if (!result.ok) {
109
+ expect(result.error).toBeInstanceOf(InvalidBomTypeError);
110
+ }
111
+ });
112
+
113
+ it("returns error when no component lines are provided", async () => {
114
+ const { db, spies } = createMockDb<Transaction>();
115
+
116
+ // parent item exists
117
+ spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
118
+
119
+ const result = await run(db, { ...validInput, lines: [] }, ctx);
120
+
121
+ expect(result.ok).toBe(false);
122
+ if (!result.ok) {
123
+ expect(result.error).toBeInstanceOf(ComponentLineRequiredError);
124
+ }
125
+ });
126
+
127
+ it("returns error when a component quantity is not positive", async () => {
128
+ const { db, spies } = createMockDb<Transaction>();
129
+
130
+ // parent item exists
131
+ spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
132
+
133
+ const result = await run(
134
+ db,
135
+ {
136
+ ...validInput,
137
+ lines: [{ itemId: "item-2", requiredQuantity: 0, unitOfMeasure: "EA" }],
138
+ },
139
+ ctx,
140
+ );
141
+
142
+ expect(result.ok).toBe(false);
143
+ if (!result.ok) {
144
+ expect(result.error).toBeInstanceOf(InvalidComponentQuantityError);
145
+ }
146
+ });
147
+
148
+ it("returns error when a component item is inactive", async () => {
149
+ const { db, spies } = createMockDb<Transaction>();
150
+
151
+ // parent item exists
152
+ spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
153
+ // component item not found (treated as inactive)
154
+ spies.select.mockReturnValueOnce(undefined);
155
+
156
+ const result = await run(db, validInput, ctx);
157
+
158
+ expect(result.ok).toBe(false);
159
+ if (!result.ok) {
160
+ expect(result.error).toBeInstanceOf(ComponentItemInactiveError);
161
+ }
162
+ });
163
+
164
+ it("returns error when a component item exists but is inactive", async () => {
165
+ const { db, spies } = createMockDb<Transaction>();
166
+
167
+ // parent item exists
168
+ spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
169
+ // component item exists but is inactive
170
+ spies.select.mockReturnValueOnce({ id: "item-2", isActive: false });
171
+
172
+ const result = await run(db, validInput, ctx);
173
+
174
+ expect(result.ok).toBe(false);
175
+ if (!result.ok) {
176
+ expect(result.error).toBeInstanceOf(ComponentItemInactiveError);
177
+ }
178
+ });
179
+
180
+ it("returns error when the version identity already exists in scope", async () => {
181
+ const { db, spies } = createMockDb<Transaction>();
182
+
183
+ // parent item exists
184
+ spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
185
+ // component item exists
186
+ spies.select.mockReturnValueOnce({ id: "item-2", status: "ACTIVE" });
187
+ // existing BOM with same version identity
188
+ spies.select.mockReturnValueOnce(baseDraftBom);
189
+
190
+ const result = await run(db, validInput, ctx);
191
+
192
+ expect(result.ok).toBe(false);
193
+ if (!result.ok) {
194
+ expect(result.error).toBeInstanceOf(BomVersionConflictError);
195
+ }
196
+ });
197
+
198
+ it("returns error when the parent item is inactive", async () => {
199
+ const { db, spies } = createMockDb<Transaction>();
200
+
201
+ spies.select.mockReturnValueOnce({ id: "item-1", status: "INACTIVE", isManufacturable: true });
202
+
203
+ const result = await run(db, validInput, ctx);
204
+
205
+ expect(result.ok).toBe(false);
206
+ if (!result.ok) {
207
+ expect(result.error).toBeInstanceOf(ParentItemNotManufacturedError);
208
+ }
209
+ });
210
+ });
@@ -0,0 +1,176 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ParentItemNotFoundError,
4
+ ParentItemNotManufacturedError,
5
+ InvalidBomTypeError,
6
+ ComponentLineRequiredError,
7
+ InvalidComponentQuantityError,
8
+ ComponentItemInactiveError,
9
+ BomVersionConflictError,
10
+ } from "../lib/errors.generated";
11
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
12
+
13
+ const VALID_BOM_TYPES = ["MANUFACTURE", "PHANTOM", "KIT"] as const;
14
+
15
+ export interface CreateBillOfMaterialLineInput {
16
+ itemId: string;
17
+ requiredQuantity: number;
18
+ unitOfMeasure?: string | null;
19
+ scrapAssumption?: number | null;
20
+ isSubassembly?: boolean | null;
21
+ }
22
+
23
+ export interface CreateBillOfMaterialInput {
24
+ parentItemId: string;
25
+ companyId: string;
26
+ siteId?: string | null;
27
+ bomType: string;
28
+ effectivityStartDate?: Date | null;
29
+ effectivityEndDate?: Date | null;
30
+ defaultSelection?: boolean | null;
31
+ revisionNumber?: string | null;
32
+ lines: CreateBillOfMaterialLineInput[];
33
+ }
34
+
35
+ /**
36
+ * Function: createBillOfMaterial
37
+ *
38
+ * Creates a draft BOM version for one manufactured parent item at company or
39
+ * site scope. Establishes the initial bomType, effectivity window,
40
+ * default-selection intent, and component-line payload.
41
+ */
42
+ export async function run<CF extends Record<string, unknown>>(
43
+ db: Transaction,
44
+ input: CreateBillOfMaterialInput & CF,
45
+ _ctx: CommandContext,
46
+ ) {
47
+ const {
48
+ parentItemId,
49
+ companyId,
50
+ siteId,
51
+ bomType,
52
+ effectivityStartDate,
53
+ effectivityEndDate,
54
+ defaultSelection,
55
+ revisionNumber,
56
+ lines,
57
+ ...customFields
58
+ } = input;
59
+
60
+ // 1. Validate parent item exists
61
+ const parentItem = await db
62
+ .selectFrom("Item")
63
+ .selectAll()
64
+ .where("id", "=", parentItemId)
65
+ .executeTakeFirst();
66
+
67
+ if (!parentItem) {
68
+ return err(new ParentItemNotFoundError(parentItemId));
69
+ }
70
+
71
+ // 2. Validate parent item is active and manufacturable.
72
+ const parentItemRecord = parentItem as Record<string, unknown>;
73
+ const isInactive =
74
+ ("status" in parentItemRecord && parentItemRecord.status !== "ACTIVE") ||
75
+ ("isActive" in parentItemRecord && parentItemRecord.isActive === false);
76
+ const isNotManufacturable =
77
+ "isManufacturable" in parentItemRecord && parentItemRecord.isManufacturable === false;
78
+
79
+ if (isInactive || isNotManufacturable) {
80
+ return err(new ParentItemNotManufacturedError(parentItemId));
81
+ }
82
+
83
+ // 3. Validate bomType
84
+ if (!VALID_BOM_TYPES.includes(bomType as (typeof VALID_BOM_TYPES)[number])) {
85
+ return err(new InvalidBomTypeError(parentItemId));
86
+ }
87
+
88
+ // 4. Validate at least one component line
89
+ if (!lines || lines.length === 0) {
90
+ return err(new ComponentLineRequiredError(parentItemId));
91
+ }
92
+
93
+ // 5. Validate each component line
94
+ for (const line of lines) {
95
+ if (line.requiredQuantity <= 0) {
96
+ return err(new InvalidComponentQuantityError(line.itemId));
97
+ }
98
+
99
+ // Check component item is not inactive (exists in Item table)
100
+ const componentItem = await db
101
+ .selectFrom("Item")
102
+ .selectAll()
103
+ .where("id", "=", line.itemId)
104
+ .executeTakeFirst();
105
+
106
+ if (!componentItem) {
107
+ return err(new ComponentItemInactiveError(line.itemId));
108
+ }
109
+
110
+ // Check component item is active
111
+ if ("isActive" in componentItem && componentItem.isActive === false) {
112
+ return err(new ComponentItemInactiveError(line.itemId));
113
+ }
114
+ if ("status" in componentItem && componentItem.status === "INACTIVE") {
115
+ return err(new ComponentItemInactiveError(line.itemId));
116
+ }
117
+ }
118
+
119
+ // 6. Check version identity uniqueness within same parent item and scope
120
+ const existingBomQuery = db
121
+ .selectFrom("BillOfMaterial")
122
+ .selectAll()
123
+ .where("parentItemId", "=", parentItemId)
124
+ .where("companyId", "=", companyId);
125
+
126
+ const existingBom = await (
127
+ siteId ? existingBomQuery.where("siteId", "=", siteId) : existingBomQuery
128
+ )
129
+ .where("revisionNumber", "=", revisionNumber ?? null)
130
+ .forUpdate()
131
+ .executeTakeFirst();
132
+
133
+ if (existingBom) {
134
+ return err(new BomVersionConflictError(parentItemId));
135
+ }
136
+
137
+ // 7. Create draft BOM
138
+ const billOfMaterial = await db
139
+ .insertInto("BillOfMaterial")
140
+ .values({
141
+ ...(customFields as Record<string, unknown>),
142
+ parentItemId,
143
+ companyId,
144
+ siteId: siteId ?? null,
145
+ bomType: bomType as "MANUFACTURE" | "PHANTOM" | "KIT",
146
+ effectivityStartDate: effectivityStartDate ?? null,
147
+ effectivityEndDate: effectivityEndDate ?? null,
148
+ defaultSelection: defaultSelection ?? null,
149
+ revisionNumber: revisionNumber ?? null,
150
+ status: "DRAFT",
151
+ createdAt: new Date(),
152
+ updatedAt: null,
153
+ })
154
+ .returningAll()
155
+ .executeTakeFirst();
156
+
157
+ // 8. Create component lines
158
+ for (const line of lines) {
159
+ await db
160
+ .insertInto("BillOfMaterialLine")
161
+ .values({
162
+ billOfMaterialId: billOfMaterial!.id,
163
+ itemId: line.itemId,
164
+ requiredQuantity: line.requiredQuantity,
165
+ unitOfMeasure: line.unitOfMeasure ?? null,
166
+ scrapAssumption: line.scrapAssumption ?? null,
167
+ isSubassembly: line.isSubassembly ?? null,
168
+ createdAt: new Date(),
169
+ updatedAt: null,
170
+ })
171
+ .returningAll()
172
+ .executeTakeFirst();
173
+ }
174
+
175
+ return ok({ billOfMaterial: billOfMaterial! });
176
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./createProductionOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const createProductionOrder = defineCommand(permissions.createProductionOrder, run);