@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,677 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import Link from 'next/link'
5
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
6
+ import type { SortingState } from '@tanstack/react-table'
7
+ import { useQuery, useQueryClient } from '@tanstack/react-query'
8
+ import { ArrowLeftRight, Package } from 'lucide-react'
9
+ import { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'
10
+ import { DataTable } from '@open-mercato/ui/backend/DataTable'
11
+ import { EmptyState } from '@open-mercato/ui/backend/EmptyState'
12
+ import { RowActions } from '@open-mercato/ui/backend/RowActions'
13
+ import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
14
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
15
+ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
16
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
17
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
18
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
19
+ import { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'
20
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
21
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
22
+ import { Button } from '@open-mercato/ui/primitives/button'
23
+ import {
24
+ Select,
25
+ SelectContent,
26
+ SelectItem,
27
+ SelectTrigger,
28
+ SelectValue,
29
+ } from '@open-mercato/ui/primitives/select'
30
+ import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
31
+ import { E } from '#generated/entities.ids.generated'
32
+ import { extensionPoints } from '@open-mercato/core/modules/wms/extension-points'
33
+ import { flashMutationError } from '../../lib/flashMutationError'
34
+ import {
35
+ AssignPutawayTaskDialog,
36
+ type PutawayAssignTarget,
37
+ } from './AssignPutawayTaskDialog'
38
+ import {
39
+ CompletePutawayTaskDialog,
40
+ type PutawayTaskTarget,
41
+ } from './CompletePutawayTaskDialog'
42
+ import {
43
+ canShowPutawayCompleteAction,
44
+ formatAgingLabel,
45
+ mapPutawayQueueSortDir,
46
+ mapPutawayQueueSortField,
47
+ putawayStatusVariant,
48
+ } from './inboundStatusUi'
49
+ import { applyPutawayLockTokenFromConflict } from './putawayQueueOptimisticLock'
50
+ import { MoveInventoryDialog } from './MoveInventoryDialog'
51
+ import { useWmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'
52
+ import {
53
+ resolveCatalogVariantLabel,
54
+ resolveLocationLabel,
55
+ } from './inventoryMutationLoaders'
56
+
57
+ type PutawayTaskRow = {
58
+ id: string
59
+ warehouse_id?: string | null
60
+ source_location_id?: string | null
61
+ target_location_id?: string | null
62
+ catalog_variant_id?: string | null
63
+ lot_id?: string | null
64
+ serial_number?: string | null
65
+ quantity?: string | number | null
66
+ status?: string | null
67
+ assigned_to?: string | null
68
+ priority?: number | null
69
+ created_at?: string | null
70
+ updated_at?: string | null
71
+ }
72
+
73
+ type PagedResponse<T> = {
74
+ items: T[]
75
+ total: number
76
+ totalPages: number
77
+ totalIsCapped?: boolean
78
+ }
79
+
80
+ function toNumber(value: string | number | null | undefined): number {
81
+ const parsed = Number(value ?? 0)
82
+ return Number.isFinite(parsed) ? parsed : 0
83
+ }
84
+
85
+ export default function WmsPutawayQueuePage() {
86
+ const t = useT()
87
+ const access = useWmsInventoryMutationAccess()
88
+ const queryClient = useQueryClient()
89
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
90
+ const { runMutation, retryLastMutation } = useGuardedMutation<{
91
+ retryLastMutation: () => Promise<boolean>
92
+ }>({
93
+ contextId: 'wms-putaway-queue',
94
+ })
95
+ const mutationContext = React.useMemo(
96
+ () => ({ retryLastMutation }),
97
+ [retryLastMutation],
98
+ )
99
+
100
+ const [page, setPage] = React.useState(1)
101
+ const [statusFilter, setStatusFilter] = React.useState<'active' | 'open' | 'in_progress' | 'done' | 'cancelled' | 'all'>(
102
+ 'active',
103
+ )
104
+ const [sorting, setSorting] = React.useState<SortingState>([{ id: 'createdAt', desc: false }])
105
+ const [completeOpen, setCompleteOpen] = React.useState(false)
106
+ const [activeTask, setActiveTask] = React.useState<PutawayTaskTarget | null>(null)
107
+ const [assignOpen, setAssignOpen] = React.useState(false)
108
+ const [assignTask, setAssignTask] = React.useState<PutawayAssignTarget | null>(null)
109
+ const [manualPutawayOpen, setManualPutawayOpen] = React.useState(false)
110
+ const [labelCache, setLabelCache] = React.useState<Record<string, string>>({})
111
+ const [nowMs, setNowMs] = React.useState(() => Date.now())
112
+ /** Fresh If-Match tokens after 409; ref so guarded retry reads latest without re-seed. */
113
+ const lockTokensRef = React.useRef<Record<string, string>>({})
114
+ const [lockOverrides, setLockOverrides] = React.useState<Record<string, string>>({})
115
+
116
+ React.useEffect(() => {
117
+ const timer = window.setInterval(() => setNowMs(Date.now()), 60_000)
118
+ return () => window.clearInterval(timer)
119
+ }, [])
120
+
121
+ const queryKey = React.useMemo(
122
+ () => ['wms-putaway-queue', page, statusFilter, sorting],
123
+ [page, sorting, statusFilter],
124
+ )
125
+
126
+ const tasksQuery = useQuery({
127
+ queryKey,
128
+ queryFn: async () => {
129
+ const sortCol = sorting[0]
130
+ const base = {
131
+ page: String(page),
132
+ pageSize: '50',
133
+ sortField: sortCol ? mapPutawayQueueSortField(sortCol.id) : 'createdAt',
134
+ sortDir: sortCol ? mapPutawayQueueSortDir(sortCol.id, Boolean(sortCol.desc)) : 'asc',
135
+ }
136
+ const load = async (status?: string) => {
137
+ const params = new URLSearchParams(base)
138
+ if (status) params.set('status', status)
139
+ const call = await apiCall<PagedResponse<PutawayTaskRow>>(
140
+ `/api/wms/putaway-tasks?${params.toString()}`,
141
+ )
142
+ if (!call.ok) {
143
+ await raiseCrudError(
144
+ call.response,
145
+ t('wms.backend.putaway.errors.load', 'Failed to load putaway tasks.'),
146
+ )
147
+ }
148
+ return call.result ?? { items: [], total: 0, totalPages: 1 }
149
+ }
150
+
151
+ // Active queue: single server-side multi-status page (not two truncated first pages).
152
+ if (statusFilter === 'active') return load('open,in_progress')
153
+ if (statusFilter === 'all') return load()
154
+ return load(statusFilter)
155
+ },
156
+ })
157
+
158
+ React.useEffect(() => {
159
+ const ids = new Set<string>()
160
+ for (const row of tasksQuery.data?.items ?? []) {
161
+ if (row.catalog_variant_id) ids.add(row.catalog_variant_id)
162
+ if (row.source_location_id) ids.add(row.source_location_id)
163
+ if (row.target_location_id) ids.add(row.target_location_id)
164
+ }
165
+ const missing = [...ids].filter((id) => !labelCache[id])
166
+ if (missing.length === 0) return
167
+ let cancelled = false
168
+ void Promise.all(
169
+ missing.map(async (id) => {
170
+ const [variant, location] = await Promise.all([
171
+ resolveCatalogVariantLabel(id),
172
+ resolveLocationLabel(id),
173
+ ])
174
+ return [id, variant || location] as const
175
+ }),
176
+ ).then((entries) => {
177
+ if (cancelled) return
178
+ setLabelCache((prev) => {
179
+ const next = { ...prev }
180
+ for (const [id, label] of entries) {
181
+ if (label) next[id] = label
182
+ }
183
+ return next
184
+ })
185
+ })
186
+ return () => {
187
+ cancelled = true
188
+ }
189
+ }, [labelCache, tasksQuery.data?.items])
190
+
191
+ const applyLockTokenFromConflict = React.useCallback(
192
+ (taskId: string, status: number, body: unknown) => {
193
+ const refreshed = applyPutawayLockTokenFromConflict(lockTokensRef.current, taskId, {
194
+ status,
195
+ body,
196
+ })
197
+ if (!refreshed) return
198
+ setLockOverrides((prev) => ({ ...prev, [taskId]: refreshed }))
199
+ queryClient.setQueriesData<PagedResponse<PutawayTaskRow>>(
200
+ { queryKey: ['wms-putaway-queue'] },
201
+ (previous) => {
202
+ if (!previous?.items) return previous
203
+ return {
204
+ ...previous,
205
+ items: previous.items.map((row) =>
206
+ row.id === taskId ? { ...row, updated_at: refreshed } : row,
207
+ ),
208
+ }
209
+ },
210
+ )
211
+ },
212
+ [queryClient],
213
+ )
214
+
215
+ const postTaskAction = React.useCallback(
216
+ async (
217
+ taskId: string,
218
+ action: 'assign' | 'start' | 'cancel',
219
+ body: Record<string, unknown>,
220
+ updatedAt?: string | null,
221
+ ): Promise<'ok' | 'conflict'> => {
222
+ // Seed once per user action (outside operation) so guarded retry keeps the
223
+ // refreshed token after a 409 instead of re-binding the original row version.
224
+ if (typeof updatedAt === 'string' && updatedAt.trim()) {
225
+ lockTokensRef.current[taskId] = updatedAt.trim()
226
+ }
227
+ let conflictHandled = false
228
+ await runMutation({
229
+ operation: async () => {
230
+ const call = await apiCall<{ ok?: boolean }>(
231
+ `/api/wms/putaway-tasks/${encodeURIComponent(taskId)}/${action}`,
232
+ {
233
+ method: 'POST',
234
+ headers: {
235
+ 'content-type': 'application/json',
236
+ ...buildOptimisticLockHeader(lockTokensRef.current[taskId]),
237
+ },
238
+ body: JSON.stringify(body),
239
+ },
240
+ )
241
+ if (!call.ok) {
242
+ const refreshLockFromConflict = () => {
243
+ applyLockTokenFromConflict(taskId, call.status, call.result)
244
+ void queryClient.invalidateQueries({ queryKey: ['wms-putaway-queue'] })
245
+ }
246
+ if (
247
+ surfaceRecordConflict({ status: call.status, body: call.result }, t, {
248
+ onRefresh: refreshLockFromConflict,
249
+ })
250
+ ) {
251
+ refreshLockFromConflict()
252
+ conflictHandled = true
253
+ return {}
254
+ }
255
+ await raiseCrudError(
256
+ call.response,
257
+ t('wms.backend.putaway.errors.action', 'Failed to update putaway task.'),
258
+ )
259
+ }
260
+ return call.result ?? {}
261
+ },
262
+ context: mutationContext,
263
+ mutationPayload: body,
264
+ })
265
+ if (conflictHandled) return 'conflict'
266
+ await queryClient.invalidateQueries({ queryKey: ['wms-putaway-queue'] })
267
+ return 'ok'
268
+ },
269
+ [applyLockTokenFromConflict, mutationContext, queryClient, runMutation, t],
270
+ )
271
+
272
+ const resolveRowUpdatedAt = React.useCallback(
273
+ (row: PutawayTaskRow) => lockOverrides[row.id] ?? row.updated_at ?? null,
274
+ [lockOverrides],
275
+ )
276
+
277
+ const handleAssignSelf = React.useCallback(
278
+ async (row: PutawayTaskRow) => {
279
+ if (!access.userId || !access.organizationId || !access.tenantId) return
280
+ try {
281
+ const result = await postTaskAction(
282
+ row.id,
283
+ 'assign',
284
+ {
285
+ organizationId: access.organizationId,
286
+ tenantId: access.tenantId,
287
+ assignedTo: access.userId,
288
+ },
289
+ resolveRowUpdatedAt(row),
290
+ )
291
+ if (result === 'conflict') return
292
+ flash(t('wms.backend.putaway.flash.assigned', 'Putaway task assigned'), 'success')
293
+ } catch (error) {
294
+ flashMutationError(error, t('wms.backend.putaway.errors.action', 'Failed to update putaway task.'), t)
295
+ }
296
+ },
297
+ [access, postTaskAction, resolveRowUpdatedAt, t],
298
+ )
299
+
300
+ const handleStart = React.useCallback(
301
+ async (row: PutawayTaskRow) => {
302
+ if (!access.organizationId || !access.tenantId) return
303
+ try {
304
+ const result = await postTaskAction(
305
+ row.id,
306
+ 'start',
307
+ {
308
+ organizationId: access.organizationId,
309
+ tenantId: access.tenantId,
310
+ },
311
+ resolveRowUpdatedAt(row),
312
+ )
313
+ if (result === 'conflict') return
314
+ flash(t('wms.backend.putaway.flash.started', 'Putaway task started'), 'success')
315
+ } catch (error) {
316
+ flashMutationError(error, t('wms.backend.putaway.errors.action', 'Failed to update putaway task.'), t)
317
+ }
318
+ },
319
+ [access, postTaskAction, resolveRowUpdatedAt, t],
320
+ )
321
+
322
+ const handleCancel = React.useCallback(
323
+ async (row: PutawayTaskRow) => {
324
+ if (!access.organizationId || !access.tenantId) return
325
+ const confirmed = await confirm({
326
+ title: t('wms.backend.putaway.cancel.confirm.title', 'Cancel putaway task?'),
327
+ description: t(
328
+ 'wms.backend.putaway.cancel.confirm.description',
329
+ 'The task will be cancelled and will no longer appear in the active queue.',
330
+ ),
331
+ confirmText: t('wms.backend.putaway.cancel.confirm.submit', 'Cancel task'),
332
+ cancelText: t('wms.backend.putaway.cancel.confirm.keep', 'Keep task'),
333
+ variant: 'destructive',
334
+ })
335
+ if (!confirmed) return
336
+ try {
337
+ const result = await postTaskAction(
338
+ row.id,
339
+ 'cancel',
340
+ {
341
+ organizationId: access.organizationId,
342
+ tenantId: access.tenantId,
343
+ },
344
+ resolveRowUpdatedAt(row),
345
+ )
346
+ if (result === 'conflict') return
347
+ flash(t('wms.backend.putaway.flash.cancelled', 'Putaway task cancelled'), 'success')
348
+ } catch (error) {
349
+ flashMutationError(error, t('wms.backend.putaway.errors.action', 'Failed to update putaway task.'), t)
350
+ }
351
+ },
352
+ [access, confirm, postTaskAction, resolveRowUpdatedAt, t],
353
+ )
354
+
355
+ const openComplete = React.useCallback(
356
+ (row: PutawayTaskRow) => {
357
+ setActiveTask({
358
+ id: row.id,
359
+ warehouseId: row.warehouse_id ?? '',
360
+ quantity: toNumber(row.quantity),
361
+ targetLocationId: row.target_location_id,
362
+ catalogVariantId: row.catalog_variant_id,
363
+ sourceLocationId: row.source_location_id,
364
+ serialNumber: row.serial_number,
365
+ updatedAt: resolveRowUpdatedAt(row),
366
+ })
367
+ setCompleteOpen(true)
368
+ },
369
+ [resolveRowUpdatedAt],
370
+ )
371
+
372
+ const openAssign = React.useCallback(
373
+ (row: PutawayTaskRow) => {
374
+ setAssignTask({
375
+ id: row.id,
376
+ assignedTo: row.assigned_to,
377
+ updatedAt: resolveRowUpdatedAt(row),
378
+ })
379
+ setAssignOpen(true)
380
+ },
381
+ [resolveRowUpdatedAt],
382
+ )
383
+
384
+ const columns = React.useMemo<ColumnDef<PutawayTaskRow>[]>(
385
+ () => [
386
+ {
387
+ id: 'status',
388
+ accessorKey: 'status',
389
+ header: t('wms.backend.putaway.columns.status', 'Status'),
390
+ enableSorting: true,
391
+ cell: ({ row }) => {
392
+ const status = row.original.status?.trim()
393
+ if (!status) return '—'
394
+ return (
395
+ <StatusBadge variant={putawayStatusVariant(status)} dot>
396
+ {t(`wms.backend.putaway.status.${status}`, status)}
397
+ </StatusBadge>
398
+ )
399
+ },
400
+ },
401
+ {
402
+ id: 'variant',
403
+ header: t('wms.backend.putaway.columns.variant', 'Variant'),
404
+ cell: ({ row }) => {
405
+ const id = row.original.catalog_variant_id
406
+ if (!id) return '—'
407
+ return labelCache[id] || id.slice(0, 8)
408
+ },
409
+ },
410
+ {
411
+ id: 'quantity',
412
+ accessorKey: 'quantity',
413
+ header: t('wms.backend.putaway.columns.quantity', 'Qty'),
414
+ enableSorting: true,
415
+ cell: ({ row }) => toNumber(row.original.quantity),
416
+ },
417
+ {
418
+ id: 'serial',
419
+ accessorKey: 'serial_number',
420
+ header: t('wms.backend.putaway.columns.serial', 'Serial'),
421
+ cell: ({ row }) => {
422
+ const serial = row.original.serial_number?.trim()
423
+ if (!serial) return '—'
424
+ return serial
425
+ },
426
+ },
427
+ {
428
+ id: 'source',
429
+ header: t('wms.backend.putaway.columns.source', 'Source'),
430
+ cell: ({ row }) => {
431
+ const id = row.original.source_location_id
432
+ if (!id) return '—'
433
+ return labelCache[id] || id.slice(0, 8)
434
+ },
435
+ },
436
+ {
437
+ id: 'target',
438
+ header: t('wms.backend.putaway.columns.target', 'Target'),
439
+ cell: ({ row }) => {
440
+ const id = row.original.target_location_id
441
+ if (!id) return '—'
442
+ return labelCache[id] || id.slice(0, 8)
443
+ },
444
+ },
445
+ {
446
+ id: 'assignee',
447
+ header: t('wms.backend.putaway.columns.assignee', 'Assignee'),
448
+ cell: ({ row }) => {
449
+ const id = row.original.assigned_to
450
+ if (!id) return t('wms.backend.putaway.assignee.unassigned', 'Unassigned')
451
+ if (id === access.userId) return t('wms.backend.putaway.assignee.me', 'Me')
452
+ return id.slice(0, 8)
453
+ },
454
+ },
455
+ {
456
+ id: 'aging',
457
+ accessorKey: 'created_at',
458
+ header: t('wms.backend.putaway.columns.aging', 'Aging'),
459
+ enableSorting: true,
460
+ cell: ({ row }) => formatAgingLabel(row.original.created_at, nowMs) ?? '—',
461
+ },
462
+ ],
463
+ [access.userId, labelCache, nowMs, t],
464
+ )
465
+
466
+ const rowActions = React.useCallback(
467
+ (row: PutawayTaskRow) => {
468
+ const status = row.status
469
+ if (status !== 'open' && status !== 'in_progress') return null
470
+
471
+ const isAssignee = Boolean(access.userId && row.assigned_to === access.userId)
472
+ const canComplete = canShowPutawayCompleteAction({
473
+ canManagePutaway: access.canManagePutaway,
474
+ canAdjustInventory: access.canAdjust,
475
+ isAssignee,
476
+ })
477
+ if (!access.canManagePutaway && !canComplete) return null
478
+
479
+ const items = []
480
+ if (access.canManagePutaway) {
481
+ items.push({
482
+ id: 'assign',
483
+ label: t('wms.backend.putaway.actions.assign', 'Assign…'),
484
+ onSelect: () => openAssign(row),
485
+ })
486
+ if (!row.assigned_to || row.assigned_to !== access.userId) {
487
+ items.push({
488
+ id: 'assign-me',
489
+ label: t('wms.backend.putaway.actions.assignMe', 'Assign to me'),
490
+ onSelect: () => void handleAssignSelf(row),
491
+ })
492
+ }
493
+ if (status === 'open') {
494
+ items.push({
495
+ id: 'start',
496
+ label: t('wms.backend.putaway.actions.start', 'Start'),
497
+ onSelect: () => void handleStart(row),
498
+ })
499
+ }
500
+ }
501
+ if (canComplete) {
502
+ items.push({
503
+ id: 'complete',
504
+ label: t('wms.backend.putaway.actions.complete', 'Complete'),
505
+ onSelect: () => openComplete(row),
506
+ })
507
+ }
508
+ if (access.canManagePutaway) {
509
+ items.push({
510
+ id: 'cancel',
511
+ label: t('wms.backend.putaway.actions.cancel', 'Cancel'),
512
+ onSelect: () => void handleCancel(row),
513
+ })
514
+ }
515
+ if (items.length === 0) return null
516
+ return <RowActions items={items} />
517
+ },
518
+ [
519
+ access.canAdjust,
520
+ access.canManagePutaway,
521
+ access.userId,
522
+ handleAssignSelf,
523
+ handleCancel,
524
+ handleStart,
525
+ openAssign,
526
+ openComplete,
527
+ t,
528
+ ],
529
+ )
530
+
531
+ return (
532
+ <Page>
533
+ <PageBody className="space-y-6">
534
+ <PageHeader
535
+ title={t('wms.backend.putaway.title', 'Putaway queue')}
536
+ description={t(
537
+ 'wms.backend.putaway.description',
538
+ 'Open and in-progress putaway tasks from accepted receipts.',
539
+ )}
540
+ actions={
541
+ <div className="flex flex-wrap gap-2">
542
+ <Button type="button" variant="outline" asChild>
543
+ <Link href="/backend/wms/asns">{t('wms.backend.putaway.actions.asns', 'ASNs')}</Link>
544
+ </Button>
545
+ {access.canMove ? (
546
+ <Button type="button" variant="outline" onClick={() => setManualPutawayOpen(true)}>
547
+ <ArrowLeftRight className="size-4" />
548
+ {t('wms.backend.putaway.actions.manualPutaway', 'Manual put away')}
549
+ </Button>
550
+ ) : null}
551
+ </div>
552
+ }
553
+ />
554
+
555
+ {tasksQuery.isLoading ? (
556
+ <LoadingMessage label={t('wms.backend.putaway.loading', 'Loading putaway tasks…')} />
557
+ ) : null}
558
+ {tasksQuery.isError ? (
559
+ <ErrorMessage label={t('wms.backend.putaway.errors.load', 'Failed to load putaway tasks.')} />
560
+ ) : null}
561
+
562
+ {tasksQuery.data ? (
563
+ <section className="rounded-lg border bg-card p-5 text-card-foreground shadow-sm">
564
+ <div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
565
+ <div className="flex items-start gap-3">
566
+ <div className="rounded-md border bg-muted/40 p-2 text-muted-foreground">
567
+ <Package className="size-5" />
568
+ </div>
569
+ <div className="space-y-1">
570
+ <h2 className="text-xl font-semibold">
571
+ {t('wms.backend.putaway.section.title', 'Tasks')}
572
+ </h2>
573
+ <p className="text-sm text-muted-foreground">
574
+ {t(
575
+ 'wms.backend.putaway.section.description',
576
+ 'Filter and complete open or in-progress putaway work.',
577
+ )}
578
+ </p>
579
+ </div>
580
+ </div>
581
+ <Select
582
+ value={statusFilter}
583
+ onValueChange={(value) => {
584
+ setPage(1)
585
+ setStatusFilter(value as typeof statusFilter)
586
+ }}
587
+ >
588
+ <SelectTrigger className="w-[200px]" data-testid="putaway-status-filter">
589
+ <SelectValue />
590
+ </SelectTrigger>
591
+ <SelectContent>
592
+ <SelectItem value="active">
593
+ {t('wms.backend.putaway.filters.active', 'Open + in progress')}
594
+ </SelectItem>
595
+ <SelectItem value="open">{t('wms.backend.putaway.status.open', 'Open')}</SelectItem>
596
+ <SelectItem value="in_progress">
597
+ {t('wms.backend.putaway.status.in_progress', 'In progress')}
598
+ </SelectItem>
599
+ <SelectItem value="done">{t('wms.backend.putaway.status.done', 'Done')}</SelectItem>
600
+ <SelectItem value="cancelled">
601
+ {t('wms.backend.putaway.status.cancelled', 'Cancelled')}
602
+ </SelectItem>
603
+ <SelectItem value="all">{t('wms.backend.putaway.filters.all', 'All statuses')}</SelectItem>
604
+ </SelectContent>
605
+ </Select>
606
+ </div>
607
+
608
+ <DataTable
609
+ embedded
610
+ columns={columns}
611
+ data={tasksQuery.data.items}
612
+ isLoading={tasksQuery.isFetching}
613
+ entityId={E.wms.putaway_task}
614
+ sortable
615
+ manualSorting
616
+ sorting={sorting}
617
+ onSortingChange={(next) => {
618
+ setSorting(next)
619
+ setPage(1)
620
+ }}
621
+ rowActions={rowActions}
622
+ pagination={{
623
+ page,
624
+ pageSize: 50,
625
+ total: tasksQuery.data.total,
626
+ totalPages: tasksQuery.data.totalPages,
627
+ totalIsCapped: tasksQuery.data?.totalIsCapped === true,
628
+ onPageChange: setPage,
629
+ }}
630
+ perspective={{ tableId: extensionPoints.hosts.putawayTasksTable.tableId }}
631
+ emptyState={
632
+ <EmptyState
633
+ title={t('wms.backend.putaway.empty.title', 'No putaway tasks')}
634
+ description={t(
635
+ 'wms.backend.putaway.empty.description',
636
+ 'Tasks appear after ASN lines are received with QC pass.',
637
+ )}
638
+ />
639
+ }
640
+ />
641
+ </section>
642
+ ) : null}
643
+ </PageBody>
644
+
645
+ {access.canManagePutaway ? (
646
+ <AssignPutawayTaskDialog
647
+ open={assignOpen}
648
+ onOpenChange={setAssignOpen}
649
+ access={access}
650
+ task={assignTask}
651
+ onConflict={(taskId, status, body) => {
652
+ applyLockTokenFromConflict(taskId, status, body)
653
+ }}
654
+ onSuccess={() => void tasksQuery.refetch()}
655
+ />
656
+ ) : null}
657
+ {access.canAdjust ? (
658
+ <CompletePutawayTaskDialog
659
+ open={completeOpen}
660
+ onOpenChange={setCompleteOpen}
661
+ access={access}
662
+ task={activeTask}
663
+ onSuccess={() => void tasksQuery.refetch()}
664
+ />
665
+ ) : null}
666
+ {access.canMove ? (
667
+ <MoveInventoryDialog
668
+ open={manualPutawayOpen}
669
+ onOpenChange={setManualPutawayOpen}
670
+ access={access}
671
+ movementType="putaway"
672
+ />
673
+ ) : null}
674
+ {ConfirmDialogElement}
675
+ </Page>
676
+ )
677
+ }
@@ -38,7 +38,7 @@ import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
38
38
  import { cn } from '@open-mercato/shared/lib/utils'
39
39
  import { E } from '#generated/entities.ids.generated'
40
40
  import {
41
- inventoryMovementReasonLabel,
41
+ formatInventoryMovementActivitySubtitle,
42
42
  type InventoryDisplayTranslator,
43
43
  } from '../../lib/inventoryDisplayUi'
44
44
  import { AdjustInventoryDialog } from './AdjustInventoryDialog'
@@ -338,17 +338,16 @@ function formatMovementSubtitle(
338
338
  row: InventoryMovementRow,
339
339
  t: InventoryDisplayTranslator,
340
340
  ): string | null {
341
- const reasonLabel = inventoryMovementReasonLabel(
341
+ return formatInventoryMovementActivitySubtitle(
342
342
  {
343
343
  reasonCode: row.reason_code,
344
344
  reason: row.reason,
345
345
  movementType: row.type,
346
+ referenceType: row.reference_type,
347
+ referenceId: row.reference_id,
346
348
  },
347
349
  t,
348
350
  )
349
- if (reasonLabel) return reasonLabel
350
- if (row.reference_type && row.reference_id) return `${row.reference_type} · ${row.reference_id}`
351
- return null
352
351
  }
353
352
 
354
353
  function formatMovementLocation(row: InventoryMovementRow): string {