@open-mercato/core 0.10.1-develop.7709.1.046124599c → 0.10.1-develop.7714.1.9dbb8200d8

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 (276) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/asn/index.js +19 -0
  3. package/dist/generated/entities/asn/index.js.map +7 -0
  4. package/dist/generated/entities/putaway_task/index.js +25 -0
  5. package/dist/generated/entities/putaway_task/index.js.map +7 -0
  6. package/dist/generated/entities/receiving_line/index.js +21 -0
  7. package/dist/generated/entities/receiving_line/index.js.map +7 -0
  8. package/dist/generated/entities.ids.generated.js +4 -1
  9. package/dist/generated/entities.ids.generated.js.map +2 -2
  10. package/dist/generated/entity-fields-registry.js +34 -0
  11. package/dist/generated/entity-fields-registry.js.map +2 -2
  12. package/dist/modules/communication_channels/lib/sanitize-channel-html.js +22 -3
  13. package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +2 -2
  14. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +40 -11
  15. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +2 -2
  16. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +13 -2
  17. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
  18. package/dist/modules/wms/acl.js +2 -0
  19. package/dist/modules/wms/acl.js.map +2 -2
  20. package/dist/modules/wms/api/asns/[id]/complete/route.js +61 -0
  21. package/dist/modules/wms/api/asns/[id]/complete/route.js.map +7 -0
  22. package/dist/modules/wms/api/asns/[id]/receive/route.js +68 -0
  23. package/dist/modules/wms/api/asns/[id]/receive/route.js.map +7 -0
  24. package/dist/modules/wms/api/asns/route.js +197 -0
  25. package/dist/modules/wms/api/asns/route.js.map +7 -0
  26. package/dist/modules/wms/api/inventory/helpers.js +42 -34
  27. package/dist/modules/wms/api/inventory/helpers.js.map +2 -2
  28. package/dist/modules/wms/api/inventory/receive/route.js +1 -1
  29. package/dist/modules/wms/api/inventory/receive/route.js.map +2 -2
  30. package/dist/modules/wms/api/listEnrichers.js +23 -0
  31. package/dist/modules/wms/api/listEnrichers.js.map +2 -2
  32. package/dist/modules/wms/api/putaway-tasks/[id]/assign/route.js +56 -0
  33. package/dist/modules/wms/api/putaway-tasks/[id]/assign/route.js.map +7 -0
  34. package/dist/modules/wms/api/putaway-tasks/[id]/cancel/route.js +56 -0
  35. package/dist/modules/wms/api/putaway-tasks/[id]/cancel/route.js.map +7 -0
  36. package/dist/modules/wms/api/putaway-tasks/[id]/complete/route.js +65 -0
  37. package/dist/modules/wms/api/putaway-tasks/[id]/complete/route.js.map +7 -0
  38. package/dist/modules/wms/api/putaway-tasks/[id]/start/route.js +56 -0
  39. package/dist/modules/wms/api/putaway-tasks/[id]/start/route.js.map +7 -0
  40. package/dist/modules/wms/api/putaway-tasks/create-from-balance/route.js +51 -0
  41. package/dist/modules/wms/api/putaway-tasks/create-from-balance/route.js.map +7 -0
  42. package/dist/modules/wms/api/putaway-tasks/route.js +192 -0
  43. package/dist/modules/wms/api/putaway-tasks/route.js.map +7 -0
  44. package/dist/modules/wms/api/receiving-lines/route.js +180 -0
  45. package/dist/modules/wms/api/receiving-lines/route.js.map +7 -0
  46. package/dist/modules/wms/api/scan/putaway/route.js +104 -0
  47. package/dist/modules/wms/api/scan/putaway/route.js.map +7 -0
  48. package/dist/modules/wms/api/scan/receive/route.js +109 -0
  49. package/dist/modules/wms/api/scan/receive/route.js.map +7 -0
  50. package/dist/modules/wms/api/scan/resolve-location/route.js +89 -0
  51. package/dist/modules/wms/api/scan/resolve-location/route.js.map +7 -0
  52. package/dist/modules/wms/api/scan/resolve-lot/route.js +89 -0
  53. package/dist/modules/wms/api/scan/resolve-lot/route.js.map +7 -0
  54. package/dist/modules/wms/backend/wms/asns/[id]/page.js +9 -0
  55. package/dist/modules/wms/backend/wms/asns/[id]/page.js.map +7 -0
  56. package/dist/modules/wms/backend/wms/asns/[id]/page.meta.js +18 -0
  57. package/dist/modules/wms/backend/wms/asns/[id]/page.meta.js.map +7 -0
  58. package/dist/modules/wms/backend/wms/asns/page.js +9 -0
  59. package/dist/modules/wms/backend/wms/asns/page.js.map +7 -0
  60. package/dist/modules/wms/backend/wms/asns/page.meta.js +18 -0
  61. package/dist/modules/wms/backend/wms/asns/page.meta.js.map +7 -0
  62. package/dist/modules/wms/backend/wms/putaway/page.js +9 -0
  63. package/dist/modules/wms/backend/wms/putaway/page.js.map +7 -0
  64. package/dist/modules/wms/backend/wms/putaway/page.meta.js +18 -0
  65. package/dist/modules/wms/backend/wms/putaway/page.meta.js.map +7 -0
  66. package/dist/modules/wms/backend/wms/receiving/page.js +8 -0
  67. package/dist/modules/wms/backend/wms/receiving/page.js.map +7 -0
  68. package/dist/modules/wms/backend/wms/receiving/page.meta.js +18 -0
  69. package/dist/modules/wms/backend/wms/receiving/page.meta.js.map +7 -0
  70. package/dist/modules/wms/ce.js +27 -0
  71. package/dist/modules/wms/ce.js.map +2 -2
  72. package/dist/modules/wms/commands/asn.js +1746 -0
  73. package/dist/modules/wms/commands/asn.js.map +7 -0
  74. package/dist/modules/wms/commands/index.js +2 -0
  75. package/dist/modules/wms/commands/index.js.map +2 -2
  76. package/dist/modules/wms/commands/inventory-actions.js +347 -243
  77. package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
  78. package/dist/modules/wms/commands/putaway.js +945 -0
  79. package/dist/modules/wms/commands/putaway.js.map +7 -0
  80. package/dist/modules/wms/commands/shared.js +51 -0
  81. package/dist/modules/wms/commands/shared.js.map +2 -2
  82. package/dist/modules/wms/components/backend/AssignPutawayTaskDialog.js +278 -0
  83. package/dist/modules/wms/components/backend/AssignPutawayTaskDialog.js.map +7 -0
  84. package/dist/modules/wms/components/backend/CompletePutawayTaskDialog.js +327 -0
  85. package/dist/modules/wms/components/backend/CompletePutawayTaskDialog.js.map +7 -0
  86. package/dist/modules/wms/components/backend/CreateAsnDialog.js +404 -0
  87. package/dist/modules/wms/components/backend/CreateAsnDialog.js.map +7 -0
  88. package/dist/modules/wms/components/backend/InventoryOperationsSection.js +16 -3
  89. package/dist/modules/wms/components/backend/InventoryOperationsSection.js.map +2 -2
  90. package/dist/modules/wms/components/backend/ReceiveAsnLineDialog.js +476 -0
  91. package/dist/modules/wms/components/backend/ReceiveAsnLineDialog.js.map +7 -0
  92. package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js +2 -2
  93. package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js.map +2 -2
  94. package/dist/modules/wms/components/backend/WmsAsnDetailPage.js +524 -0
  95. package/dist/modules/wms/components/backend/WmsAsnDetailPage.js.map +7 -0
  96. package/dist/modules/wms/components/backend/WmsAsnsListPage.js +339 -0
  97. package/dist/modules/wms/components/backend/WmsAsnsListPage.js.map +7 -0
  98. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +5 -6
  99. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
  100. package/dist/modules/wms/components/backend/WmsLotDetailPage.js +5 -6
  101. package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
  102. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js +14 -10
  103. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +2 -2
  104. package/dist/modules/wms/components/backend/WmsPutawayQueuePage.js +598 -0
  105. package/dist/modules/wms/components/backend/WmsPutawayQueuePage.js.map +7 -0
  106. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +5 -6
  107. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
  108. package/dist/modules/wms/components/backend/asnCompleteOptimisticLock.js +32 -0
  109. package/dist/modules/wms/components/backend/asnCompleteOptimisticLock.js.map +7 -0
  110. package/dist/modules/wms/components/backend/asnReceivingLinesLoader.js +36 -0
  111. package/dist/modules/wms/components/backend/asnReceivingLinesLoader.js.map +7 -0
  112. package/dist/modules/wms/components/backend/inboundStatusUi.js +106 -0
  113. package/dist/modules/wms/components/backend/inboundStatusUi.js.map +7 -0
  114. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +17 -0
  115. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +2 -2
  116. package/dist/modules/wms/components/backend/putawayQueueOptimisticLock.js +14 -0
  117. package/dist/modules/wms/components/backend/putawayQueueOptimisticLock.js.map +7 -0
  118. package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js +4 -0
  119. package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js.map +2 -2
  120. package/dist/modules/wms/data/enrichers.js +117 -12
  121. package/dist/modules/wms/data/enrichers.js.map +2 -2
  122. package/dist/modules/wms/data/entities.js +150 -1
  123. package/dist/modules/wms/data/entities.js.map +2 -2
  124. package/dist/modules/wms/data/validators.js +214 -0
  125. package/dist/modules/wms/data/validators.js.map +2 -2
  126. package/dist/modules/wms/events.js +12 -1
  127. package/dist/modules/wms/events.js.map +2 -2
  128. package/dist/modules/wms/extension-points.js +3 -1
  129. package/dist/modules/wms/extension-points.js.map +2 -2
  130. package/dist/modules/wms/lib/asnReceiving.js +124 -0
  131. package/dist/modules/wms/lib/asnReceiving.js.map +7 -0
  132. package/dist/modules/wms/lib/asnVendor.js +52 -0
  133. package/dist/modules/wms/lib/asnVendor.js.map +7 -0
  134. package/dist/modules/wms/lib/flashMutationError.js +4 -0
  135. package/dist/modules/wms/lib/flashMutationError.js.map +2 -2
  136. package/dist/modules/wms/lib/inventoryDisplayUi.js +24 -0
  137. package/dist/modules/wms/lib/inventoryDisplayUi.js.map +2 -2
  138. package/dist/modules/wms/lib/inventoryPolicy.js +17 -0
  139. package/dist/modules/wms/lib/inventoryPolicy.js.map +2 -2
  140. package/dist/modules/wms/lib/loadOperationalDashboard.js +67 -1
  141. package/dist/modules/wms/lib/loadOperationalDashboard.js.map +2 -2
  142. package/dist/modules/wms/lib/putaway.js +141 -0
  143. package/dist/modules/wms/lib/putaway.js.map +7 -0
  144. package/dist/modules/wms/lib/roleFeatures.js +3 -1
  145. package/dist/modules/wms/lib/roleFeatures.js.map +2 -2
  146. package/dist/modules/wms/lib/salesOrderInventoryAutomation.js +99 -1
  147. package/dist/modules/wms/lib/salesOrderInventoryAutomation.js.map +2 -2
  148. package/dist/modules/wms/lib/scanResolve.js +147 -0
  149. package/dist/modules/wms/lib/scanResolve.js.map +7 -0
  150. package/dist/modules/wms/lib/wmsIntegrationToggles.js +1 -1
  151. package/dist/modules/wms/lib/wmsIntegrationToggles.js.map +2 -2
  152. package/dist/modules/wms/migrations/Migration20260812120000.js +43 -0
  153. package/dist/modules/wms/migrations/Migration20260812120000.js.map +7 -0
  154. package/dist/modules/wms/migrations/Migration20260812130000.js +30 -0
  155. package/dist/modules/wms/migrations/Migration20260812130000.js.map +7 -0
  156. package/dist/modules/wms/migrations/Migration20260822120000.js +17 -0
  157. package/dist/modules/wms/migrations/Migration20260822120000.js.map +7 -0
  158. package/dist/modules/wms/migrations/Migration20260822130000.js +17 -0
  159. package/dist/modules/wms/migrations/Migration20260822130000.js.map +7 -0
  160. package/dist/modules/wms/migrations/Migration20261029120000.js +13 -0
  161. package/dist/modules/wms/migrations/Migration20261029120000.js.map +7 -0
  162. package/dist/modules/wms/search.js +85 -0
  163. package/dist/modules/wms/search.js.map +2 -2
  164. package/dist/modules/wms/subscribers/asn-line-received-reevaluate-reservations.js +14 -0
  165. package/dist/modules/wms/subscribers/asn-line-received-reevaluate-reservations.js.map +7 -0
  166. package/dist/modules/wms/subscribers/invalidate-enricher-cache-asn.js +12 -0
  167. package/dist/modules/wms/subscribers/invalidate-enricher-cache-asn.js.map +7 -0
  168. package/dist/modules/wms/subscribers/procurement-goods-receipt-created.js +163 -0
  169. package/dist/modules/wms/subscribers/procurement-goods-receipt-created.js.map +7 -0
  170. package/dist/modules/wms/subscribers/putaway-completed-reevaluate-reservations.js +14 -0
  171. package/dist/modules/wms/subscribers/putaway-completed-reevaluate-reservations.js.map +7 -0
  172. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js +37 -8
  173. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js.map +2 -2
  174. package/generated/entities/asn/index.ts +8 -0
  175. package/generated/entities/putaway_task/index.ts +11 -0
  176. package/generated/entities/receiving_line/index.ts +9 -0
  177. package/generated/entities.ids.generated.ts +4 -1
  178. package/generated/entity-fields-registry.ts +34 -0
  179. package/package.json +7 -7
  180. package/src/modules/auth/i18n/de.json +2 -0
  181. package/src/modules/auth/i18n/en.json +2 -0
  182. package/src/modules/auth/i18n/es.json +2 -0
  183. package/src/modules/auth/i18n/ko.json +2 -0
  184. package/src/modules/auth/i18n/pl.json +2 -0
  185. package/src/modules/communication_channels/i18n/de.json +2 -0
  186. package/src/modules/communication_channels/i18n/en.json +2 -0
  187. package/src/modules/communication_channels/i18n/es.json +2 -0
  188. package/src/modules/communication_channels/i18n/ko.json +2 -0
  189. package/src/modules/communication_channels/i18n/pl.json +2 -0
  190. package/src/modules/communication_channels/lib/sanitize-channel-html.ts +23 -3
  191. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +44 -12
  192. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +16 -2
  193. package/src/modules/wms/acl.ts +2 -0
  194. package/src/modules/wms/api/asns/[id]/complete/route.ts +62 -0
  195. package/src/modules/wms/api/asns/[id]/receive/route.ts +74 -0
  196. package/src/modules/wms/api/asns/route.ts +200 -0
  197. package/src/modules/wms/api/inventory/helpers.ts +57 -34
  198. package/src/modules/wms/api/inventory/receive/route.ts +2 -1
  199. package/src/modules/wms/api/listEnrichers.ts +38 -0
  200. package/src/modules/wms/api/putaway-tasks/[id]/assign/route.ts +56 -0
  201. package/src/modules/wms/api/putaway-tasks/[id]/cancel/route.ts +56 -0
  202. package/src/modules/wms/api/putaway-tasks/[id]/complete/route.ts +66 -0
  203. package/src/modules/wms/api/putaway-tasks/[id]/start/route.ts +56 -0
  204. package/src/modules/wms/api/putaway-tasks/create-from-balance/route.ts +50 -0
  205. package/src/modules/wms/api/putaway-tasks/route.ts +199 -0
  206. package/src/modules/wms/api/receiving-lines/route.ts +180 -0
  207. package/src/modules/wms/api/scan/putaway/route.ts +108 -0
  208. package/src/modules/wms/api/scan/receive/route.ts +118 -0
  209. package/src/modules/wms/api/scan/resolve-location/route.ts +92 -0
  210. package/src/modules/wms/api/scan/resolve-lot/route.ts +92 -0
  211. package/src/modules/wms/backend/wms/asns/[id]/page.meta.ts +14 -0
  212. package/src/modules/wms/backend/wms/asns/[id]/page.tsx +5 -0
  213. package/src/modules/wms/backend/wms/asns/page.meta.ts +14 -0
  214. package/src/modules/wms/backend/wms/asns/page.tsx +5 -0
  215. package/src/modules/wms/backend/wms/putaway/page.meta.ts +14 -0
  216. package/src/modules/wms/backend/wms/putaway/page.tsx +5 -0
  217. package/src/modules/wms/backend/wms/receiving/page.meta.ts +14 -0
  218. package/src/modules/wms/backend/wms/receiving/page.tsx +5 -0
  219. package/src/modules/wms/ce.ts +27 -0
  220. package/src/modules/wms/commands/asn.ts +2122 -0
  221. package/src/modules/wms/commands/index.ts +2 -0
  222. package/src/modules/wms/commands/inventory-actions.ts +413 -243
  223. package/src/modules/wms/commands/putaway.ts +1169 -0
  224. package/src/modules/wms/commands/shared.ts +51 -0
  225. package/src/modules/wms/components/backend/AssignPutawayTaskDialog.tsx +328 -0
  226. package/src/modules/wms/components/backend/CompletePutawayTaskDialog.tsx +366 -0
  227. package/src/modules/wms/components/backend/CreateAsnDialog.tsx +427 -0
  228. package/src/modules/wms/components/backend/InventoryOperationsSection.tsx +20 -2
  229. package/src/modules/wms/components/backend/ReceiveAsnLineDialog.tsx +535 -0
  230. package/src/modules/wms/components/backend/ReceiveInventoryDialog.tsx +2 -2
  231. package/src/modules/wms/components/backend/WmsAsnDetailPage.tsx +647 -0
  232. package/src/modules/wms/components/backend/WmsAsnsListPage.tsx +407 -0
  233. package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +4 -5
  234. package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +4 -5
  235. package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +12 -8
  236. package/src/modules/wms/components/backend/WmsPutawayQueuePage.tsx +677 -0
  237. package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +4 -5
  238. package/src/modules/wms/components/backend/asnCompleteOptimisticLock.ts +52 -0
  239. package/src/modules/wms/components/backend/asnReceivingLinesLoader.ts +83 -0
  240. package/src/modules/wms/components/backend/inboundStatusUi.ts +154 -0
  241. package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +20 -0
  242. package/src/modules/wms/components/backend/putawayQueueOptimisticLock.ts +20 -0
  243. package/src/modules/wms/components/backend/useWmsInventoryMutationAccess.ts +4 -0
  244. package/src/modules/wms/data/enrichers.ts +175 -13
  245. package/src/modules/wms/data/entities.ts +155 -0
  246. package/src/modules/wms/data/validators.ts +256 -0
  247. package/src/modules/wms/events.ts +11 -0
  248. package/src/modules/wms/extension-points.ts +2 -0
  249. package/src/modules/wms/i18n/de.json +205 -3
  250. package/src/modules/wms/i18n/en.json +205 -3
  251. package/src/modules/wms/i18n/es.json +205 -3
  252. package/src/modules/wms/i18n/ko.json +205 -3
  253. package/src/modules/wms/i18n/pl.json +205 -3
  254. package/src/modules/wms/lib/asnReceiving.ts +226 -0
  255. package/src/modules/wms/lib/asnVendor.ts +80 -0
  256. package/src/modules/wms/lib/flashMutationError.ts +4 -0
  257. package/src/modules/wms/lib/inventoryDisplayUi.ts +46 -0
  258. package/src/modules/wms/lib/inventoryPolicy.ts +31 -1
  259. package/src/modules/wms/lib/loadOperationalDashboard.ts +89 -1
  260. package/src/modules/wms/lib/putaway.ts +210 -0
  261. package/src/modules/wms/lib/roleFeatures.ts +2 -0
  262. package/src/modules/wms/lib/salesOrderInventoryAutomation.ts +150 -1
  263. package/src/modules/wms/lib/scanResolve.ts +185 -0
  264. package/src/modules/wms/lib/wmsIntegrationToggles.ts +2 -1
  265. package/src/modules/wms/migrations/.snapshot-open-mercato.json +1000 -0
  266. package/src/modules/wms/migrations/Migration20260812120000.ts +43 -0
  267. package/src/modules/wms/migrations/Migration20260812130000.ts +28 -0
  268. package/src/modules/wms/migrations/Migration20260822120000.ts +17 -0
  269. package/src/modules/wms/migrations/Migration20260822130000.ts +15 -0
  270. package/src/modules/wms/migrations/Migration20261029120000.ts +11 -0
  271. package/src/modules/wms/search.ts +95 -0
  272. package/src/modules/wms/subscribers/asn-line-received-reevaluate-reservations.ts +21 -0
  273. package/src/modules/wms/subscribers/invalidate-enricher-cache-asn.ts +12 -0
  274. package/src/modules/wms/subscribers/procurement-goods-receipt-created.ts +238 -0
  275. package/src/modules/wms/subscribers/putaway-completed-reevaluate-reservations.ts +21 -0
  276. package/src/modules/wms/widgets/injection/sales-order-stock-context/widget.client.tsx +63 -13
