@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
@@ -1,10 +1,40 @@
1
- import type { InventoryLotStatus, InventoryStrategy } from '../data/entities'
1
+ import type {
2
+ InventoryLotStatus,
3
+ InventoryStrategy,
4
+ WarehouseLocationType,
5
+ } from '../data/entities'
2
6
 
3
7
  type LotEligibilityInput = {
4
8
  status?: InventoryLotStatus | null
5
9
  expiresAt?: Date | null
6
10
  } | null | undefined
7
11
 
12
+ /** Staging/dock hold inbound stock; Phase 2 must not treat them as pick/reserve candidates. */
13
+ export const NON_RESERVABLE_LOCATION_TYPES: ReadonlySet<WarehouseLocationType> = new Set([
14
+ 'staging',
15
+ 'dock',
16
+ ])
17
+
18
+ export function isReservableLocationType(type: string | null | undefined): boolean {
19
+ if (typeof type !== 'string' || type.length === 0) return true
20
+ return !NON_RESERVABLE_LOCATION_TYPES.has(type as WarehouseLocationType)
21
+ }
22
+
23
+ /**
24
+ * Returns false when the balance sits on staging/dock. Unpopulated location FKs
25
+ * (string id only) stay eligible so callers that omit `populate: ['location']`
26
+ * do not accidentally empty the candidate set.
27
+ */
28
+ export function isBalanceLocationReservable(balance: { location?: unknown }): boolean {
29
+ const location = balance.location
30
+ if (!location || typeof location === 'string') return true
31
+ const type =
32
+ typeof (location as { type?: unknown }).type === 'string'
33
+ ? (location as { type: string }).type
34
+ : null
35
+ return isReservableLocationType(type)
36
+ }
37
+
8
38
  export function isLotEligible(lot: LotEligibilityInput, now: Date = new Date()): boolean {
9
39
  if (!lot) return true
10
40
  if (lot.status !== 'available') return false
@@ -1,6 +1,7 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
3
  import {
4
+ Asn,
4
5
  InventoryBalance,
5
6
  InventoryLot,
6
7
  InventoryMovement,
@@ -16,6 +17,8 @@ const TREND_DAYS = 7
16
17
  const MONTHLY_TREND_MONTHS = 6
17
18
  const ACTIVITY_LIMIT = 10
18
19
  const EXPIRY_CARD_LIMIT = 5
20
+ const UUID_PATTERN =
21
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
19
22
 
20
23
  export type OperationalDashboardScope = {
21
24
  organizationId: string
@@ -52,8 +55,12 @@ export type OperationalDashboardActivityRow = {
52
55
  variantId: string
53
56
  referenceType: string | null
54
57
  referenceId: string | null
58
+ /** Human-readable reference (e.g. ASN reference number) — never the idempotency UUID. */
59
+ referenceLabel: string | null
55
60
  reason: string | null
56
61
  reasonCode: string | null
62
+ /** Movement metadata `source` (e.g. `asn_receive`) for activity label recovery. */
63
+ source: string | null
57
64
  locationLabel: string
58
65
  performedAt: string
59
66
  }
@@ -340,6 +347,66 @@ async function loadVariantSkus(
340
347
  return map
341
348
  }
342
349
 
350
+ function readMovementMetadataRecord(
351
+ metadata: InventoryMovement['metadata'],
352
+ ): Record<string, unknown> | null {
353
+ if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) return null
354
+ return metadata as Record<string, unknown>
355
+ }
356
+
357
+ export function extractActivityMovementSource(
358
+ metadata: InventoryMovement['metadata'],
359
+ ): string | null {
360
+ const record = readMovementMetadataRecord(metadata)
361
+ const source = record?.source
362
+ return typeof source === 'string' && source.trim() ? source.trim() : null
363
+ }
364
+
365
+ export function extractActivityAsnId(metadata: InventoryMovement['metadata']): string | null {
366
+ const record = readMovementMetadataRecord(metadata)
367
+ const asnId = record?.asnId
368
+ if (typeof asnId !== 'string') return null
369
+ const trimmed = asnId.trim()
370
+ return UUID_PATTERN.test(trimmed) ? trimmed : null
371
+ }
372
+
373
+ export function extractActivityAsnReferenceNumber(
374
+ metadata: InventoryMovement['metadata'],
375
+ ): string | null {
376
+ const record = readMovementMetadataRecord(metadata)
377
+ const value = record?.asnReferenceNumber
378
+ if (typeof value !== 'string') return null
379
+ const trimmed = value.trim()
380
+ return trimmed.length > 0 ? trimmed : null
381
+ }
382
+
383
+ async function loadAsnReferenceNumbers(
384
+ em: EntityManager,
385
+ scope: OperationalDashboardScope,
386
+ asnIds: string[],
387
+ ): Promise<Map<string, string>> {
388
+ if (asnIds.length === 0) return new Map()
389
+ const decryptionScope = resolveDecryptionScope(scope)
390
+ const asns = await findWithDecryption(
391
+ em,
392
+ Asn,
393
+ {
394
+ id: { $in: asnIds },
395
+ organizationId: scope.organizationId,
396
+ tenantId: scope.tenantId,
397
+ deletedAt: null,
398
+ },
399
+ undefined,
400
+ decryptionScope,
401
+ )
402
+ const map = new Map<string, string>()
403
+ for (const asn of asns) {
404
+ const label = asn.referenceNumber?.trim()
405
+ if (label) map.set(asn.id, label)
406
+ }
407
+ return map
408
+ }
409
+
343
410
  async function loadVariantIdsForProductLevelProfiles(
344
411
  em: EntityManager,
345
412
  scope: OperationalDashboardScope,
@@ -785,8 +852,27 @@ export async function loadOperationalDashboard(
785
852
  const variantIds = Array.from(new Set(activityMovements.map((movement) => movement.catalogVariantId)))
786
853
  const variantSkus = await loadVariantSkus(em, scope, variantIds)
787
854
 
855
+ const asnIdsNeedingLookup = Array.from(
856
+ new Set(
857
+ activityMovements
858
+ .map((movement) => {
859
+ if (extractActivityAsnReferenceNumber(movement.metadata)) return null
860
+ return extractActivityAsnId(movement.metadata)
861
+ })
862
+ .filter((id): id is string => Boolean(id)),
863
+ ),
864
+ )
865
+ const asnReferenceNumbers = await loadAsnReferenceNumbers(em, scope, asnIdsNeedingLookup)
866
+
788
867
  const recentActivity: OperationalDashboardActivityRow[] = activityMovements.map((movement) => {
789
868
  const warehouseLabel = resolveWarehouseLabel(movement.warehouse, movement.warehouse.id)
869
+ const source = extractActivityMovementSource(movement.metadata)
870
+ const metadataAsnReference = extractActivityAsnReferenceNumber(movement.metadata)
871
+ const asnId = extractActivityAsnId(movement.metadata)
872
+ const referenceLabel =
873
+ metadataAsnReference ?? (asnId ? asnReferenceNumbers.get(asnId) ?? null : null)
874
+ const reasonCode =
875
+ movement.reasonCode?.trim() || (source === 'asn_receive' ? 'asn_receive' : null)
790
876
  return {
791
877
  id: movement.id,
792
878
  movementType: movement.type,
@@ -795,8 +881,10 @@ export async function loadOperationalDashboard(
795
881
  variantId: movement.catalogVariantId,
796
882
  referenceType: movement.referenceType ?? null,
797
883
  referenceId: movement.referenceId ?? null,
884
+ referenceLabel,
798
885
  reason: movement.reason ?? null,
799
- reasonCode: movement.reasonCode ?? null,
886
+ reasonCode,
887
+ source,
800
888
  locationLabel: resolveLocationLabel(
801
889
  warehouseLabel,
802
890
  movement.locationFrom?.code,
@@ -0,0 +1,210 @@
1
+ import { createHash } from 'crypto'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
4
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
6
+ import {
7
+ InventoryMovement,
8
+ type PutawayTaskStatus,
9
+ type WarehouseLocationType,
10
+ } from '../data/entities'
11
+
12
+ const TERMINAL: ReadonlySet<PutawayTaskStatus> = new Set(['done', 'cancelled'])
13
+ const FORBIDDEN_PUTAWAY_TARGET_TYPES: ReadonlySet<WarehouseLocationType> = new Set(['staging', 'dock'])
14
+ const PUTAWAY_TASK_STATUSES: ReadonlySet<PutawayTaskStatus> = new Set([
15
+ 'open',
16
+ 'in_progress',
17
+ 'done',
18
+ 'cancelled',
19
+ ])
20
+
21
+ /**
22
+ * Parse list `status` query: single value or comma-separated (e.g. `open,in_progress`).
23
+ * Returns `$eq` / `$in` filter fragment, or undefined when empty/invalid.
24
+ */
25
+ export function buildPutawayTaskStatusFilter(
26
+ statusQuery: string | undefined,
27
+ ): { $eq: PutawayTaskStatus } | { $in: PutawayTaskStatus[] } | undefined {
28
+ if (typeof statusQuery !== 'string' || statusQuery.trim().length === 0) return undefined
29
+ const statuses = [
30
+ ...new Set(
31
+ statusQuery
32
+ .split(',')
33
+ .map((value) => value.trim())
34
+ .filter((value): value is PutawayTaskStatus =>
35
+ PUTAWAY_TASK_STATUSES.has(value as PutawayTaskStatus),
36
+ ),
37
+ ),
38
+ ]
39
+ if (statuses.length === 0) return undefined
40
+ if (statuses.length === 1) return { $eq: statuses[0]! }
41
+ return { $in: statuses }
42
+ }
43
+
44
+ export function assertPutawayAssignable(status: PutawayTaskStatus): void {
45
+ if (TERMINAL.has(status)) {
46
+ throw new CrudHttpError(409, { error: 'invalid_putaway_state' })
47
+ }
48
+ }
49
+
50
+ export function assertPutawayStartable(status: PutawayTaskStatus): void {
51
+ if (status !== 'open' && status !== 'in_progress') {
52
+ throw new CrudHttpError(409, { error: 'invalid_putaway_state' })
53
+ }
54
+ }
55
+
56
+ export function assertPutawayCompletable(status: PutawayTaskStatus): void {
57
+ if (status !== 'open' && status !== 'in_progress') {
58
+ throw new CrudHttpError(409, { error: 'invalid_putaway_state' })
59
+ }
60
+ }
61
+
62
+ export function assertPutawayCancellable(status: PutawayTaskStatus): void {
63
+ if (TERMINAL.has(status)) {
64
+ throw new CrudHttpError(409, { error: 'invalid_putaway_state' })
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Soft-delete is archival cleanup for cancelled tasks only.
70
+ * Done is permanent history: soft-deleting it would drop the row from the
71
+ * partial unique index (`deleted_at IS NULL`) while leaving `putaway_key`,
72
+ * letting idempotent ASN receive recreate an open putaway for already-moved stock.
73
+ * Cancel first (clears the key); refuse open/in_progress/done delete.
74
+ */
75
+ export function assertPutawayDeletable(status: PutawayTaskStatus): void {
76
+ if (status === 'cancelled') return
77
+ if (status === 'done') {
78
+ throw new CrudHttpError(409, { error: 'putaway_delete_done_forbidden' })
79
+ }
80
+ throw new CrudHttpError(409, { error: 'putaway_delete_requires_terminal_status' })
81
+ }
82
+
83
+ /** Reject lifecycle fields on putaway-task CRUD before zod strips them. */
84
+ export function assertPutawayLifecycleFieldsForbidden(rawInput: unknown): void {
85
+ if (!rawInput || typeof rawInput !== 'object') return
86
+ const payload = rawInput as Record<string, unknown>
87
+ // Status and assignee changes go through dedicated lifecycle endpoints.
88
+ if ('status' in payload || 'assignedTo' in payload || 'assigned_to' in payload) {
89
+ throw new CrudHttpError(422, { error: 'lifecycle_field_forbidden' })
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Complete authorization: managers may complete any task; operators with
95
+ * `wms.adjust_inventory` may complete only when the task is assigned to them.
96
+ */
97
+ export function assertPutawayCompleteAuthorized(input: {
98
+ canManagePutaway: boolean
99
+ canAdjustInventory: boolean
100
+ actorUserId: string | null | undefined
101
+ assignedTo: string | null | undefined
102
+ }): void {
103
+ if (input.canManagePutaway) return
104
+ if (
105
+ input.canAdjustInventory
106
+ && typeof input.actorUserId === 'string'
107
+ && input.actorUserId.length > 0
108
+ && input.assignedTo === input.actorUserId
109
+ ) {
110
+ return
111
+ }
112
+ throw new CrudHttpError(403, { error: 'Forbidden' })
113
+ }
114
+
115
+ /** Reject completing with more than the open task quantity. */
116
+ export function assertPutawayConfirmedQuantity(
117
+ taskQuantity: number,
118
+ confirmedQuantity: number,
119
+ ): void {
120
+ if (!(confirmedQuantity > 0) || !Number.isFinite(confirmedQuantity)) {
121
+ throw new CrudHttpError(422, { error: 'invalid_putaway_quantity' })
122
+ }
123
+ if (confirmedQuantity > taskQuantity + 0.000001) {
124
+ throw new CrudHttpError(422, { error: 'putaway_quantity_exceeds_task' })
125
+ }
126
+ }
127
+
128
+ export function putawayResidualQuantity(taskQuantity: number, confirmedQuantity: number): number {
129
+ const residual = taskQuantity - confirmedQuantity
130
+ return residual > 0.000001 ? residual : 0
131
+ }
132
+
133
+ /**
134
+ * Staging floor for new putaway: on-hand minus reserved/allocated minus open
135
+ * and in-progress putaway commitments (optionally excluding one task being updated).
136
+ */
137
+ export function computeUncommittedPutawaySourceQuantity(input: {
138
+ quantityOnHand: number
139
+ quantityReserved: number
140
+ quantityAllocated: number
141
+ openPutawayQuantities: number[]
142
+ }): number {
143
+ const available =
144
+ input.quantityOnHand - input.quantityReserved - input.quantityAllocated
145
+ const committed = input.openPutawayQuantities.reduce((sum, qty) => sum + qty, 0)
146
+ return available - committed
147
+ }
148
+
149
+ /** True when requested qty fits the uncommitted staging floor (epsilon-tolerant). */
150
+ export function hasUncommittedPutawaySourceQuantity(
151
+ remainingAvailable: number,
152
+ requestedQuantity: number,
153
+ ): boolean {
154
+ return requestedQuantity <= remainingAvailable + 0.000001
155
+ }
156
+
157
+ /**
158
+ * Prefer an open/in_progress peer whose quantity covers the receipt when
159
+ * recreate would oversubscribe staging stock already queued elsewhere.
160
+ */
161
+ export function selectCoveringOpenPutawayTask<T extends { quantity: number }>(
162
+ tasks: T[],
163
+ requestedQuantity: number,
164
+ ): T | null {
165
+ if (!(requestedQuantity > 0.000001) || tasks.length === 0) return null
166
+ let best: T | null = null
167
+ for (const task of tasks) {
168
+ if (task.quantity + 0.000001 < requestedQuantity) continue
169
+ if (!best || task.quantity < best.quantity) best = task
170
+ }
171
+ return best
172
+ }
173
+
174
+ /**
175
+ * Stable movement reference for putaway complete — task id only (not confirmed qty).
176
+ * Retries with the same or different confirmedQuantity must not open a second
177
+ * stock move under a different idempotency signature; quantity conflicts are
178
+ * detected by looking up this reference before moving.
179
+ */
180
+ export function buildPutawayCompleteReferenceId(taskId: string): string {
181
+ const hex = createHash('sha256').update(`wms:putaway-complete:${taskId}`).digest('hex')
182
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`
183
+ }
184
+
185
+ export function assertPutawayTargetLocationType(type: WarehouseLocationType): void {
186
+ if (FORBIDDEN_PUTAWAY_TARGET_TYPES.has(type)) {
187
+ throw new CrudHttpError(422, { error: 'invalid_putaway_target_location' })
188
+ }
189
+ }
190
+
191
+ export async function resolveLotIdFromMovement(
192
+ em: EntityManager,
193
+ ctx: CommandRuntimeContext,
194
+ movementId: string,
195
+ ): Promise<string | null> {
196
+ const tenantId = ctx.auth?.tenantId ?? null
197
+ const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
198
+ if (!tenantId || !organizationId) return null
199
+ const movement = await findOneWithDecryption(
200
+ em,
201
+ InventoryMovement,
202
+ { id: movementId, deletedAt: null },
203
+ undefined,
204
+ { tenantId, organizationId },
205
+ )
206
+ if (!movement) return null
207
+ const lotRaw = movement.lot ?? null
208
+ if (!lotRaw) return null
209
+ return typeof lotRaw === 'string' ? lotRaw : lotRaw.id
210
+ }
@@ -20,6 +20,8 @@ export const WMS_MANAGE_FEATURES = [
20
20
  'wms.manage_locations',
21
21
  'wms.manage_inventory',
22
22
  'wms.manage_reservations',
23
+ 'wms.manage_asn',
24
+ 'wms.manage_putaway',
23
25
  ] as const
24
26
 
25
27
  export const WMS_SUPERVISOR_FEATURES = [
@@ -3,11 +3,13 @@ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib
3
3
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
4
4
  import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
5
5
  import type { FeatureTogglesService } from '@open-mercato/core/modules/feature_toggles/lib/feature-flag-check'
6
+ import { createLogger } from '@open-mercato/shared/lib/logger'
6
7
  import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
7
8
  import { E } from '#generated/entities.ids.generated'
8
9
  import { InventoryBalance, InventoryReservation } from '../data/entities'
9
10
  import { emitWmsEvent } from '../events'
10
11
  import { loadExplicitWarehouseIdForOrder } from './salesOrderWarehouseAssignment'
12
+ import { isBalanceLocationReservable } from './inventoryPolicy'
11
13
  import {
12
14
  resolvePrimaryWarehouseId,
13
15
  sortWarehouseAvailabilityForReservation,
@@ -15,6 +17,8 @@ import {
15
17
  } from './primaryWarehousePolicy'
16
18
  import { resolveWmsIntegrationToggleEnabled } from './wmsIntegrationToggles'
17
19
 
20
+ const logger = createLogger('wms')
21
+
18
22
  type EventContext = {
19
23
  resolve: <T = unknown>(name: string) => T
20
24
  }
@@ -49,10 +53,29 @@ type SalesOrderRow = {
49
53
  id?: string
50
54
  order_number?: string | null
51
55
  status?: string | null
56
+ fulfillment_status?: string | null
52
57
  tenant_id?: string | null
53
58
  organization_id?: string | null
54
59
  }
55
60
 
61
+ /** Only confirmed (non-fulfilled / non-cancelled) orders may receive re-reservations. */
62
+ export function isReservableOrderStatus(
63
+ status?: string | null,
64
+ fulfillmentStatus?: string | null,
65
+ ): boolean {
66
+ const normalized = (status ?? '').trim().toLowerCase()
67
+ if (normalized !== 'confirmed') return false
68
+ const fulfillment = (fulfillmentStatus ?? '').trim().toLowerCase()
69
+ if (
70
+ fulfillment === 'fulfilled' ||
71
+ fulfillment === 'cancelled' ||
72
+ fulfillment === 'canceled'
73
+ ) {
74
+ return false
75
+ }
76
+ return true
77
+ }
78
+
56
79
  type SalesOrderLineRow = {
57
80
  id?: string
58
81
  kind?: string | null
@@ -197,7 +220,7 @@ async function loadBalances(
197
220
  organizationId: scope.organizationId,
198
221
  deletedAt: null,
199
222
  },
200
- undefined,
223
+ { populate: ['location'] },
201
224
  scope,
202
225
  )
203
226
  }
@@ -232,6 +255,10 @@ function buildWarehouseAvailability(
232
255
  ): Map<string, WarehouseAvailability[]> {
233
256
  const byVariant = new Map<string, Map<string, number>>()
234
257
  for (const balance of balances) {
258
+ // Staging/dock are inbound holding locations — availability for sales
259
+ // reservation must ignore them so ASN receive re-eval does not claim
260
+ // stock that putaway still needs to move into storage.
261
+ if (!isBalanceLocationReservable(balance)) continue
235
262
  const warehouseId = getWarehouseId(balance)
236
263
  if (!warehouseId) continue
237
264
  const available =
@@ -364,6 +391,128 @@ export async function reserveInventoryForConfirmedOrder(
364
391
  }
365
392
  }
366
393
 
394
+ type StockIncreasePayload = {
395
+ catalogVariantId?: string | null
396
+ tenantId?: string | null
397
+ organizationId?: string | null
398
+ }
399
+
400
+ /**
401
+ * Re-runs reservation automation for sales orders that include the received
402
+ * catalog variant. Idempotent: reserveInventoryForConfirmedOrder only fills
403
+ * remaining shortfall. No-ops when the sales integration toggle is off or
404
+ * sales query peers are unavailable.
405
+ *
406
+ * Candidate discovery starts from currently reservable (confirmed,
407
+ * non-terminal fulfillment) sales orders — not lifetime `sales_order_line`
408
+ * history for the variant — then keeps only orders that still reference the
409
+ * received catalog variant. That bounds work to the open order set.
410
+ */
411
+ const REEVAL_ORDER_PAGE_SIZE = 500
412
+
413
+ export async function reevaluateReservationsAfterStockIncrease(
414
+ payload: StockIncreasePayload,
415
+ ctx: EventContext,
416
+ ): Promise<void> {
417
+ if (!payload.catalogVariantId || !payload.tenantId || !payload.organizationId) return
418
+ if (!(await isInventoryAutomationEnabled(ctx, payload.tenantId))) return
419
+
420
+ const scope: Scope = {
421
+ tenantId: payload.tenantId,
422
+ organizationId: payload.organizationId,
423
+ }
424
+
425
+ let queryEngine: QueryEngine
426
+ try {
427
+ queryEngine = ctx.resolve<QueryEngine>('queryEngine')
428
+ } catch {
429
+ // Sales module / query engine absent or unavailable — degrade gracefully.
430
+ return
431
+ }
432
+
433
+ // Page reservable orders, then intersect with lines for this variant.
434
+ // Process each page immediately (bounded memory); reserveInventoryForConfirmedOrder is idempotent.
435
+ let page = 1
436
+
437
+ for (;;) {
438
+ let orderItems: SalesOrderRow[]
439
+ try {
440
+ const result = await queryEngine.query<SalesOrderRow>(E.sales.sales_order, {
441
+ tenantId: scope.tenantId,
442
+ organizationId: scope.organizationId,
443
+ filters: { status: { $eq: 'confirmed' } },
444
+ fields: ['id', 'status', 'fulfillment_status'],
445
+ page: { page, pageSize: REEVAL_ORDER_PAGE_SIZE },
446
+ })
447
+ orderItems = result.items
448
+ } catch {
449
+ return
450
+ }
451
+
452
+ if (orderItems.length === 0) break
453
+
454
+ const reservableOrderIds = orderItems
455
+ .filter((order) => isReservableOrderStatus(order.status, order.fulfillment_status))
456
+ .map((order) => order.id)
457
+ .filter((value): value is string => typeof value === 'string' && value.length > 0)
458
+
459
+ if (reservableOrderIds.length > 0) {
460
+ const matchingOrderIds = new Set<string>()
461
+ let linePage = 1
462
+ try {
463
+ for (;;) {
464
+ const lines = await queryEngine.query<SalesOrderLineRow & { order_id?: string | null }>(
465
+ E.sales.sales_order_line,
466
+ {
467
+ tenantId: scope.tenantId,
468
+ organizationId: scope.organizationId,
469
+ filters: {
470
+ product_variant_id: { $eq: payload.catalogVariantId },
471
+ order_id: { $in: reservableOrderIds },
472
+ },
473
+ fields: ['id', 'order_id', 'product_variant_id'],
474
+ page: { page: linePage, pageSize: REEVAL_ORDER_PAGE_SIZE },
475
+ },
476
+ )
477
+ for (const line of lines.items) {
478
+ if (typeof line.order_id === 'string' && line.order_id.length > 0) {
479
+ matchingOrderIds.add(line.order_id)
480
+ }
481
+ }
482
+ if (lines.items.length < REEVAL_ORDER_PAGE_SIZE) break
483
+ linePage += 1
484
+ }
485
+ } catch (error) {
486
+ // Transient peer failure on one page must not abort later order pages.
487
+ logger.warn('Reservation re-eval line lookup failed; continuing next page', {
488
+ catalogVariantId: payload.catalogVariantId,
489
+ page,
490
+ orderCount: reservableOrderIds.length,
491
+ error: error instanceof Error ? error.message : String(error),
492
+ })
493
+ }
494
+
495
+ for (const orderId of matchingOrderIds) {
496
+ try {
497
+ await reserveInventoryForConfirmedOrder(
498
+ {
499
+ orderId,
500
+ tenantId: scope.tenantId,
501
+ organizationId: scope.organizationId,
502
+ },
503
+ ctx,
504
+ )
505
+ } catch {
506
+ // Keep processing remaining orders; a single peer failure must not abort the batch.
507
+ }
508
+ }
509
+ }
510
+
511
+ if (orderItems.length < REEVAL_ORDER_PAGE_SIZE) break
512
+ page += 1
513
+ }
514
+ }
515
+
367
516
  export async function releaseInventoryForCancelledOrder(
368
517
  payload: SalesOrderLifecyclePayload,
369
518
  ctx: EventContext,