@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,43 @@
1
+ import { Migration } from '@mikro-orm/migrations'
2
+
3
+ export class Migration20260812120000_wms extends Migration {
4
+ override up(): void | Promise<void> {
5
+ this.addSql(
6
+ `create table "wms_asns" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "metadata" jsonb null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, "warehouse_id" uuid not null, "vendor_id" uuid null, "status" text not null default 'draft', "expected_at" timestamptz not null, "reference_number" text null, "notes" text null, primary key ("id"));`,
7
+ )
8
+ this.addSql(
9
+ `create index "wms_asns_org_warehouse_status_expected_idx" on "wms_asns" ("organization_id", "warehouse_id", "status", "expected_at");`,
10
+ )
11
+ this.addSql(
12
+ `create index "wms_asns_org_vendor_expected_idx" on "wms_asns" ("organization_id", "vendor_id", "expected_at" desc) where deleted_at is null;`,
13
+ )
14
+ this.addSql(`create index "wms_asns_org_tenant_idx" on "wms_asns" ("organization_id", "tenant_id");`)
15
+
16
+ this.addSql(
17
+ `create table "wms_receiving_lines" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "metadata" jsonb null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, "asn_id" uuid not null, "catalog_variant_id" uuid not null, "expected_qty" numeric(16,4) not null, "received_qty" numeric(16,4) not null default '0', "lot_number" text null, "serial_numbers" jsonb null, "qc_status" text not null default 'pending', "target_staging_location_id" uuid null, "rejection_reason" text null, primary key ("id"));`,
18
+ )
19
+ this.addSql(
20
+ `create index "wms_receiving_lines_org_variant_qc_idx" on "wms_receiving_lines" ("organization_id", "catalog_variant_id", "qc_status");`,
21
+ )
22
+ this.addSql(
23
+ `create index "wms_receiving_lines_org_asn_idx" on "wms_receiving_lines" ("organization_id", "asn_id");`,
24
+ )
25
+ this.addSql(
26
+ `create index "wms_receiving_lines_org_tenant_idx" on "wms_receiving_lines" ("organization_id", "tenant_id");`,
27
+ )
28
+
29
+ this.addSql(
30
+ `alter table "wms_asns" add constraint "wms_asns_warehouse_id_foreign" foreign key ("warehouse_id") references "wms_warehouses" ("id");`,
31
+ )
32
+ this.addSql(
33
+ `alter table "wms_receiving_lines" add constraint "wms_receiving_lines_asn_id_foreign" foreign key ("asn_id") references "wms_asns" ("id");`,
34
+ )
35
+ }
36
+
37
+ override down(): void | Promise<void> {
38
+ this.addSql(`alter table "wms_receiving_lines" drop constraint if exists "wms_receiving_lines_asn_id_foreign";`)
39
+ this.addSql(`alter table "wms_asns" drop constraint if exists "wms_asns_warehouse_id_foreign";`)
40
+ this.addSql(`drop table if exists "wms_receiving_lines" cascade;`)
41
+ this.addSql(`drop table if exists "wms_asns" cascade;`)
42
+ }
43
+ }
@@ -0,0 +1,28 @@
1
+ import { Migration } from '@mikro-orm/migrations'
2
+
3
+ export class Migration20260812130000_wms extends Migration {
4
+ override up(): void | Promise<void> {
5
+ this.addSql(
6
+ `create table "wms_putaway_tasks" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "metadata" jsonb null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, "warehouse_id" uuid not null, "source_location_id" uuid not null, "target_location_id" uuid null, "catalog_variant_id" uuid not null, "lot_id" uuid null, "quantity" numeric(16,4) not null, "status" text not null default 'open', "assigned_to" uuid null, "priority" int not null default 5, primary key ("id"));`,
7
+ )
8
+ this.addSql(
9
+ `create index "wms_putaway_tasks_org_warehouse_status_priority_idx" on "wms_putaway_tasks" ("organization_id", "warehouse_id", "status", "priority");`,
10
+ )
11
+ this.addSql(
12
+ `create index "wms_putaway_tasks_org_assigned_status_idx" on "wms_putaway_tasks" ("organization_id", "assigned_to", "status");`,
13
+ )
14
+ this.addSql(
15
+ `create index "wms_putaway_tasks_org_tenant_idx" on "wms_putaway_tasks" ("organization_id", "tenant_id");`,
16
+ )
17
+ this.addSql(
18
+ `alter table "wms_putaway_tasks" add constraint "wms_putaway_tasks_warehouse_id_foreign" foreign key ("warehouse_id") references "wms_warehouses" ("id");`,
19
+ )
20
+ }
21
+
22
+ override down(): void | Promise<void> {
23
+ this.addSql(
24
+ `alter table "wms_putaway_tasks" drop constraint if exists "wms_putaway_tasks_warehouse_id_foreign";`,
25
+ )
26
+ this.addSql(`drop table if exists "wms_putaway_tasks" cascade;`)
27
+ }
28
+ }
@@ -0,0 +1,17 @@
1
+ import { Migration } from '@mikro-orm/migrations'
2
+
3
+ export class Migration20260822120000_wms extends Migration {
4
+ override up(): void | Promise<void> {
5
+ this.addSql(`alter table "wms_putaway_tasks" add column "putaway_key" text null;`)
6
+ // Exclude cancelled so cancel-without-clearing-key (legacy rows) cannot block
7
+ // ASN receive recreate; cancel also nulls putaway_key in the command path.
8
+ this.addSql(
9
+ `create unique index "wms_putaway_tasks_org_putaway_key_unique_idx" on "wms_putaway_tasks" ("organization_id", "putaway_key") where putaway_key is not null and deleted_at is null and status <> 'cancelled';`,
10
+ )
11
+ }
12
+
13
+ override down(): void | Promise<void> {
14
+ this.addSql(`drop index if exists "wms_putaway_tasks_org_putaway_key_unique_idx";`)
15
+ this.addSql(`alter table "wms_putaway_tasks" drop column if exists "putaway_key";`)
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ import { Migration } from '@mikro-orm/migrations'
2
+
3
+ export class Migration20260822130000_wms extends Migration {
4
+ override up(): void | Promise<void> {
5
+ this.addSql(`alter table "wms_asns" add column "source_key" text null;`)
6
+ this.addSql(
7
+ `create unique index "wms_asns_org_source_key_unique_idx" on "wms_asns" ("organization_id", "source_key") where source_key is not null and deleted_at is null;`,
8
+ )
9
+ }
10
+
11
+ override down(): void | Promise<void> {
12
+ this.addSql(`drop index if exists "wms_asns_org_source_key_unique_idx";`)
13
+ this.addSql(`alter table "wms_asns" drop column if exists "source_key";`)
14
+ }
15
+ }
@@ -0,0 +1,11 @@
1
+ import { Migration } from '@mikro-orm/migrations'
2
+
3
+ export class Migration20261029120000_wms extends Migration {
4
+ override up(): void | Promise<void> {
5
+ this.addSql(`alter table "wms_putaway_tasks" add column "serial_number" text null;`)
6
+ }
7
+
8
+ override down(): void | Promise<void> {
9
+ this.addSql(`alter table "wms_putaway_tasks" drop column if exists "serial_number";`)
10
+ }
11
+ }
@@ -102,6 +102,37 @@ function buildLotPresenter(
102
102
  return { title, subtitle, icon: 'package-search' }
103
103
  }
104
104
 
105
+ function buildAsnPresenter(
106
+ t: TranslateFn,
107
+ record: Record<string, unknown>,
108
+ ): SearchResultPresenter {
109
+ const label = t('wms.search.badge.asn', 'ASN')
110
+ const title =
111
+ pickString(record.reference_number, record.referenceNumber, record.id) ?? label
112
+ const subtitle = formatSubtitle(
113
+ record.status,
114
+ record.warehouse_id ?? record.warehouseId,
115
+ record.expected_at ?? record.expectedAt,
116
+ )
117
+ return { title, subtitle, icon: 'truck', badge: label }
118
+ }
119
+
120
+ function buildPutawayPresenter(
121
+ t: TranslateFn,
122
+ record: Record<string, unknown>,
123
+ ): SearchResultPresenter {
124
+ const label = t('wms.search.badge.putaway', 'Putaway task')
125
+ const title =
126
+ pickString(record.catalog_variant_id, record.catalogVariantId, record.id) ?? label
127
+ const subtitle = formatSubtitle(
128
+ record.status,
129
+ record.warehouse_id ?? record.warehouseId,
130
+ record.source_location_id ?? record.sourceLocationId,
131
+ record.target_location_id ?? record.targetLocationId,
132
+ )
133
+ return { title, subtitle, icon: 'package', badge: label }
134
+ }
135
+
105
136
  export const searchConfig: SearchModuleConfig = {
106
137
  entities: [
107
138
  {
@@ -227,6 +258,70 @@ export const searchConfig: SearchModuleConfig = {
227
258
  searchable: ['lot_number', 'batch_number', 'sku', 'catalog_variant_id', 'status', 'expires_at'],
228
259
  },
229
260
  },
261
+ {
262
+ entityId: E.wms.asn,
263
+ aclFeatures: ['wms.view'],
264
+ enabled: true,
265
+ priority: 7,
266
+ buildSource: async (ctx) => {
267
+ const { t } = await resolveTranslations()
268
+ const record = ctx.record
269
+ const lines: string[] = []
270
+ appendLine(lines, 'Reference', record.reference_number ?? record.referenceNumber)
271
+ appendLine(lines, 'Status', record.status)
272
+ appendLine(lines, 'Warehouse', record.warehouse_id ?? record.warehouseId)
273
+ appendLine(lines, 'Vendor', record.vendor_id ?? record.vendorId)
274
+ appendLine(lines, 'Expected at', record.expected_at ?? record.expectedAt)
275
+ appendLine(lines, 'Notes', record.notes)
276
+ return buildIndexSource(ctx, buildAsnPresenter(t, record), lines)
277
+ },
278
+ formatResult: async (ctx) => {
279
+ const { t } = await resolveTranslations()
280
+ return buildAsnPresenter(t, ctx.record)
281
+ },
282
+ resolveUrl: async (ctx) => {
283
+ const id = pickString(ctx.record.id)
284
+ if (id) return `${WMS_ROOT_URL}/asns/${id}`
285
+ return WMS_ROOT_URL
286
+ },
287
+ fieldPolicy: {
288
+ searchable: ['reference_number', 'status', 'warehouse_id', 'vendor_id', 'notes'],
289
+ },
290
+ },
291
+ {
292
+ entityId: E.wms.putaway_task,
293
+ aclFeatures: ['wms.view'],
294
+ enabled: true,
295
+ priority: 6,
296
+ buildSource: async (ctx) => {
297
+ const { t } = await resolveTranslations()
298
+ const record = ctx.record
299
+ const lines: string[] = []
300
+ appendLine(lines, 'Status', record.status)
301
+ appendLine(lines, 'Warehouse', record.warehouse_id ?? record.warehouseId)
302
+ appendLine(lines, 'Source', record.source_location_id ?? record.sourceLocationId)
303
+ appendLine(lines, 'Target', record.target_location_id ?? record.targetLocationId)
304
+ appendLine(lines, 'Variant', record.catalog_variant_id ?? record.catalogVariantId)
305
+ appendLine(lines, 'Assigned to', record.assigned_to ?? record.assignedTo)
306
+ appendLine(lines, 'Quantity', record.quantity)
307
+ return buildIndexSource(ctx, buildPutawayPresenter(t, record), lines)
308
+ },
309
+ formatResult: async (ctx) => {
310
+ const { t } = await resolveTranslations()
311
+ return buildPutawayPresenter(t, ctx.record)
312
+ },
313
+ resolveUrl: async () => `${WMS_ROOT_URL}/putaway`,
314
+ fieldPolicy: {
315
+ searchable: [
316
+ 'status',
317
+ 'warehouse_id',
318
+ 'source_location_id',
319
+ 'target_location_id',
320
+ 'catalog_variant_id',
321
+ 'assigned_to',
322
+ ],
323
+ },
324
+ },
230
325
  ],
231
326
  }
232
327
 
@@ -0,0 +1,21 @@
1
+ import { reevaluateReservationsAfterStockIncrease } from '../lib/salesOrderInventoryAutomation'
2
+
3
+ export const metadata = {
4
+ event: 'wms.asn.line_received',
5
+ persistent: true,
6
+ id: 'wms:asn-line-received-reevaluate-reservations',
7
+ }
8
+
9
+ type SubscriberContext = {
10
+ resolve: <T = unknown>(name: string) => T
11
+ }
12
+
13
+ type AsnLineReceivedPayload = {
14
+ catalogVariantId?: string | null
15
+ tenantId?: string | null
16
+ organizationId?: string | null
17
+ }
18
+
19
+ export default async function handle(payload: AsnLineReceivedPayload, ctx: SubscriberContext) {
20
+ await reevaluateReservationsAfterStockIncrease(payload, ctx)
21
+ }
@@ -0,0 +1,12 @@
1
+ import { createInventoryEnricherCacheHandler } from '../lib/inventoryEnricherCacheSubscriber'
2
+
3
+ // Sales-order enricher `inboundSummary` aggregates open ASN rows (`draft` /
4
+ // `in_transit`). ASN lifecycle writes change that projection without always
5
+ // touching balances, so they need their own invalidation family.
6
+ export const metadata = {
7
+ event: 'wms.asn.*',
8
+ persistent: false,
9
+ id: 'wms:invalidate-enricher-cache-asn',
10
+ }
11
+
12
+ export default createInventoryEnricherCacheHandler('inventory')
@@ -0,0 +1,238 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
3
+ import type { FeatureTogglesService } from '@open-mercato/core/modules/feature_toggles/lib/feature-flag-check'
4
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
5
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
6
+ import { createLogger } from '@open-mercato/shared/lib/logger'
7
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
8
+ import { Asn } from '../data/entities'
9
+ import { resolveWmsIntegrationToggleEnabled } from '../lib/wmsIntegrationToggles'
10
+
11
+ const logger = createLogger('wms')
12
+
13
+ export const metadata = {
14
+ event: 'procurement.goods_receipt.created',
15
+ persistent: true,
16
+ id: 'wms:procurement-goods-receipt-created',
17
+ }
18
+
19
+ type SubscriberContext = {
20
+ resolve: <T = unknown>(name: string) => T
21
+ }
22
+
23
+ /**
24
+ * Assumed procurement.goods_receipt.created payload (procurement module is
25
+ * optional / thin today). Fields beyond id + scope are best-effort:
26
+ *
27
+ * - id / goodsReceiptId: required for idempotent create via Asn.sourceKey
28
+ * - warehouseId (required to create): destination warehouse UUID
29
+ * - vendorId?: customers UUID
30
+ * - expectedAt?: ISO date (defaults to now)
31
+ * - referenceNumber?: external PO/GRN display reference (falls back to goods receipt id;
32
+ * not used as the uniqueness key — free-form refs may legitimately collide)
33
+ * - tenantId / organizationId: required scope
34
+ * - lines?: [{ catalogVariantId, expectedQty, lotNumber? }]
35
+ *
36
+ * When the toggle is off, required fields are missing, or peers are absent,
37
+ * this subscriber no-ops.
38
+ */
39
+ type GoodsReceiptCreatedPayload = {
40
+ id?: string | null
41
+ goodsReceiptId?: string | null
42
+ warehouseId?: string | null
43
+ vendorId?: string | null
44
+ expectedAt?: string | Date | null
45
+ referenceNumber?: string | null
46
+ tenantId?: string | null
47
+ organizationId?: string | null
48
+ lines?: Array<{
49
+ catalogVariantId?: string | null
50
+ expectedQty?: string | number | null
51
+ lotNumber?: string | null
52
+ }> | null
53
+ }
54
+
55
+ export function buildProcurementGoodsReceiptSourceKey(goodsReceiptId: string): string {
56
+ return `procurement.goods_receipt:${goodsReceiptId}`
57
+ }
58
+
59
+ function tryResolve<T>(ctx: SubscriberContext, name: string): T | undefined {
60
+ try {
61
+ return ctx.resolve<T>(name)
62
+ } catch {
63
+ return undefined
64
+ }
65
+ }
66
+
67
+ function asUuid(value: unknown): string | null {
68
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null
69
+ }
70
+
71
+ function toPositiveQuantity(value: unknown): number | null {
72
+ const numeric = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN
73
+ if (!Number.isFinite(numeric) || numeric <= 0) return null
74
+ return numeric
75
+ }
76
+
77
+ function isUniqueConstraintError(error: unknown): boolean {
78
+ if (!error || typeof error !== 'object') return false
79
+ const code = (error as { code?: string }).code
80
+ if (code === '23505') return true
81
+ const name = (error as { name?: string }).name
82
+ if (name === 'UniqueConstraintViolationException') return true
83
+ const cause = (error as { cause?: unknown }).cause
84
+ if (cause && cause !== error) return isUniqueConstraintError(cause)
85
+ const driverError = (error as { driverError?: unknown }).driverError
86
+ if (driverError && driverError !== error) return isUniqueConstraintError(driverError)
87
+ return false
88
+ }
89
+
90
+ async function findExistingAsnForGoodsReceipt(
91
+ em: EntityManager,
92
+ scope: { organizationId: string; tenantId: string },
93
+ goodsReceiptId: string,
94
+ sourceKey: string,
95
+ ): Promise<Asn | null> {
96
+ const bySourceKey = await findOneWithDecryption(
97
+ em,
98
+ Asn,
99
+ {
100
+ organizationId: scope.organizationId,
101
+ tenantId: scope.tenantId,
102
+ sourceKey,
103
+ deletedAt: null,
104
+ },
105
+ undefined,
106
+ scope,
107
+ )
108
+ if (bySourceKey) return bySourceKey
109
+
110
+ // Legacy Story-4 ASNs keyed only by referenceNumber = goodsReceiptId (no source_key).
111
+ return findOneWithDecryption(
112
+ em,
113
+ Asn,
114
+ {
115
+ organizationId: scope.organizationId,
116
+ tenantId: scope.tenantId,
117
+ referenceNumber: goodsReceiptId,
118
+ deletedAt: null,
119
+ },
120
+ undefined,
121
+ scope,
122
+ )
123
+ }
124
+
125
+ export default async function handle(payload: GoodsReceiptCreatedPayload, ctx: SubscriberContext) {
126
+ const tenantId = asUuid(payload.tenantId)
127
+ const organizationId = asUuid(payload.organizationId)
128
+ if (!tenantId || !organizationId) return
129
+
130
+ const featureTogglesService = tryResolve<FeatureTogglesService>(ctx, 'featureTogglesService')
131
+ const emRoot = tryResolve<EntityManager>(ctx, 'em')
132
+ const commandBus = tryResolve<CommandBus>(ctx, 'commandBus')
133
+ if (!featureTogglesService || !emRoot || !commandBus) return
134
+
135
+ const enabled = await resolveWmsIntegrationToggleEnabled(
136
+ featureTogglesService,
137
+ emRoot,
138
+ 'wms_integration_procurement_goods_receipt',
139
+ tenantId,
140
+ )
141
+ if (!enabled) return
142
+
143
+ const warehouseId = asUuid(payload.warehouseId)
144
+ if (!warehouseId) {
145
+ logger.debug('procurement goods receipt skipped: missing warehouseId', {
146
+ goodsReceiptId: payload.id ?? payload.goodsReceiptId ?? null,
147
+ })
148
+ return
149
+ }
150
+
151
+ const goodsReceiptId = asUuid(payload.goodsReceiptId) ?? asUuid(payload.id)
152
+ if (!goodsReceiptId) {
153
+ logger.debug('procurement goods receipt skipped: missing goodsReceiptId')
154
+ return
155
+ }
156
+
157
+ const sourceKey = buildProcurementGoodsReceiptSourceKey(goodsReceiptId)
158
+ const referenceNumber =
159
+ (typeof payload.referenceNumber === 'string' && payload.referenceNumber.trim().length > 0
160
+ ? payload.referenceNumber.trim()
161
+ : null) ?? goodsReceiptId
162
+
163
+ const em = emRoot.fork()
164
+ const scope = { organizationId, tenantId }
165
+ // Match by sourceKey (any status) so retries never create a second ASN.
166
+ const existing = await findExistingAsnForGoodsReceipt(em, scope, goodsReceiptId, sourceKey)
167
+ if (existing) return
168
+
169
+ const expectedAt =
170
+ payload.expectedAt instanceof Date
171
+ ? payload.expectedAt
172
+ : typeof payload.expectedAt === 'string' && payload.expectedAt.trim().length > 0
173
+ ? new Date(payload.expectedAt)
174
+ : new Date()
175
+
176
+ const lines = (payload.lines ?? [])
177
+ .map((line) => {
178
+ const catalogVariantId = asUuid(line.catalogVariantId)
179
+ const expectedQty = toPositiveQuantity(line.expectedQty)
180
+ if (!catalogVariantId || expectedQty == null) return null
181
+ return {
182
+ catalogVariantId,
183
+ expectedQty,
184
+ lotNumber:
185
+ typeof line.lotNumber === 'string' && line.lotNumber.trim().length > 0
186
+ ? line.lotNumber.trim()
187
+ : null,
188
+ }
189
+ })
190
+ .filter((line): line is NonNullable<typeof line> => line !== null)
191
+
192
+ const commandCtx: CommandRuntimeContext = {
193
+ container: { resolve: ctx.resolve } as CommandRuntimeContext['container'],
194
+ auth: null,
195
+ organizationScope: null,
196
+ selectedOrganizationId: organizationId,
197
+ organizationIds: [organizationId],
198
+ }
199
+
200
+ const { translate } = await resolveTranslations()
201
+ const notes = translate(
202
+ 'wms.subscribers.procurementGoodsReceipt.asnNotes',
203
+ 'Created from procurement goods receipt {goodsReceiptId}',
204
+ { goodsReceiptId },
205
+ )
206
+
207
+ try {
208
+ await commandBus.execute('wms.asns.create', {
209
+ input: {
210
+ organizationId,
211
+ tenantId,
212
+ warehouseId,
213
+ vendorId: asUuid(payload.vendorId),
214
+ status: 'draft',
215
+ expectedAt,
216
+ referenceNumber,
217
+ sourceKey,
218
+ notes,
219
+ lines,
220
+ metadata: {
221
+ source: 'procurement.goods_receipt.created',
222
+ goodsReceiptId,
223
+ },
224
+ },
225
+ ctx: commandCtx,
226
+ })
227
+ } catch (err) {
228
+ // Concurrent duplicate events: unique (organization_id, source_key) wins; loser is idempotent.
229
+ if (isUniqueConstraintError(err)) return
230
+ logger.error('procurement goods receipt ASN create failed', { err, goodsReceiptId })
231
+ getTelemetryRuntime()?.reportError(err, {
232
+ module: 'wms',
233
+ code: 'wms.procurement_goods_receipt_asn_create_failed',
234
+ attributes: { goodsReceiptId },
235
+ })
236
+ throw err
237
+ }
238
+ }
@@ -0,0 +1,21 @@
1
+ import { reevaluateReservationsAfterStockIncrease } from '../lib/salesOrderInventoryAutomation'
2
+
3
+ export const metadata = {
4
+ event: 'wms.putaway.completed',
5
+ persistent: true,
6
+ id: 'wms:putaway-completed-reevaluate-reservations',
7
+ }
8
+
9
+ type SubscriberContext = {
10
+ resolve: <T = unknown>(name: string) => T
11
+ }
12
+
13
+ type PutawayCompletedPayload = {
14
+ catalogVariantId?: string | null
15
+ tenantId?: string | null
16
+ organizationId?: string | null
17
+ }
18
+
19
+ export default async function handle(payload: PutawayCompletedPayload, ctx: SubscriberContext) {
20
+ await reevaluateReservationsAfterStockIncrease(payload, ctx)
21
+ }
@@ -42,6 +42,16 @@ type SalesOrderWmsPayload = {
42
42
  reservationSummary?: {
43
43
  status?: ReservationStatus
44
44
  reservationIds?: string[]
45
+ reservations?: Array<{
46
+ id?: string
47
+ reservationLabel?: string
48
+ catalogVariantId?: string
49
+ warehouseId?: string | null
50
+ warehouseName?: string | null
51
+ quantity?: string
52
+ variantName?: string | null
53
+ variantSku?: string | null
54
+ }>
45
55
  }
46
56
  }
47
57
 
@@ -113,6 +123,51 @@ export default function SalesOrderStockContextWidget(
113
123
  [wms?.stockSummary],
114
124
  )
115
125
 
126
+ const activeReservations = React.useMemo(() => {
127
+ const enriched = Array.isArray(wms?.reservationSummary?.reservations)
128
+ ? (wms.reservationSummary?.reservations ?? [])
129
+ : []
130
+ const fromEnriched = enriched
131
+ .map((item) => {
132
+ const id = typeof item?.id === 'string' ? item.id : null
133
+ if (!id) return null
134
+ const reservationLabel =
135
+ typeof item.reservationLabel === 'string' && item.reservationLabel.trim().length > 0
136
+ ? item.reservationLabel.trim()
137
+ : id
138
+ return {
139
+ id,
140
+ reservationLabel,
141
+ warehouse_name: item.warehouseName ?? null,
142
+ variant_name: item.variantName ?? null,
143
+ variant_sku: item.variantSku ?? null,
144
+ quantity: item.quantity ?? null,
145
+ }
146
+ })
147
+ .filter((item): item is NonNullable<typeof item> => item !== null)
148
+ if (fromEnriched.length > 0) return fromEnriched
149
+
150
+ const ids = Array.isArray(wms?.reservationSummary?.reservationIds)
151
+ ? (wms.reservationSummary?.reservationIds ?? [])
152
+ : []
153
+ return ids
154
+ .filter((id): id is string => typeof id === 'string' && id.length > 0)
155
+ .map((id) => ({
156
+ id,
157
+ reservationLabel: id,
158
+ warehouse_name: null,
159
+ variant_name: null,
160
+ variant_sku: null,
161
+ quantity: null,
162
+ }))
163
+ }, [wms?.reservationSummary?.reservationIds, wms?.reservationSummary?.reservations])
164
+
165
+ const releaseReservation = React.useMemo(() => {
166
+ if (!releaseReservationId) return null
167
+ const match = activeReservations.find((item) => item.id === releaseReservationId)
168
+ return match ?? { id: releaseReservationId }
169
+ }, [activeReservations, releaseReservationId])
170
+
116
171
  React.useEffect(() => {
117
172
  const variantIds = stockSummary
118
173
  .map((item) => item.catalogVariantId)
@@ -255,13 +310,6 @@ export default function SalesOrderStockContextWidget(
255
310
  if (!wms) return null
256
311
 
257
312
  const reservationStatus = wms.reservationSummary?.status ?? 'unreserved'
258
- const reservationIds = Array.isArray(wms.reservationSummary?.reservationIds)
259
- ? (wms.reservationSummary?.reservationIds ?? [])
260
- : []
261
-
262
- const releaseReservation = releaseReservationId
263
- ? { id: releaseReservationId }
264
- : null
265
313
 
266
314
  return (
267
315
  <div className="rounded-lg border bg-card px-4 py-3 space-y-4">
@@ -344,7 +392,7 @@ export default function SalesOrderStockContextWidget(
344
392
  'Reservation records',
345
393
  )}
346
394
  </dt>
347
- <dd className="font-medium">{reservationIds.length}</dd>
395
+ <dd className="font-medium">{activeReservations.length}</dd>
348
396
  </div>
349
397
  </dl>
350
398
 
@@ -398,7 +446,7 @@ export default function SalesOrderStockContextWidget(
398
446
  </p>
399
447
  )}
400
448
 
401
- {access.canRelease && reservationIds.length > 0 ? (
449
+ {access.canRelease && activeReservations.length > 0 ? (
402
450
  <div className="space-y-2">
403
451
  <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
404
452
  {t(
@@ -407,18 +455,20 @@ export default function SalesOrderStockContextWidget(
407
455
  )}
408
456
  </p>
409
457
  <div className="space-y-1">
410
- {reservationIds.map((rid) => (
458
+ {activeReservations.map((reservation) => (
411
459
  <div
412
- key={rid}
460
+ key={reservation.id}
413
461
  className="flex items-center justify-between gap-2 rounded-md border border-border/70 bg-background px-3 py-1.5"
414
462
  >
415
- <span className="font-mono text-xs text-muted-foreground truncate">{rid}</span>
463
+ <span className="text-xs text-foreground truncate" title={reservation.reservationLabel}>
464
+ {reservation.reservationLabel}
465
+ </span>
416
466
  <Button
417
467
  type="button"
418
468
  variant="ghost"
419
469
  size="sm"
420
470
  className="h-6 shrink-0 px-2 text-xs text-status-error-fg hover:text-status-error-fg"
421
- onClick={() => setReleaseReservationId(rid)}
471
+ onClick={() => setReleaseReservationId(reservation.id)}
422
472
  >
423
473
  <ShieldOff className="size-3 mr-1" />
424
474
  {t('wms.widgets.sales.stockContext.releaseBtn', 'Release')}