@@ -0,0 +1,945 @@
1
+ import { registerCommand } from "@open-mercato/shared/lib/commands";
2
+ import { emitCrudSideEffects } from "@open-mercato/shared/lib/commands/helpers";
3
+ import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
4
+ import { LockMode } from "@mikro-orm/core";
5
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
6
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
7
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
+ import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
9
+ import {
10
+ InventoryBalance,
11
+ PutawayTask,
12
+ Warehouse,
13
+ WarehouseLocation
14
+ } from "../data/entities.js";
15
+ import {
16
+ putawayTaskAssignSchema,
17
+ putawayTaskCancelSchema,
18
+ putawayTaskCompleteSchema,
19
+ putawayTaskCreateFromBalanceSchema,
20
+ putawayTaskCreateSchema,
21
+ putawayTaskStartSchema,
22
+ putawayTaskUpdateSchema
23
+ } from "../data/validators.js";
24
+ import { emitWmsEvent } from "../events.js";
25
+ import {
26
+ assertPutawayAssignable,
27
+ assertPutawayCancellable,
28
+ computeUncommittedPutawaySourceQuantity,
29
+ hasUncommittedPutawaySourceQuantity,
30
+ assertPutawayCompleteAuthorized,
31
+ assertPutawayCompletable,
32
+ assertPutawayConfirmedQuantity,
33
+ assertPutawayDeletable,
34
+ assertPutawayLifecycleFieldsForbidden,
35
+ assertPutawayStartable,
36
+ assertPutawayTargetLocationType,
37
+ buildPutawayCompleteReferenceId,
38
+ putawayResidualQuantity
39
+ } from "../lib/putaway.js";
40
+ import {
41
+ applyInventoryMoveInTransaction,
42
+ emitInventoryMoveSideEffects,
43
+ findPutawayCompleteMovementByReference
44
+ } from "./inventory-actions.js";
45
+ import {
46
+ ensureOrganizationScope,
47
+ ensureTenantScope,
48
+ putawayTaskCrudEvents,
49
+ putawayTaskCrudIndexer,
50
+ requireId,
51
+ toNumericString,
52
+ WMS_INVENTORY_BALANCE_RESOURCE,
53
+ WMS_PUTAWAY_TASK_RESOURCE
54
+ } from "./shared.js";
55
+ function resolveScope(ctx, fallback) {
56
+ const tenantId = fallback?.tenantId ?? ctx.auth?.tenantId ?? null;
57
+ const organizationId = fallback?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
58
+ if (!tenantId || !organizationId) {
59
+ throw new CrudHttpError(400, { error: "Tenant and organization scope are required." });
60
+ }
61
+ return { tenantId, organizationId };
62
+ }
63
+ function resolveEm(ctx) {
64
+ return ctx.container.resolve("em").fork();
65
+ }
66
+ async function resolvePutawayCompleteAuthorization(ctx, scope, assignedTo) {
67
+ const actorUserId = typeof ctx.auth?.sub === "string" ? ctx.auth.sub : null;
68
+ let canManagePutaway = false;
69
+ let canAdjustInventory = false;
70
+ if (actorUserId) {
71
+ try {
72
+ const rbacService = ctx.container.resolve("rbacService");
73
+ if (typeof rbacService.userHasAllFeatures === "function") {
74
+ ;
75
+ [canManagePutaway, canAdjustInventory] = await Promise.all([
76
+ rbacService.userHasAllFeatures(actorUserId, ["wms.manage_putaway"], scope),
77
+ rbacService.userHasAllFeatures(actorUserId, ["wms.adjust_inventory"], scope)
78
+ ]);
79
+ }
80
+ } catch {
81
+ }
82
+ }
83
+ assertPutawayCompleteAuthorized({
84
+ canManagePutaway,
85
+ canAdjustInventory,
86
+ actorUserId,
87
+ assignedTo
88
+ });
89
+ }
90
+ function toJsonValue(value) {
91
+ if (value === void 0) return void 0;
92
+ return value ?? null;
93
+ }
94
+ function iso(value) {
95
+ if (value instanceof Date) return value.toISOString();
96
+ return new Date(value).toISOString();
97
+ }
98
+ function snapshotPutawayTask(record) {
99
+ const warehouseId = typeof record.warehouse === "string" ? record.warehouse : record.warehouse.id;
100
+ return {
101
+ id: record.id,
102
+ organizationId: record.organizationId,
103
+ tenantId: record.tenantId,
104
+ warehouseId,
105
+ sourceLocationId: record.sourceLocationId,
106
+ targetLocationId: record.targetLocationId ?? null,
107
+ catalogVariantId: record.catalogVariantId,
108
+ lotId: record.lotId ?? null,
109
+ quantity: record.quantity,
110
+ status: record.status,
111
+ assignedTo: record.assignedTo ?? null,
112
+ priority: Number(record.priority),
113
+ metadata: record.metadata ?? null,
114
+ createdAt: iso(record.createdAt),
115
+ updatedAt: iso(record.updatedAt)
116
+ };
117
+ }
118
+ async function loadPutawayTask(em, ctx, id) {
119
+ const task = await findOneWithDecryption(
120
+ em,
121
+ PutawayTask,
122
+ { id, deletedAt: null },
123
+ void 0,
124
+ resolveScope(ctx)
125
+ );
126
+ if (!task) throw new CrudHttpError(404, { error: "Putaway task not found." });
127
+ ensureTenantScope(ctx, task.tenantId);
128
+ ensureOrganizationScope(ctx, task.organizationId);
129
+ return task;
130
+ }
131
+ async function withLockedPutawayTask(em, ctx, id, scope, fn) {
132
+ return em.transactional(async (trx) => {
133
+ const task = await findOneWithDecryption(
134
+ trx,
135
+ PutawayTask,
136
+ { id, deletedAt: null },
137
+ { lockMode: LockMode.PESSIMISTIC_WRITE },
138
+ scope
139
+ );
140
+ if (!task) throw new CrudHttpError(404, { error: "Putaway task not found." });
141
+ ensureTenantScope(ctx, task.tenantId);
142
+ ensureOrganizationScope(ctx, task.organizationId);
143
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
144
+ resourceKind: WMS_PUTAWAY_TASK_RESOURCE,
145
+ resourceId: task.id,
146
+ current: task.updatedAt,
147
+ request: ctx.request ?? null
148
+ });
149
+ return fn(task, trx);
150
+ });
151
+ }
152
+ async function requireWarehouse(em, ctx, warehouseId, scope) {
153
+ const warehouse = await findOneWithDecryption(
154
+ em,
155
+ Warehouse,
156
+ { id: warehouseId, deletedAt: null },
157
+ void 0,
158
+ scope
159
+ );
160
+ if (!warehouse) throw new CrudHttpError(404, { error: "Warehouse not found." });
161
+ ensureTenantScope(ctx, warehouse.tenantId);
162
+ ensureOrganizationScope(ctx, warehouse.organizationId);
163
+ return warehouse;
164
+ }
165
+ async function requireActiveLocation(em, ctx, locationId, warehouseId, scope) {
166
+ const location = await findOneWithDecryption(
167
+ em,
168
+ WarehouseLocation,
169
+ { id: locationId, deletedAt: null },
170
+ void 0,
171
+ scope
172
+ );
173
+ if (!location) throw new CrudHttpError(404, { error: "Warehouse location not found." });
174
+ ensureTenantScope(ctx, location.tenantId);
175
+ ensureOrganizationScope(ctx, location.organizationId);
176
+ const locationWarehouseId = typeof location.warehouse === "string" ? location.warehouse : location.warehouse.id;
177
+ if (locationWarehouseId !== warehouseId) {
178
+ throw new CrudHttpError(422, { error: "invalid_location" });
179
+ }
180
+ if (!location.isActive) {
181
+ throw new CrudHttpError(422, { error: "inactive_location" });
182
+ }
183
+ return location;
184
+ }
185
+ function toNumber(value) {
186
+ if (typeof value === "number" && Number.isFinite(value)) return value;
187
+ if (typeof value === "string" && value.trim().length > 0) {
188
+ const parsed = Number(value);
189
+ if (Number.isFinite(parsed)) return parsed;
190
+ }
191
+ return 0;
192
+ }
193
+ async function requireAvailableBalanceQuantity(em, ctx, input, scope, options) {
194
+ const serialNumber = input.serialNumber?.trim() ? input.serialNumber.trim() : null;
195
+ const balance = await findOneWithDecryption(
196
+ em,
197
+ InventoryBalance,
198
+ {
199
+ warehouse: input.warehouseId,
200
+ location: input.sourceLocationId,
201
+ catalogVariantId: input.catalogVariantId,
202
+ lot: input.lotId ?? null,
203
+ serialNumber,
204
+ deletedAt: null
205
+ },
206
+ options?.lock ? { lockMode: LockMode.PESSIMISTIC_WRITE } : void 0,
207
+ scope
208
+ );
209
+ if (!balance) {
210
+ throw new CrudHttpError(409, { error: "insufficient_stock" });
211
+ }
212
+ ensureTenantScope(ctx, balance.tenantId);
213
+ ensureOrganizationScope(ctx, balance.organizationId);
214
+ const openTasks = await findWithDecryption(
215
+ em,
216
+ PutawayTask,
217
+ {
218
+ warehouse: input.warehouseId,
219
+ sourceLocationId: input.sourceLocationId,
220
+ catalogVariantId: input.catalogVariantId,
221
+ lotId: input.lotId ?? null,
222
+ serialNumber,
223
+ status: { $in: ["open", "in_progress"] },
224
+ deletedAt: null
225
+ },
226
+ void 0,
227
+ scope
228
+ );
229
+ const remaining = computeUncommittedPutawaySourceQuantity({
230
+ quantityOnHand: toNumber(balance.quantityOnHand),
231
+ quantityReserved: toNumber(balance.quantityReserved),
232
+ quantityAllocated: toNumber(balance.quantityAllocated),
233
+ openPutawayQuantities: openTasks.filter((task) => !(options?.excludeTaskId && task.id === options.excludeTaskId)).map((task) => toNumber(task.quantity))
234
+ });
235
+ if (!hasUncommittedPutawaySourceQuantity(remaining, input.quantity)) {
236
+ throw new CrudHttpError(409, { error: "insufficient_stock" });
237
+ }
238
+ return balance;
239
+ }
240
+ async function emitPutawaySideEffects(ctx, tasks) {
241
+ const dataEngine = ctx.container.resolve("dataEngine");
242
+ for (const entry of tasks) {
243
+ await emitCrudSideEffects({
244
+ dataEngine,
245
+ action: entry.action,
246
+ entity: entry.entity,
247
+ identifiers: {
248
+ id: entry.entity.id,
249
+ organizationId: entry.entity.organizationId,
250
+ tenantId: entry.entity.tenantId
251
+ },
252
+ events: putawayTaskCrudEvents,
253
+ indexer: putawayTaskCrudIndexer
254
+ });
255
+ }
256
+ }
257
+ async function buildCrudLog(ctx, input, resultId, actionKey, fallbackLabel) {
258
+ const { translate } = await resolveTranslations();
259
+ return {
260
+ actionLabel: translate(actionKey, fallbackLabel),
261
+ resourceKind: WMS_PUTAWAY_TASK_RESOURCE,
262
+ resourceId: resultId ?? input?.id ?? null,
263
+ tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,
264
+ organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
265
+ };
266
+ }
267
+ function readPutawaySourceBucket(task) {
268
+ const warehouseId = typeof task.warehouse === "string" ? task.warehouse : task.warehouse.id;
269
+ const serialNumber = task.serialNumber?.trim() ?? "";
270
+ return {
271
+ warehouseId,
272
+ sourceLocationId: task.sourceLocationId,
273
+ catalogVariantId: task.catalogVariantId,
274
+ lotId: task.lotId ?? null,
275
+ serialNumber: serialNumber.length > 0 ? serialNumber : null
276
+ };
277
+ }
278
+ function samePutawaySourceBucket(left, right) {
279
+ return left.warehouseId === right.warehouseId && left.sourceLocationId === right.sourceLocationId && left.catalogVariantId === right.catalogVariantId && (left.lotId ?? null) === (right.lotId ?? null) && (left.serialNumber ?? null) === (right.serialNumber ?? null);
280
+ }
281
+ async function assertResultingPutawayCapacity(em, ctx, scope, input) {
282
+ const bucketChanged = !samePutawaySourceBucket(input.previous, input.next);
283
+ const quantityIncreased = input.nextQuantity > input.previousQuantity + 1e-6;
284
+ if (!bucketChanged && !quantityIncreased) return;
285
+ await requireAvailableBalanceQuantity(
286
+ em,
287
+ ctx,
288
+ {
289
+ warehouseId: input.next.warehouseId,
290
+ sourceLocationId: input.next.sourceLocationId,
291
+ catalogVariantId: input.next.catalogVariantId,
292
+ lotId: input.next.lotId,
293
+ serialNumber: input.next.serialNumber,
294
+ quantity: input.nextQuantity
295
+ },
296
+ scope,
297
+ { lock: true, excludeTaskId: input.excludeTaskId }
298
+ );
299
+ }
300
+ async function createOpenPutawayTask(em, ctx, input) {
301
+ const scope = resolveScope(ctx, input);
302
+ await requireWarehouse(em, ctx, input.warehouseId, scope);
303
+ await requireActiveLocation(em, ctx, input.sourceLocationId, input.warehouseId, scope);
304
+ if (input.targetLocationId) {
305
+ await requireActiveLocation(em, ctx, input.targetLocationId, input.warehouseId, scope);
306
+ }
307
+ const task = em.create(PutawayTask, {
308
+ organizationId: scope.organizationId,
309
+ tenantId: scope.tenantId,
310
+ warehouse: em.getReference(Warehouse, input.warehouseId),
311
+ sourceLocationId: input.sourceLocationId,
312
+ targetLocationId: input.targetLocationId ?? null,
313
+ catalogVariantId: input.catalogVariantId,
314
+ lotId: input.lotId ?? null,
315
+ serialNumber: input.serialNumber?.trim() ? input.serialNumber.trim() : null,
316
+ quantity: toNumericString(input.quantity),
317
+ status: "open",
318
+ assignedTo: input.assignedTo ?? null,
319
+ priority: input.priority ?? 5,
320
+ putawayKey: input.putawayKey ?? null,
321
+ metadata: toJsonValue(input.metadata) ?? null
322
+ });
323
+ await em.persist(task).flush();
324
+ return task;
325
+ }
326
+ async function ensureResidualPutawayTask(em, ctx, input) {
327
+ const residualKey = `wms:putaway-residual:${input.parent.id}`;
328
+ const existing = await findOneWithDecryption(
329
+ em,
330
+ PutawayTask,
331
+ {
332
+ organizationId: input.scope.organizationId,
333
+ tenantId: input.scope.tenantId,
334
+ putawayKey: residualKey,
335
+ deletedAt: null,
336
+ status: { $in: ["open", "in_progress", "done"] }
337
+ },
338
+ void 0,
339
+ input.scope
340
+ );
341
+ if (existing) return;
342
+ try {
343
+ await createOpenPutawayTask(em, ctx, {
344
+ organizationId: input.scope.organizationId,
345
+ tenantId: input.scope.tenantId,
346
+ warehouseId: input.warehouseId,
347
+ sourceLocationId: input.parent.sourceLocationId,
348
+ targetLocationId: null,
349
+ catalogVariantId: input.parent.catalogVariantId,
350
+ lotId: input.parent.lotId,
351
+ serialNumber: input.parent.serialNumber ?? null,
352
+ quantity: input.residual,
353
+ assignedTo: input.parent.assignedTo,
354
+ priority: input.parent.priority,
355
+ putawayKey: residualKey,
356
+ metadata: {
357
+ source: "putaway_residual",
358
+ parentPutawayTaskId: input.parent.id
359
+ }
360
+ });
361
+ } catch (error) {
362
+ const code = error && typeof error === "object" ? error.code : void 0;
363
+ const name = error && typeof error === "object" ? error.name : void 0;
364
+ if (code !== "23505" && name !== "UniqueConstraintViolationException") throw error;
365
+ }
366
+ }
367
+ const createPutawayTaskCommand = {
368
+ id: "wms.putaway-tasks.create",
369
+ async execute(rawInput, ctx) {
370
+ const input = putawayTaskCreateSchema.parse(rawInput ?? {});
371
+ ensureTenantScope(ctx, input.tenantId);
372
+ ensureOrganizationScope(ctx, input.organizationId);
373
+ const em = resolveEm(ctx);
374
+ const scope = resolveScope(ctx, input);
375
+ const task = await em.transactional(async (trx) => {
376
+ await requireAvailableBalanceQuantity(
377
+ trx,
378
+ ctx,
379
+ {
380
+ warehouseId: input.warehouseId,
381
+ sourceLocationId: input.sourceLocationId,
382
+ catalogVariantId: input.catalogVariantId,
383
+ lotId: input.lotId,
384
+ quantity: input.quantity
385
+ },
386
+ scope,
387
+ { lock: true }
388
+ );
389
+ return createOpenPutawayTask(trx, ctx, input);
390
+ });
391
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "created" }]);
392
+ void emitWmsEvent("wms.putaway.created", {
393
+ id: task.id,
394
+ warehouseId: input.warehouseId,
395
+ sourceLocationId: task.sourceLocationId,
396
+ targetLocationId: task.targetLocationId ?? null,
397
+ catalogVariantId: task.catalogVariantId,
398
+ quantity: task.quantity,
399
+ tenantId: task.tenantId,
400
+ organizationId: task.organizationId
401
+ }).catch(() => void 0);
402
+ return { taskId: task.id };
403
+ },
404
+ captureAfter: async (_input, result, ctx) => {
405
+ if (!result?.taskId) return null;
406
+ const task = await loadPutawayTask(resolveEm(ctx), ctx, result.taskId);
407
+ return snapshotPutawayTask(task);
408
+ },
409
+ undo: async ({ logEntry, ctx }) => {
410
+ const payload = extractUndoPayload(logEntry);
411
+ const after = payload?.after;
412
+ if (!after?.id) return;
413
+ const em = resolveEm(ctx);
414
+ const scope = { tenantId: after.tenantId, organizationId: after.organizationId };
415
+ const task = await withLockedPutawayTask(em, ctx, after.id, scope, async (locked, trx) => {
416
+ if (locked.status !== "open") {
417
+ throw new CrudHttpError(409, { error: "invalid_putaway_state" });
418
+ }
419
+ locked.deletedAt = /* @__PURE__ */ new Date();
420
+ await trx.flush();
421
+ return locked;
422
+ });
423
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "deleted" }]);
424
+ },
425
+ buildLog: async ({ snapshots, input, result, ctx }) => {
426
+ const after = snapshots?.after;
427
+ return {
428
+ ...await buildCrudLog(ctx, input, result?.taskId ?? null, "wms.audit.putaway.create", "Create putaway task"),
429
+ snapshotAfter: after,
430
+ payload: { undo: { after } }
431
+ };
432
+ }
433
+ };
434
+ const updatePutawayTaskCommand = {
435
+ id: "wms.putaway-tasks.update",
436
+ async prepare(rawInput, ctx) {
437
+ const id = requireId(rawInput?.id, "Putaway task");
438
+ const task = await loadPutawayTask(resolveEm(ctx), ctx, id);
439
+ return { before: snapshotPutawayTask(task) };
440
+ },
441
+ async execute(rawInput, ctx) {
442
+ assertPutawayLifecycleFieldsForbidden(rawInput);
443
+ const input = putawayTaskUpdateSchema.parse(rawInput ?? {});
444
+ const em = resolveEm(ctx);
445
+ const scope = resolveScope(ctx);
446
+ const task = await withLockedPutawayTask(em, ctx, input.id, scope, async (locked, trx) => {
447
+ if (locked.status === "done" || locked.status === "cancelled") {
448
+ throw new CrudHttpError(409, { error: "invalid_putaway_state" });
449
+ }
450
+ const previousBucket = readPutawaySourceBucket(locked);
451
+ const nextBucket = {
452
+ warehouseId: input.warehouseId ?? previousBucket.warehouseId,
453
+ sourceLocationId: input.sourceLocationId ?? previousBucket.sourceLocationId,
454
+ catalogVariantId: input.catalogVariantId ?? previousBucket.catalogVariantId,
455
+ lotId: input.lotId !== void 0 ? input.lotId : previousBucket.lotId,
456
+ serialNumber: previousBucket.serialNumber
457
+ };
458
+ const previousQty = toNumber(locked.quantity);
459
+ const nextQty = input.quantity !== void 0 ? Number(input.quantity) : previousQty;
460
+ const taskScope = resolveScope(ctx, {
461
+ tenantId: locked.tenantId,
462
+ organizationId: locked.organizationId
463
+ });
464
+ if (input.warehouseId) {
465
+ await requireWarehouse(trx, ctx, input.warehouseId, taskScope);
466
+ }
467
+ if (input.sourceLocationId) {
468
+ await requireActiveLocation(trx, ctx, input.sourceLocationId, nextBucket.warehouseId, taskScope);
469
+ }
470
+ if (input.targetLocationId) {
471
+ await requireActiveLocation(trx, ctx, input.targetLocationId, nextBucket.warehouseId, taskScope);
472
+ }
473
+ await assertResultingPutawayCapacity(trx, ctx, taskScope, {
474
+ previous: previousBucket,
475
+ next: nextBucket,
476
+ previousQuantity: previousQty,
477
+ nextQuantity: nextQty,
478
+ excludeTaskId: locked.id
479
+ });
480
+ if (input.warehouseId) {
481
+ locked.warehouse = trx.getReference(Warehouse, input.warehouseId);
482
+ }
483
+ if (input.sourceLocationId) locked.sourceLocationId = input.sourceLocationId;
484
+ if (input.targetLocationId !== void 0) locked.targetLocationId = input.targetLocationId;
485
+ if (input.catalogVariantId !== void 0) locked.catalogVariantId = input.catalogVariantId;
486
+ if (input.lotId !== void 0) locked.lotId = input.lotId;
487
+ if (input.quantity !== void 0) locked.quantity = toNumericString(input.quantity);
488
+ if (input.priority !== void 0) locked.priority = input.priority;
489
+ if (input.metadata !== void 0) locked.metadata = toJsonValue(input.metadata) ?? null;
490
+ await trx.flush();
491
+ return locked;
492
+ });
493
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "updated" }]);
494
+ return { taskId: task.id };
495
+ },
496
+ captureAfter: async (_input, result, ctx) => {
497
+ if (!result?.taskId) return null;
498
+ const task = await loadPutawayTask(resolveEm(ctx), ctx, result.taskId);
499
+ return snapshotPutawayTask(task);
500
+ },
501
+ undo: async ({ logEntry, ctx }) => {
502
+ const payload = extractUndoPayload(logEntry);
503
+ const before = payload?.before;
504
+ if (!before?.id) return;
505
+ const em = resolveEm(ctx);
506
+ const scope = { tenantId: before.tenantId, organizationId: before.organizationId };
507
+ const task = await withLockedPutawayTask(em, ctx, before.id, scope, async (locked, trx) => {
508
+ if (locked.status === "done" || locked.status === "cancelled") {
509
+ throw new CrudHttpError(409, { error: "invalid_putaway_state" });
510
+ }
511
+ if (before.status === "done" || before.status === "cancelled") {
512
+ throw new CrudHttpError(409, { error: "invalid_putaway_state" });
513
+ }
514
+ const previousBucket = readPutawaySourceBucket(locked);
515
+ const restoredBucket = {
516
+ warehouseId: before.warehouseId,
517
+ sourceLocationId: before.sourceLocationId,
518
+ catalogVariantId: before.catalogVariantId,
519
+ lotId: before.lotId ?? null,
520
+ serialNumber: previousBucket.serialNumber
521
+ };
522
+ await assertResultingPutawayCapacity(trx, ctx, scope, {
523
+ previous: previousBucket,
524
+ next: restoredBucket,
525
+ previousQuantity: toNumber(locked.quantity),
526
+ nextQuantity: toNumber(before.quantity),
527
+ excludeTaskId: locked.id
528
+ });
529
+ locked.warehouse = trx.getReference(Warehouse, before.warehouseId);
530
+ locked.sourceLocationId = before.sourceLocationId;
531
+ locked.targetLocationId = before.targetLocationId;
532
+ locked.catalogVariantId = before.catalogVariantId;
533
+ locked.lotId = before.lotId;
534
+ locked.quantity = before.quantity;
535
+ locked.status = before.status;
536
+ locked.assignedTo = before.assignedTo;
537
+ locked.priority = before.priority;
538
+ locked.metadata = before.metadata;
539
+ locked.deletedAt = null;
540
+ await trx.flush();
541
+ return locked;
542
+ });
543
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "updated" }]);
544
+ },
545
+ buildLog: async ({ snapshots, input, result, ctx }) => {
546
+ const before = snapshots?.before;
547
+ const after = snapshots?.after;
548
+ return {
549
+ ...await buildCrudLog(ctx, input, result?.taskId ?? null, "wms.audit.putaway.update", "Update putaway task"),
550
+ snapshotBefore: before,
551
+ snapshotAfter: after,
552
+ payload: { undo: { before, after } }
553
+ };
554
+ }
555
+ };
556
+ const deletePutawayTaskCommand = {
557
+ id: "wms.putaway-tasks.delete",
558
+ async prepare(rawInput, ctx) {
559
+ const id = requireId(rawInput?.id, "Putaway task");
560
+ const task = await loadPutawayTask(resolveEm(ctx), ctx, id);
561
+ return { before: snapshotPutawayTask(task) };
562
+ },
563
+ async execute(rawInput, ctx) {
564
+ const id = requireId(rawInput?.id, "Putaway task");
565
+ const em = resolveEm(ctx);
566
+ const scope = resolveScope(ctx);
567
+ const task = await withLockedPutawayTask(em, ctx, id, scope, async (locked, trx) => {
568
+ assertPutawayDeletable(locked.status);
569
+ locked.deletedAt = /* @__PURE__ */ new Date();
570
+ await trx.flush();
571
+ return locked;
572
+ });
573
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "deleted" }]);
574
+ return { taskId: task.id };
575
+ },
576
+ undo: async ({ logEntry, ctx }) => {
577
+ const payload = extractUndoPayload(logEntry);
578
+ const before = payload?.before;
579
+ if (!before?.id) return;
580
+ const em = resolveEm(ctx);
581
+ const scope = { tenantId: before.tenantId, organizationId: before.organizationId };
582
+ const task = await em.transactional(async (trx) => {
583
+ const locked = await findOneWithDecryption(
584
+ trx,
585
+ PutawayTask,
586
+ { id: before.id },
587
+ { lockMode: LockMode.PESSIMISTIC_WRITE },
588
+ scope
589
+ );
590
+ if (!locked) return null;
591
+ ensureTenantScope(ctx, locked.tenantId);
592
+ ensureOrganizationScope(ctx, locked.organizationId);
593
+ if (locked.status !== "cancelled") {
594
+ throw new CrudHttpError(409, { error: "invalid_putaway_state" });
595
+ }
596
+ if (!locked.deletedAt) return locked;
597
+ locked.deletedAt = null;
598
+ await trx.flush();
599
+ return locked;
600
+ });
601
+ if (!task) return;
602
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "updated" }]);
603
+ },
604
+ buildLog: async ({ snapshots, input, result, ctx }) => {
605
+ const before = snapshots?.before;
606
+ return {
607
+ ...await buildCrudLog(ctx, input, result?.taskId ?? null, "wms.audit.putaway.delete", "Delete putaway task"),
608
+ snapshotBefore: before,
609
+ payload: { undo: { before } }
610
+ };
611
+ }
612
+ };
613
+ const createPutawayTaskFromBalanceCommand = {
614
+ id: "wms.putaway-tasks.create-from-balance",
615
+ async execute(rawInput, ctx) {
616
+ const input = putawayTaskCreateFromBalanceSchema.parse(rawInput ?? {});
617
+ ensureTenantScope(ctx, input.tenantId);
618
+ ensureOrganizationScope(ctx, input.organizationId);
619
+ const em = resolveEm(ctx);
620
+ const scope = resolveScope(ctx, input);
621
+ const task = await em.transactional(async (trx) => {
622
+ await requireAvailableBalanceQuantity(
623
+ trx,
624
+ ctx,
625
+ {
626
+ warehouseId: input.warehouseId,
627
+ sourceLocationId: input.sourceLocationId,
628
+ catalogVariantId: input.catalogVariantId,
629
+ lotId: input.lotId,
630
+ quantity: input.quantity
631
+ },
632
+ scope,
633
+ { lock: true }
634
+ );
635
+ return createOpenPutawayTask(trx, ctx, {
636
+ organizationId: input.organizationId,
637
+ tenantId: input.tenantId,
638
+ warehouseId: input.warehouseId,
639
+ sourceLocationId: input.sourceLocationId,
640
+ targetLocationId: input.targetLocationId,
641
+ catalogVariantId: input.catalogVariantId,
642
+ lotId: input.lotId,
643
+ quantity: input.quantity,
644
+ priority: input.priority,
645
+ assignedTo: input.assignedTo,
646
+ metadata: {
647
+ ...input.metadata ?? {},
648
+ source: "create_from_balance"
649
+ }
650
+ });
651
+ });
652
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "created" }]);
653
+ void emitWmsEvent("wms.putaway.created", {
654
+ id: task.id,
655
+ warehouseId: input.warehouseId,
656
+ sourceLocationId: task.sourceLocationId,
657
+ targetLocationId: task.targetLocationId ?? null,
658
+ catalogVariantId: task.catalogVariantId,
659
+ quantity: task.quantity,
660
+ tenantId: task.tenantId,
661
+ organizationId: task.organizationId
662
+ }).catch(() => void 0);
663
+ return { taskId: task.id };
664
+ },
665
+ buildLog: async ({ input, result, ctx }) => buildCrudLog(
666
+ ctx,
667
+ input,
668
+ result?.taskId ?? null,
669
+ "wms.audit.putaway.createFromBalance",
670
+ "Create putaway task from balance"
671
+ )
672
+ };
673
+ const assignPutawayTaskCommand = {
674
+ id: "wms.putaway-tasks.assign",
675
+ isUndoable: false,
676
+ async execute(rawInput, ctx) {
677
+ const input = putawayTaskAssignSchema.parse(rawInput ?? {});
678
+ ensureTenantScope(ctx, input.tenantId);
679
+ ensureOrganizationScope(ctx, input.organizationId);
680
+ const em = resolveEm(ctx);
681
+ const scope = resolveScope(ctx, input);
682
+ const task = await withLockedPutawayTask(em, ctx, input.id, scope, async (locked, trx) => {
683
+ assertPutawayAssignable(locked.status);
684
+ locked.assignedTo = input.assignedTo;
685
+ await trx.flush();
686
+ return locked;
687
+ });
688
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "updated" }]);
689
+ void emitWmsEvent("wms.putaway.assigned", {
690
+ id: task.id,
691
+ assignedTo: task.assignedTo,
692
+ tenantId: task.tenantId,
693
+ organizationId: task.organizationId
694
+ }).catch(() => void 0);
695
+ return { taskId: task.id };
696
+ },
697
+ buildLog: async ({ input, result, ctx }) => buildCrudLog(ctx, input, result?.taskId ?? null, "wms.audit.putaway.assign", "Assign putaway task")
698
+ };
699
+ const startPutawayTaskCommand = {
700
+ id: "wms.putaway-tasks.start",
701
+ isUndoable: false,
702
+ async execute(rawInput, ctx) {
703
+ const input = putawayTaskStartSchema.parse(rawInput ?? {});
704
+ ensureTenantScope(ctx, input.tenantId);
705
+ ensureOrganizationScope(ctx, input.organizationId);
706
+ const em = resolveEm(ctx);
707
+ const scope = resolveScope(ctx, input);
708
+ const task = await withLockedPutawayTask(em, ctx, input.id, scope, async (locked, trx) => {
709
+ assertPutawayStartable(locked.status);
710
+ locked.status = "in_progress";
711
+ await trx.flush();
712
+ return locked;
713
+ });
714
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "updated" }]);
715
+ void emitWmsEvent("wms.putaway.started", {
716
+ id: task.id,
717
+ tenantId: task.tenantId,
718
+ organizationId: task.organizationId
719
+ }).catch(() => void 0);
720
+ return { taskId: task.id };
721
+ },
722
+ buildLog: async ({ input, result, ctx }) => buildCrudLog(ctx, input, result?.taskId ?? null, "wms.audit.putaway.start", "Start putaway task")
723
+ };
724
+ const completePutawayTaskCommand = {
725
+ id: "wms.putaway-tasks.complete",
726
+ // Stock-affecting — align with Phase 1 inventory undo policy.
727
+ isUndoable: false,
728
+ async execute(rawInput, ctx) {
729
+ const input = putawayTaskCompleteSchema.parse(rawInput ?? {});
730
+ ensureTenantScope(ctx, input.tenantId);
731
+ ensureOrganizationScope(ctx, input.organizationId);
732
+ const rootEm = resolveEm(ctx);
733
+ const scope = resolveScope(ctx, input);
734
+ const referenceId = buildPutawayCompleteReferenceId(input.id);
735
+ const outcome = await rootEm.transactional(async (trx) => {
736
+ const task = await findOneWithDecryption(
737
+ trx,
738
+ PutawayTask,
739
+ { id: input.id, deletedAt: null },
740
+ { lockMode: LockMode.PESSIMISTIC_WRITE },
741
+ scope
742
+ );
743
+ if (!task) throw new CrudHttpError(404, { error: "Putaway task not found." });
744
+ ensureTenantScope(ctx, task.tenantId);
745
+ ensureOrganizationScope(ctx, task.organizationId);
746
+ await resolvePutawayCompleteAuthorization(ctx, scope, task.assignedTo);
747
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
748
+ resourceKind: WMS_PUTAWAY_TASK_RESOURCE,
749
+ resourceId: task.id,
750
+ current: task.updatedAt,
751
+ request: ctx.request ?? null
752
+ });
753
+ const warehouseId = typeof task.warehouse === "string" ? task.warehouse : task.warehouse.id;
754
+ const taskQuantity = toNumber(task.quantity);
755
+ const existingByRef = await findPutawayCompleteMovementByReference(trx, scope, referenceId);
756
+ if (task.status === "done") {
757
+ if (!existingByRef) {
758
+ throw new CrudHttpError(409, { error: "invalid_putaway_state" });
759
+ }
760
+ const existingQty = toNumber(existingByRef.quantity);
761
+ if (Math.abs(existingQty - input.confirmedQuantity) > 1e-6) {
762
+ throw new CrudHttpError(409, { error: "putaway_complete_quantity_conflict" });
763
+ }
764
+ return {
765
+ task,
766
+ warehouseId,
767
+ taskQuantity,
768
+ moveResult: {
769
+ movementId: existingByRef.id,
770
+ warehouseId,
771
+ catalogVariantId: task.catalogVariantId,
772
+ quantity: existingQty,
773
+ tenantId: scope.tenantId,
774
+ organizationId: scope.organizationId,
775
+ movementEntity: existingByRef,
776
+ balances: [],
777
+ idempotentReplay: true
778
+ },
779
+ residual: 0,
780
+ alreadyDone: true,
781
+ emitCompletedEvent: false
782
+ };
783
+ }
784
+ assertPutawayCompletable(task.status);
785
+ assertPutawayConfirmedQuantity(taskQuantity, input.confirmedQuantity);
786
+ if (existingByRef) {
787
+ const existingQty = toNumber(existingByRef.quantity);
788
+ if (Math.abs(existingQty - input.confirmedQuantity) > 1e-6) {
789
+ throw new CrudHttpError(409, { error: "putaway_complete_quantity_conflict" });
790
+ }
791
+ task.targetLocationId = input.targetLocationId;
792
+ task.status = "done";
793
+ task.quantity = toNumericString(input.confirmedQuantity);
794
+ if (input.lotId) task.lotId = input.lotId;
795
+ await trx.flush();
796
+ const residual2 = putawayResidualQuantity(taskQuantity, input.confirmedQuantity);
797
+ if (residual2 > 0) {
798
+ await ensureResidualPutawayTask(trx, ctx, {
799
+ scope,
800
+ warehouseId,
801
+ parent: task,
802
+ residual: residual2
803
+ });
804
+ }
805
+ return {
806
+ task,
807
+ warehouseId,
808
+ taskQuantity,
809
+ moveResult: {
810
+ movementId: existingByRef.id,
811
+ warehouseId,
812
+ catalogVariantId: task.catalogVariantId,
813
+ quantity: existingQty,
814
+ tenantId: scope.tenantId,
815
+ organizationId: scope.organizationId,
816
+ movementEntity: existingByRef,
817
+ balances: [],
818
+ idempotentReplay: true
819
+ },
820
+ residual: residual2,
821
+ alreadyDone: false,
822
+ emitCompletedEvent: false
823
+ };
824
+ }
825
+ await requireActiveLocation(trx, ctx, task.sourceLocationId, warehouseId, scope);
826
+ const targetLocation = await requireActiveLocation(
827
+ trx,
828
+ ctx,
829
+ input.targetLocationId,
830
+ warehouseId,
831
+ scope
832
+ );
833
+ assertPutawayTargetLocationType(targetLocation.type);
834
+ const moveInput = {
835
+ organizationId: scope.organizationId,
836
+ tenantId: scope.tenantId,
837
+ warehouseId,
838
+ fromLocationId: task.sourceLocationId,
839
+ toLocationId: input.targetLocationId,
840
+ catalogVariantId: task.catalogVariantId,
841
+ lotId: input.lotId ?? task.lotId ?? void 0,
842
+ serialNumber: task.serialNumber?.trim() || void 0,
843
+ quantity: input.confirmedQuantity,
844
+ type: "putaway",
845
+ reason: input.reason ?? "Putaway task completion",
846
+ referenceType: "manual",
847
+ referenceId,
848
+ performedBy: input.performedBy,
849
+ performedAt: input.performedAt,
850
+ metadata: {
851
+ ...input.metadata ?? {},
852
+ putawayTaskId: task.id,
853
+ source: "putaway_task_complete"
854
+ }
855
+ };
856
+ const moveResult = await applyInventoryMoveInTransaction(trx, ctx, moveInput);
857
+ task.targetLocationId = input.targetLocationId;
858
+ task.status = "done";
859
+ task.quantity = toNumericString(input.confirmedQuantity);
860
+ if (input.lotId) task.lotId = input.lotId;
861
+ await trx.flush();
862
+ const residual = putawayResidualQuantity(taskQuantity, input.confirmedQuantity);
863
+ if (residual > 0) {
864
+ await ensureResidualPutawayTask(trx, ctx, {
865
+ scope,
866
+ warehouseId,
867
+ parent: task,
868
+ residual
869
+ });
870
+ }
871
+ return {
872
+ task,
873
+ warehouseId,
874
+ taskQuantity,
875
+ moveResult,
876
+ residual,
877
+ alreadyDone: false,
878
+ emitCompletedEvent: true
879
+ };
880
+ });
881
+ if (!outcome.alreadyDone) {
882
+ await emitPutawaySideEffects(ctx, [{ entity: outcome.task, action: "updated" }]);
883
+ }
884
+ await emitInventoryMoveSideEffects(ctx, outcome.moveResult);
885
+ if (outcome.emitCompletedEvent) {
886
+ void emitWmsEvent("wms.putaway.completed", {
887
+ id: outcome.task.id,
888
+ movementId: outcome.moveResult.movementId,
889
+ warehouseId: outcome.warehouseId,
890
+ sourceLocationId: outcome.task.sourceLocationId,
891
+ targetLocationId: outcome.task.targetLocationId,
892
+ catalogVariantId: outcome.task.catalogVariantId,
893
+ quantity: toNumericString(input.confirmedQuantity),
894
+ tenantId: outcome.task.tenantId,
895
+ organizationId: outcome.task.organizationId
896
+ }).catch(() => void 0);
897
+ }
898
+ return { taskId: outcome.task.id, movementId: outcome.moveResult.movementId };
899
+ },
900
+ buildLog: async ({ input, result, ctx }) => ({
901
+ ...await buildCrudLog(
902
+ ctx,
903
+ input,
904
+ result?.taskId ?? null,
905
+ "wms.audit.putaway.complete",
906
+ "Complete putaway task"
907
+ ),
908
+ context: { cacheAliases: [WMS_INVENTORY_BALANCE_RESOURCE, WMS_PUTAWAY_TASK_RESOURCE] }
909
+ })
910
+ };
911
+ const cancelPutawayTaskCommand = {
912
+ id: "wms.putaway-tasks.cancel",
913
+ isUndoable: false,
914
+ async execute(rawInput, ctx) {
915
+ const input = putawayTaskCancelSchema.parse(rawInput ?? {});
916
+ ensureTenantScope(ctx, input.tenantId);
917
+ ensureOrganizationScope(ctx, input.organizationId);
918
+ const em = resolveEm(ctx);
919
+ const scope = resolveScope(ctx, input);
920
+ const task = await withLockedPutawayTask(em, ctx, input.id, scope, async (locked, trx) => {
921
+ assertPutawayCancellable(locked.status);
922
+ locked.status = "cancelled";
923
+ locked.putawayKey = null;
924
+ await trx.flush();
925
+ return locked;
926
+ });
927
+ await emitPutawaySideEffects(ctx, [{ entity: task, action: "updated" }]);
928
+ void emitWmsEvent("wms.putaway.cancelled", {
929
+ id: task.id,
930
+ tenantId: task.tenantId,
931
+ organizationId: task.organizationId
932
+ }).catch(() => void 0);
933
+ return { taskId: task.id };
934
+ },
935
+ buildLog: async ({ input, result, ctx }) => buildCrudLog(ctx, input, result?.taskId ?? null, "wms.audit.putaway.cancel", "Cancel putaway task")
936
+ };
937
+ registerCommand(createPutawayTaskCommand);
938
+ registerCommand(updatePutawayTaskCommand);
939
+ registerCommand(deletePutawayTaskCommand);
940
+ registerCommand(createPutawayTaskFromBalanceCommand);
941
+ registerCommand(assignPutawayTaskCommand);
942
+ registerCommand(startPutawayTaskCommand);
943
+ registerCommand(completePutawayTaskCommand);
944
+ registerCommand(cancelPutawayTaskCommand);
945
+ //# sourceMappingURL=putaway.js.map