@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,66 @@
1
+ import { z } from 'zod'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
3
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
4
+ import { putawayTaskCompleteSchema } from '../../../../data/validators'
5
+ import { executeWmsCustomPostRoute } from '../../../inventory/helpers'
6
+
7
+ export const metadata = {
8
+ // Floor ACL: stock-moving operators (`wms.adjust_inventory`) and managers both hold this.
9
+ // Command also allows `wms.manage_putaway` for any task, or adjust + assignee match.
10
+ POST: { requireAuth: true, requireFeatures: ['wms.adjust_inventory'] },
11
+ }
12
+
13
+ const paramsSchema = z.object({
14
+ id: z.string().uuid(),
15
+ })
16
+
17
+ export async function POST(request: Request, routeContext: { params: { id: string } }) {
18
+ const parsedParams = paramsSchema.parse(routeContext.params)
19
+ const body = await readJsonSafe<Record<string, unknown>>(request, {})
20
+ const scopedRequest = new Request(request.url, {
21
+ method: 'POST',
22
+ headers: request.headers,
23
+ body: JSON.stringify({ ...body, id: parsedParams.id }),
24
+ })
25
+ return executeWmsCustomPostRoute({
26
+ request: scopedRequest,
27
+ routePath: `wms/putaway-tasks/${parsedParams.id}/complete`,
28
+ inputSchema: putawayTaskCompleteSchema,
29
+ commandId: 'wms.putaway-tasks.complete',
30
+ describeResource: (input) => ({
31
+ resourceKind: 'wms.putawayTask',
32
+ resourceId: input.id,
33
+ }),
34
+ mapSuccess: (result: { movementId: string }) => ({
35
+ ok: true,
36
+ movementId: result.movementId,
37
+ }),
38
+ })
39
+ }
40
+
41
+ const successSchema = z.object({
42
+ ok: z.literal(true),
43
+ movementId: z.string().uuid(),
44
+ })
45
+ const errorSchema = z.object({ error: z.string() })
46
+
47
+ export const openApi: OpenApiRouteDoc = {
48
+ tag: 'WMS',
49
+ summary: 'Complete putaway task',
50
+ methods: {
51
+ POST: {
52
+ summary: 'Complete putaway task',
53
+ description:
54
+ 'Confirms putaway quantity and target location, then moves stock staging→storage via an InventoryMovement of type putaway. Requires `wms.adjust_inventory`. Callers with `wms.manage_putaway` may complete any task; operators may complete only when the task is assigned to them.',
55
+ requestBody: { contentType: 'application/json', schema: putawayTaskCompleteSchema },
56
+ responses: [{ status: 200, description: 'Putaway completed', schema: successSchema }],
57
+ errors: [
58
+ { status: 400, description: 'Validation failed', schema: errorSchema },
59
+ { status: 401, description: 'Unauthorized', schema: errorSchema },
60
+ { status: 403, description: 'Forbidden', schema: errorSchema },
61
+ { status: 409, description: 'Invalid putaway state or insufficient stock', schema: errorSchema },
62
+ { status: 422, description: 'Invalid or inactive location', schema: errorSchema },
63
+ ],
64
+ },
65
+ },
66
+ }
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
3
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
4
+ import { putawayTaskStartSchema } from '../../../../data/validators'
5
+ import { executeWmsCustomPostRoute } from '../../../inventory/helpers'
6
+
7
+ export const metadata = {
8
+ POST: { requireAuth: true, requireFeatures: ['wms.manage_putaway'] },
9
+ }
10
+
11
+ const paramsSchema = z.object({
12
+ id: z.string().uuid(),
13
+ })
14
+
15
+ export async function POST(request: Request, routeContext: { params: { id: string } }) {
16
+ const parsedParams = paramsSchema.parse(routeContext.params)
17
+ const body = await readJsonSafe<Record<string, unknown>>(request, {})
18
+ const scopedRequest = new Request(request.url, {
19
+ method: 'POST',
20
+ headers: request.headers,
21
+ body: JSON.stringify({ ...body, id: parsedParams.id }),
22
+ })
23
+ return executeWmsCustomPostRoute({
24
+ request: scopedRequest,
25
+ routePath: `wms/putaway-tasks/${parsedParams.id}/start`,
26
+ inputSchema: putawayTaskStartSchema,
27
+ commandId: 'wms.putaway-tasks.start',
28
+ describeResource: (input) => ({
29
+ resourceKind: 'wms.putawayTask',
30
+ resourceId: input.id,
31
+ }),
32
+ mapSuccess: () => ({ ok: true }),
33
+ })
34
+ }
35
+
36
+ const successSchema = z.object({ ok: z.literal(true) })
37
+ const errorSchema = z.object({ error: z.string() })
38
+
39
+ export const openApi: OpenApiRouteDoc = {
40
+ tag: 'WMS',
41
+ summary: 'Start putaway task',
42
+ methods: {
43
+ POST: {
44
+ summary: 'Start putaway task',
45
+ description: 'Marks a putaway task as in progress.',
46
+ requestBody: { contentType: 'application/json', schema: putawayTaskStartSchema },
47
+ responses: [{ status: 200, description: 'Putaway task started', schema: successSchema }],
48
+ errors: [
49
+ { status: 400, description: 'Validation failed', schema: errorSchema },
50
+ { status: 401, description: 'Unauthorized', schema: errorSchema },
51
+ { status: 403, description: 'Forbidden', schema: errorSchema },
52
+ { status: 409, description: 'Invalid putaway state', schema: errorSchema },
53
+ ],
54
+ },
55
+ },
56
+ }
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
3
+ import { putawayTaskCreateFromBalanceSchema } from '../../../data/validators'
4
+ import { executeWmsCustomPostRoute } from '../../inventory/helpers'
5
+
6
+ export const metadata = {
7
+ POST: { requireAuth: true, requireFeatures: ['wms.manage_putaway'] },
8
+ }
9
+
10
+ export async function POST(request: Request) {
11
+ return executeWmsCustomPostRoute({
12
+ request,
13
+ routePath: 'wms/putaway-tasks/create-from-balance',
14
+ inputSchema: putawayTaskCreateFromBalanceSchema,
15
+ commandId: 'wms.putaway-tasks.create-from-balance',
16
+ describeResource: (input) => ({
17
+ resourceKind: 'wms.putawayTask',
18
+ resourceId: `${input.warehouseId}:${input.sourceLocationId}:${input.catalogVariantId}`,
19
+ }),
20
+ mapSuccess: (result: { taskId: string }) => ({
21
+ ok: true,
22
+ taskId: result.taskId,
23
+ }),
24
+ })
25
+ }
26
+
27
+ const successSchema = z.object({
28
+ ok: z.literal(true),
29
+ taskId: z.string().uuid(),
30
+ })
31
+ const errorSchema = z.object({ error: z.string() })
32
+
33
+ export const openApi: OpenApiRouteDoc = {
34
+ tag: 'WMS',
35
+ summary: 'Create putaway task from balance',
36
+ methods: {
37
+ POST: {
38
+ summary: 'Create putaway task from balance',
39
+ description: 'Manually creates an open putaway task against existing staged stock.',
40
+ requestBody: { contentType: 'application/json', schema: putawayTaskCreateFromBalanceSchema },
41
+ responses: [{ status: 200, description: 'Putaway task created', schema: successSchema }],
42
+ errors: [
43
+ { status: 400, description: 'Validation failed', schema: errorSchema },
44
+ { status: 401, description: 'Unauthorized', schema: errorSchema },
45
+ { status: 403, description: 'Forbidden', schema: errorSchema },
46
+ { status: 422, description: 'Invalid location', schema: errorSchema },
47
+ ],
48
+ },
49
+ },
50
+ }
@@ -0,0 +1,199 @@
1
+ import { z } from 'zod'
2
+ import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
3
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
4
+ import { parseScopedCommandInput, resolveCrudRecordId } from '@open-mercato/shared/lib/api/scoped'
5
+ import { E } from '#generated/entities.ids.generated'
6
+ import { PutawayTask } from '../../data/entities'
7
+ import { putawayTaskCreateSchema, putawayTaskUpdateSchema } from '../../data/validators'
8
+ import {
9
+ assertPutawayLifecycleFieldsForbidden,
10
+ buildPutawayTaskStatusFilter,
11
+ } from '../../lib/putaway'
12
+ import { createPagedListResponseSchema, createWmsCrudOpenApi, defaultOkResponseSchema } from '../openapi'
13
+
14
+ const F = {
15
+ id: 'id',
16
+ organization_id: 'organization_id',
17
+ tenant_id: 'tenant_id',
18
+ warehouse_id: 'warehouse_id',
19
+ source_location_id: 'source_location_id',
20
+ target_location_id: 'target_location_id',
21
+ catalog_variant_id: 'catalog_variant_id',
22
+ lot_id: 'lot_id',
23
+ serial_number: 'serial_number',
24
+ quantity: 'quantity',
25
+ status: 'status',
26
+ assigned_to: 'assigned_to',
27
+ priority: 'priority',
28
+ created_at: 'created_at',
29
+ updated_at: 'updated_at',
30
+ } as const
31
+
32
+ const routeMetadata = {
33
+ GET: { requireAuth: true, requireFeatures: ['wms.view'] },
34
+ POST: { requireAuth: true, requireFeatures: ['wms.manage_putaway'] },
35
+ PUT: { requireAuth: true, requireFeatures: ['wms.manage_putaway'] },
36
+ DELETE: { requireAuth: true, requireFeatures: ['wms.manage_putaway'] },
37
+ }
38
+
39
+ export const metadata = routeMetadata
40
+
41
+ const rawBodySchema = z.object({}).passthrough()
42
+
43
+ const listSchema = z
44
+ .object({
45
+ page: z.coerce.number().min(1).default(1),
46
+ pageSize: z.coerce.number().min(1).max(100).default(25),
47
+ ids: z.string().optional(),
48
+ warehouseId: z.string().uuid().optional(),
49
+ // Single status or comma-separated (e.g. open,in_progress for the active queue).
50
+ status: z.string().optional(),
51
+ assignedTo: z.string().uuid().optional(),
52
+ catalogVariantId: z.string().uuid().optional(),
53
+ sortField: z.string().optional(),
54
+ sortDir: z.enum(['asc', 'desc']).optional(),
55
+ })
56
+ .passthrough()
57
+
58
+ const crud = makeCrudRoute({
59
+ metadata: routeMetadata,
60
+ orm: {
61
+ entity: PutawayTask,
62
+ idField: 'id',
63
+ orgField: 'organizationId',
64
+ tenantField: 'tenantId',
65
+ softDeleteField: 'deletedAt',
66
+ },
67
+ indexer: { entityType: E.wms.putaway_task },
68
+ list: {
69
+ schema: listSchema,
70
+ disableListCache: true,
71
+ entityId: E.wms.putaway_task,
72
+ fields: [
73
+ F.id,
74
+ F.organization_id,
75
+ F.tenant_id,
76
+ F.warehouse_id,
77
+ F.source_location_id,
78
+ F.target_location_id,
79
+ F.catalog_variant_id,
80
+ F.lot_id,
81
+ F.serial_number,
82
+ F.quantity,
83
+ F.status,
84
+ F.assigned_to,
85
+ F.priority,
86
+ F.created_at,
87
+ F.updated_at,
88
+ ],
89
+ sortFieldMap: {
90
+ status: F.status,
91
+ priority: F.priority,
92
+ createdAt: F.created_at,
93
+ updatedAt: F.updated_at,
94
+ quantity: F.quantity,
95
+ },
96
+ buildFilters: async (query) => {
97
+ const filters: Record<string, unknown> = {}
98
+ if (typeof query.ids === 'string' && query.ids.trim().length > 0) {
99
+ filters[F.id] = {
100
+ $in: query.ids
101
+ .split(',')
102
+ .map((value) => value.trim())
103
+ .filter((value) => value.length > 0),
104
+ }
105
+ }
106
+ if (typeof query.warehouseId === 'string' && query.warehouseId.length > 0) {
107
+ filters[F.warehouse_id] = { $eq: query.warehouseId }
108
+ }
109
+ if (typeof query.status === 'string' && query.status.length > 0) {
110
+ const statusFilter = buildPutawayTaskStatusFilter(query.status)
111
+ if (statusFilter) filters[F.status] = statusFilter
112
+ }
113
+ if (typeof query.assignedTo === 'string' && query.assignedTo.length > 0) {
114
+ filters[F.assigned_to] = { $eq: query.assignedTo }
115
+ }
116
+ if (typeof query.catalogVariantId === 'string' && query.catalogVariantId.length > 0) {
117
+ filters[F.catalog_variant_id] = { $eq: query.catalogVariantId }
118
+ }
119
+ return filters
120
+ },
121
+ },
122
+ actions: {
123
+ create: {
124
+ commandId: 'wms.putaway-tasks.create',
125
+ schema: rawBodySchema,
126
+ mapInput: async ({ raw, ctx }) => {
127
+ const { translate } = await resolveTranslations()
128
+ return parseScopedCommandInput(putawayTaskCreateSchema, raw ?? {}, ctx, translate)
129
+ },
130
+ response: ({ result }) => ({ id: result?.taskId ?? null }),
131
+ status: 201,
132
+ },
133
+ update: {
134
+ commandId: 'wms.putaway-tasks.update',
135
+ schema: rawBodySchema,
136
+ mapInput: async ({ raw, ctx }) => {
137
+ const { translate } = await resolveTranslations()
138
+ assertPutawayLifecycleFieldsForbidden(raw)
139
+ return parseScopedCommandInput(putawayTaskUpdateSchema, raw ?? {}, ctx, translate)
140
+ },
141
+ response: () => ({ ok: true }),
142
+ },
143
+ delete: {
144
+ commandId: 'wms.putaway-tasks.delete',
145
+ schema: rawBodySchema,
146
+ mapInput: async ({ parsed, ctx }) => {
147
+ const { translate } = await resolveTranslations()
148
+ return { id: resolveCrudRecordId(parsed, ctx, translate) }
149
+ },
150
+ response: () => ({ ok: true }),
151
+ },
152
+ },
153
+ })
154
+
155
+ export const GET = crud.GET
156
+ export const POST = crud.POST
157
+ export const PUT = crud.PUT
158
+ export const DELETE = crud.DELETE
159
+
160
+ const putawayTaskListItemSchema = z.object({
161
+ id: z.string().uuid().nullable().optional(),
162
+ organization_id: z.string().uuid().nullable().optional(),
163
+ tenant_id: z.string().uuid().nullable().optional(),
164
+ warehouse_id: z.string().uuid().nullable().optional(),
165
+ source_location_id: z.string().uuid().nullable().optional(),
166
+ target_location_id: z.string().uuid().nullable().optional(),
167
+ catalog_variant_id: z.string().uuid().nullable().optional(),
168
+ lot_id: z.string().uuid().nullable().optional(),
169
+ serial_number: z.string().nullable().optional(),
170
+ quantity: z.union([z.string(), z.number()]).nullable().optional(),
171
+ status: z.string().nullable().optional(),
172
+ assigned_to: z.string().uuid().nullable().optional(),
173
+ priority: z.union([z.string(), z.number()]).nullable().optional(),
174
+ created_at: z.string().nullable().optional(),
175
+ updated_at: z.string().nullable().optional(),
176
+ })
177
+
178
+ export const openApi = createWmsCrudOpenApi({
179
+ resourceName: 'PutawayTask',
180
+ pluralName: 'Putaway tasks',
181
+ querySchema: listSchema,
182
+ listResponseSchema: createPagedListResponseSchema(putawayTaskListItemSchema),
183
+ create: {
184
+ schema: putawayTaskCreateSchema,
185
+ description:
186
+ 'Creates an open putaway task after verifying available staging balance (on-hand minus reserved/allocated minus open/in-progress putaway commitments). Target location may be null until completion. Assignee changes use POST .../assign.',
187
+ },
188
+ update: {
189
+ schema: putawayTaskUpdateSchema,
190
+ responseSchema: defaultOkResponseSchema,
191
+ description:
192
+ 'Updates putaway task fields (locations, quantity, priority, metadata). Status and assignedTo are lifecycle-only — use POST .../{assign,start,complete,cancel}.',
193
+ },
194
+ del: {
195
+ schema: z.object({ id: z.string().uuid() }),
196
+ responseSchema: defaultOkResponseSchema,
197
+ description: 'Soft-deletes a putaway task by id.',
198
+ },
199
+ })
@@ -0,0 +1,180 @@
1
+ import { z } from 'zod'
2
+ import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
3
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
4
+ import { parseScopedCommandInput, resolveCrudRecordId } from '@open-mercato/shared/lib/api/scoped'
5
+ import { E } from '#generated/entities.ids.generated'
6
+ import { ReceivingLine } from '../../data/entities'
7
+ import { receivingLineCreateSchema, receivingLineUpdateSchema } from '../../data/validators'
8
+ import { assertReceivingLineLifecycleFieldsForbidden } from '../../lib/asnReceiving'
9
+ import { createPagedListResponseSchema, createWmsCrudOpenApi, defaultOkResponseSchema } from '../openapi'
10
+
11
+ const F = {
12
+ id: 'id',
13
+ organization_id: 'organization_id',
14
+ tenant_id: 'tenant_id',
15
+ asn_id: 'asn_id',
16
+ catalog_variant_id: 'catalog_variant_id',
17
+ expected_qty: 'expected_qty',
18
+ received_qty: 'received_qty',
19
+ lot_number: 'lot_number',
20
+ serial_numbers: 'serial_numbers',
21
+ qc_status: 'qc_status',
22
+ target_staging_location_id: 'target_staging_location_id',
23
+ rejection_reason: 'rejection_reason',
24
+ created_at: 'created_at',
25
+ updated_at: 'updated_at',
26
+ } as const
27
+
28
+ const routeMetadata = {
29
+ GET: { requireAuth: true, requireFeatures: ['wms.view'] },
30
+ POST: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
31
+ PUT: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
32
+ DELETE: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
33
+ }
34
+
35
+ export const metadata = routeMetadata
36
+
37
+ const rawBodySchema = z.object({}).passthrough()
38
+
39
+ const listSchema = z.object({
40
+ page: z.coerce.number().min(1).default(1),
41
+ pageSize: z.coerce.number().min(1).max(100).default(25),
42
+ ids: z.string().optional(),
43
+ asnId: z.string().uuid().optional(),
44
+ catalogVariantId: z.string().uuid().optional(),
45
+ qcStatus: z.enum(['pending', 'passed', 'failed']).optional(),
46
+ sortField: z.string().optional(),
47
+ sortDir: z.enum(['asc', 'desc']).optional(),
48
+ }).passthrough()
49
+
50
+ const crud = makeCrudRoute({
51
+ metadata: routeMetadata,
52
+ orm: {
53
+ entity: ReceivingLine,
54
+ idField: 'id',
55
+ orgField: 'organizationId',
56
+ tenantField: 'tenantId',
57
+ softDeleteField: 'deletedAt',
58
+ },
59
+ indexer: { entityType: E.wms.receiving_line },
60
+ list: {
61
+ schema: listSchema,
62
+ disableListCache: true,
63
+ entityId: E.wms.receiving_line,
64
+ fields: [
65
+ F.id,
66
+ F.organization_id,
67
+ F.tenant_id,
68
+ F.asn_id,
69
+ F.catalog_variant_id,
70
+ F.expected_qty,
71
+ F.received_qty,
72
+ F.lot_number,
73
+ F.serial_numbers,
74
+ F.qc_status,
75
+ F.target_staging_location_id,
76
+ F.rejection_reason,
77
+ F.created_at,
78
+ F.updated_at,
79
+ ],
80
+ sortFieldMap: {
81
+ expectedQty: F.expected_qty,
82
+ receivedQty: F.received_qty,
83
+ qcStatus: F.qc_status,
84
+ createdAt: F.created_at,
85
+ updatedAt: F.updated_at,
86
+ },
87
+ buildFilters: async (query) => {
88
+ const filters: Record<string, unknown> = {}
89
+ if (typeof query.ids === 'string' && query.ids.trim().length > 0) {
90
+ filters[F.id] = {
91
+ $in: query.ids.split(',').map((value) => value.trim()).filter((value) => value.length > 0),
92
+ }
93
+ }
94
+ if (typeof query.asnId === 'string' && query.asnId.length > 0) {
95
+ filters[F.asn_id] = { $eq: query.asnId }
96
+ }
97
+ if (typeof query.catalogVariantId === 'string' && query.catalogVariantId.length > 0) {
98
+ filters[F.catalog_variant_id] = { $eq: query.catalogVariantId }
99
+ }
100
+ if (typeof query.qcStatus === 'string' && query.qcStatus.length > 0) {
101
+ filters[F.qc_status] = { $eq: query.qcStatus }
102
+ }
103
+ return filters
104
+ },
105
+ },
106
+ actions: {
107
+ create: {
108
+ commandId: 'wms.receiving-lines.create',
109
+ schema: rawBodySchema,
110
+ mapInput: async ({ raw, ctx }) => {
111
+ const { translate } = await resolveTranslations()
112
+ return parseScopedCommandInput(receivingLineCreateSchema, raw ?? {}, ctx, translate)
113
+ },
114
+ response: ({ result }) => ({ id: result?.lineId ?? null }),
115
+ status: 201,
116
+ },
117
+ update: {
118
+ commandId: 'wms.receiving-lines.update',
119
+ schema: rawBodySchema,
120
+ mapInput: async ({ raw, ctx }) => {
121
+ const { translate } = await resolveTranslations()
122
+ assertReceivingLineLifecycleFieldsForbidden(raw)
123
+ return parseScopedCommandInput(receivingLineUpdateSchema, raw ?? {}, ctx, translate)
124
+ },
125
+ response: () => ({ ok: true }),
126
+ },
127
+ delete: {
128
+ commandId: 'wms.receiving-lines.delete',
129
+ schema: rawBodySchema,
130
+ mapInput: async ({ parsed, ctx }) => {
131
+ const { translate } = await resolveTranslations()
132
+ return { id: resolveCrudRecordId(parsed, ctx, translate) }
133
+ },
134
+ response: () => ({ ok: true }),
135
+ },
136
+ },
137
+ })
138
+
139
+ export const GET = crud.GET
140
+ export const POST = crud.POST
141
+ export const PUT = crud.PUT
142
+ export const DELETE = crud.DELETE
143
+
144
+ const receivingLineListItemSchema = z.object({
145
+ id: z.string().uuid().nullable().optional(),
146
+ organization_id: z.string().uuid().nullable().optional(),
147
+ tenant_id: z.string().uuid().nullable().optional(),
148
+ asn_id: z.string().uuid().nullable().optional(),
149
+ catalog_variant_id: z.string().uuid().nullable().optional(),
150
+ expected_qty: z.union([z.string(), z.number()]).nullable().optional(),
151
+ received_qty: z.union([z.string(), z.number()]).nullable().optional(),
152
+ lot_number: z.string().nullable().optional(),
153
+ serial_numbers: z.array(z.string()).nullable().optional(),
154
+ qc_status: z.string().nullable().optional(),
155
+ target_staging_location_id: z.string().uuid().nullable().optional(),
156
+ rejection_reason: z.string().nullable().optional(),
157
+ created_at: z.string().nullable().optional(),
158
+ updated_at: z.string().nullable().optional(),
159
+ })
160
+
161
+ export const openApi = createWmsCrudOpenApi({
162
+ resourceName: 'ReceivingLine',
163
+ pluralName: 'Receiving lines',
164
+ querySchema: listSchema,
165
+ listResponseSchema: createPagedListResponseSchema(receivingLineListItemSchema),
166
+ create: {
167
+ schema: receivingLineCreateSchema,
168
+ description: 'Creates a receiving line against an ASN.',
169
+ },
170
+ update: {
171
+ schema: receivingLineUpdateSchema,
172
+ responseSchema: defaultOkResponseSchema,
173
+ description: 'Updates a receiving line by id.',
174
+ },
175
+ del: {
176
+ schema: z.object({ id: z.string().uuid() }),
177
+ responseSchema: defaultOkResponseSchema,
178
+ description: 'Soft-deletes a receiving line by id.',
179
+ },
180
+ })
@@ -0,0 +1,108 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
7
+ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
8
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
9
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
10
+ import { createLogger } from '@open-mercato/shared/lib/logger'
11
+ import { putawayTaskCompleteSchema, scanPutawaySchema } from '../../../data/validators'
12
+ import { resolveScanPutawayCommandInput } from '../../../lib/scanResolve'
13
+ import { executeWmsCustomPostRoute } from '../../inventory/helpers'
14
+
15
+ const logger = createLogger('wms')
16
+
17
+ export const metadata = {
18
+ // Same floor ACL as putaway-tasks/:id/complete (command enforces manage vs assignee).
19
+ POST: { requireAuth: true, requireFeatures: ['wms.adjust_inventory'] },
20
+ }
21
+
22
+ const successSchema = z.object({
23
+ ok: z.literal(true),
24
+ movementId: z.string().uuid(),
25
+ })
26
+ const errorSchema = z.object({ error: z.string() })
27
+
28
+ export async function POST(request: Request) {
29
+ try {
30
+ const container = await createRequestContainer()
31
+ const auth = await getAuthFromRequest(request)
32
+ if (!auth?.tenantId) {
33
+ throw new CrudHttpError(401, { error: 'Unauthorized' })
34
+ }
35
+ const organizationScope = await resolveOrganizationScopeForRequest({
36
+ container,
37
+ auth,
38
+ request,
39
+ })
40
+ const organizationId = organizationScope?.selectedId ?? auth.orgId ?? null
41
+ if (!organizationId) {
42
+ throw new CrudHttpError(400, { error: 'organization_scope_required' })
43
+ }
44
+ const body = await readJsonSafe<Record<string, unknown>>(request, {})
45
+ const parsed = scanPutawaySchema.parse(body)
46
+ if (parsed.tenantId !== auth.tenantId && !auth.isSuperAdmin) {
47
+ throw new CrudHttpError(403, { error: 'Forbidden' })
48
+ }
49
+ // Scope organization from auth/session — never trust body organizationId for
50
+ // task/location resolve (same contract as scan/receive and resolve-location/lot).
51
+ const scoped = {
52
+ ...parsed,
53
+ tenantId: auth.tenantId,
54
+ organizationId,
55
+ }
56
+ const em = (container.resolve('em') as EntityManager).fork()
57
+ const commandInput = await resolveScanPutawayCommandInput(em, scoped)
58
+ const scopedRequest = new Request(request.url, {
59
+ method: 'POST',
60
+ headers: request.headers,
61
+ body: JSON.stringify(commandInput),
62
+ })
63
+ return executeWmsCustomPostRoute({
64
+ request: scopedRequest,
65
+ routePath: 'wms/scan/putaway',
66
+ inputSchema: putawayTaskCompleteSchema,
67
+ commandId: 'wms.putaway-tasks.complete',
68
+ describeResource: (input) => ({
69
+ resourceKind: 'wms.putawayTask',
70
+ resourceId: input.id,
71
+ }),
72
+ mapSuccess: (result: { movementId: string }) => ({
73
+ ok: true,
74
+ movementId: result.movementId,
75
+ }),
76
+ })
77
+ } catch (error) {
78
+ if (error instanceof CrudHttpError) {
79
+ return NextResponse.json(error.body, { status: error.status })
80
+ }
81
+ if (error instanceof z.ZodError) {
82
+ return NextResponse.json({ error: 'Validation failed', details: error.issues }, { status: 400 })
83
+ }
84
+ logger.error('scan putaway failed', { err: error })
85
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
86
+ }
87
+ }
88
+
89
+ export const openApi: OpenApiRouteDoc = {
90
+ tag: 'WMS',
91
+ summary: 'Scan-assisted putaway complete',
92
+ methods: {
93
+ POST: {
94
+ summary: 'Complete putaway from scanned target location code',
95
+ description:
96
+ 'Resolves the scanned target location code, then executes the same command path as POST /api/wms/putaway-tasks/:id/complete. Organization is taken from the authenticated session scope, not the request body. Requires `wms.adjust_inventory`; managers with `wms.manage_putaway` may complete any task, operators only when assigned.',
97
+ requestBody: { contentType: 'application/json', schema: scanPutawaySchema },
98
+ responses: [{ status: 200, description: 'Putaway completed', schema: successSchema }],
99
+ errors: [
100
+ { status: 400, description: 'Validation failed', schema: errorSchema },
101
+ { status: 401, description: 'Unauthorized', schema: errorSchema },
102
+ { status: 404, description: 'Task or location not found', schema: errorSchema },
103
+ { status: 409, description: 'Invalid putaway state or insufficient stock', schema: errorSchema },
104
+ { status: 422, description: 'Invalid or inactive location', schema: errorSchema },
105
+ ],
106
+ },
107
+ },
108
+ }