@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,62 @@
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 { asnCloseSchema } from '../../../../data/validators'
5
+ import { executeWmsCustomPostRoute } from '../../../inventory/helpers'
6
+
7
+ export const metadata = {
8
+ POST: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
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/asns/${parsedParams.id}/complete`,
26
+ inputSchema: asnCloseSchema,
27
+ commandId: 'wms.asns.close',
28
+ describeResource: (input) => ({
29
+ resourceKind: 'wms.asn',
30
+ resourceId: input.id,
31
+ }),
32
+ mapSuccess: (result: { status: string }) => ({
33
+ ok: true,
34
+ status: result.status,
35
+ }),
36
+ })
37
+ }
38
+
39
+ const successSchema = z.object({
40
+ ok: z.literal(true),
41
+ status: z.enum(['draft', 'in_transit', 'received', 'closed']),
42
+ })
43
+ const errorSchema = z.object({ error: z.string() })
44
+
45
+ export const openApi: OpenApiRouteDoc = {
46
+ tag: 'WMS',
47
+ summary: 'Complete ASN',
48
+ methods: {
49
+ POST: {
50
+ summary: 'Complete ASN',
51
+ description:
52
+ 'Marks an ASN as received when it has receipt activity (at least one line with receivedQty > 0) and lines are complete (QC-passed accepted qty meets expected), or when closeWhenShort is true for short/QC-fail receipts after some receipt activity. Header-only ASNs and zero-receipt lines are rejected even with closeWhenShort.',
53
+ requestBody: { contentType: 'application/json', schema: asnCloseSchema },
54
+ responses: [{ status: 200, description: 'ASN completed', schema: successSchema }],
55
+ errors: [
56
+ { status: 400, description: 'Validation failed', schema: errorSchema },
57
+ { status: 401, description: 'Unauthorized', schema: errorSchema },
58
+ { status: 409, description: 'Invalid receipt state', schema: errorSchema },
59
+ ],
60
+ },
61
+ },
62
+ }
@@ -0,0 +1,74 @@
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 { asnReceiveLineSchema } from '../../../../data/validators'
5
+ import { executeWmsCustomPostRoute } from '../../../inventory/helpers'
6
+
7
+ export const metadata = {
8
+ POST: { requireAuth: true, requireFeatures: ['wms.receive_inventory'] },
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, asnId: parsedParams.id }),
22
+ })
23
+ return executeWmsCustomPostRoute({
24
+ request: scopedRequest,
25
+ routePath: `wms/asns/${parsedParams.id}/receive`,
26
+ inputSchema: asnReceiveLineSchema,
27
+ commandId: 'wms.asns.receive-line',
28
+ describeResource: (input) => ({
29
+ resourceKind: 'wms.asn',
30
+ resourceId: input.asnId,
31
+ }),
32
+ mapSuccess: (result: {
33
+ movementIds: string[]
34
+ putawayTaskIds: string[]
35
+ receivedQty?: number
36
+ asnUpdatedAt?: string
37
+ }) => ({
38
+ ok: true,
39
+ movementIds: result.movementIds,
40
+ putawayTaskIds: result.putawayTaskIds,
41
+ receivedQty: result.receivedQty,
42
+ asnUpdatedAt: result.asnUpdatedAt,
43
+ }),
44
+ })
45
+ }
46
+
47
+ const successSchema = z.object({
48
+ ok: z.literal(true),
49
+ movementIds: z.array(z.string().uuid()),
50
+ putawayTaskIds: z.array(z.string().uuid()),
51
+ receivedQty: z.number().optional(),
52
+ asnUpdatedAt: z.string().datetime().optional(),
53
+ })
54
+ const errorSchema = z.object({ error: z.string() })
55
+
56
+ export const openApi: OpenApiRouteDoc = {
57
+ tag: 'WMS',
58
+ summary: 'Receive ASN line',
59
+ methods: {
60
+ POST: {
61
+ summary: 'Receive ASN line',
62
+ description:
63
+ 'Receives a line against an ASN with QC outcome. QC pass writes receipt stock at staging/dock; QC fail records audit without balance increase. Both require absolute targetReceivedQty for retry-safe attempts (optional idempotencyKey is an extra stabilizer).',
64
+ requestBody: { contentType: 'application/json', schema: asnReceiveLineSchema },
65
+ responses: [{ status: 200, description: 'ASN line received', schema: successSchema }],
66
+ errors: [
67
+ { status: 400, description: 'Validation failed', schema: errorSchema },
68
+ { status: 401, description: 'Unauthorized', schema: errorSchema },
69
+ { status: 409, description: 'Invalid receipt state', schema: errorSchema },
70
+ { status: 422, description: 'Tracking or QC transition invalid', schema: errorSchema },
71
+ ],
72
+ },
73
+ },
74
+ }
@@ -0,0 +1,200 @@
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 { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
6
+ import { E } from '#generated/entities.ids.generated'
7
+ import { Asn } from '../../data/entities'
8
+ import { asnCreatePublicSchema, asnUpdateSchema } from '../../data/validators'
9
+ import { buildAsnStatusFilter } from '../../lib/asnReceiving'
10
+ import { attachVendorLabelsToListItems, attachWarehouseLabelsToListItems } from '../listEnrichers'
11
+ import { createPagedListResponseSchema, createWmsCrudOpenApi, defaultOkResponseSchema } from '../openapi'
12
+
13
+ const F = {
14
+ id: 'id',
15
+ organization_id: 'organization_id',
16
+ tenant_id: 'tenant_id',
17
+ warehouse_id: 'warehouse_id',
18
+ vendor_id: 'vendor_id',
19
+ status: 'status',
20
+ expected_at: 'expected_at',
21
+ reference_number: 'reference_number',
22
+ notes: 'notes',
23
+ created_at: 'created_at',
24
+ updated_at: 'updated_at',
25
+ } as const
26
+
27
+ const routeMetadata = {
28
+ GET: { requireAuth: true, requireFeatures: ['wms.view'] },
29
+ POST: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
30
+ PUT: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
31
+ DELETE: { requireAuth: true, requireFeatures: ['wms.manage_asn'] },
32
+ }
33
+
34
+ export const metadata = routeMetadata
35
+
36
+ const rawBodySchema = z.object({}).passthrough()
37
+
38
+ const listSchema = z.object({
39
+ page: z.coerce.number().min(1).default(1),
40
+ pageSize: z.coerce.number().min(1).max(100).default(25),
41
+ search: z.string().optional(),
42
+ ids: z.string().optional(),
43
+ warehouseId: z.string().uuid().optional(),
44
+ vendorId: z.string().uuid().optional(),
45
+ // Single status or comma-separated (e.g. draft,in_transit for open receiving queue).
46
+ status: z.string().optional(),
47
+ sortField: z.string().optional(),
48
+ sortDir: z.enum(['asc', 'desc']).optional(),
49
+ }).passthrough()
50
+
51
+ const crud = makeCrudRoute({
52
+ metadata: routeMetadata,
53
+ orm: {
54
+ entity: Asn,
55
+ idField: 'id',
56
+ orgField: 'organizationId',
57
+ tenantField: 'tenantId',
58
+ softDeleteField: 'deletedAt',
59
+ },
60
+ indexer: { entityType: E.wms.asn },
61
+ list: {
62
+ schema: listSchema,
63
+ disableListCache: true,
64
+ entityId: E.wms.asn,
65
+ fields: [
66
+ F.id,
67
+ F.organization_id,
68
+ F.tenant_id,
69
+ F.warehouse_id,
70
+ F.vendor_id,
71
+ F.status,
72
+ F.expected_at,
73
+ F.reference_number,
74
+ F.notes,
75
+ F.created_at,
76
+ F.updated_at,
77
+ ],
78
+ sortFieldMap: {
79
+ status: F.status,
80
+ expectedAt: F.expected_at,
81
+ createdAt: F.created_at,
82
+ updatedAt: F.updated_at,
83
+ referenceNumber: F.reference_number,
84
+ },
85
+ buildFilters: async (query) => {
86
+ const filters: Record<string, unknown> = {}
87
+ if (typeof query.ids === 'string' && query.ids.trim().length > 0) {
88
+ filters[F.id] = {
89
+ $in: query.ids.split(',').map((value) => value.trim()).filter((value) => value.length > 0),
90
+ }
91
+ }
92
+ if (typeof query.warehouseId === 'string' && query.warehouseId.length > 0) {
93
+ filters[F.warehouse_id] = { $eq: query.warehouseId }
94
+ }
95
+ if (typeof query.vendorId === 'string' && query.vendorId.length > 0) {
96
+ filters[F.vendor_id] = { $eq: query.vendorId }
97
+ }
98
+ if (typeof query.status === 'string' && query.status.length > 0) {
99
+ const statusFilter = buildAsnStatusFilter(query.status)
100
+ if (statusFilter) filters[F.status] = statusFilter
101
+ }
102
+ const term = query.search?.trim()
103
+ if (term) {
104
+ const like = `%${escapeLikePattern(term)}%`
105
+ filters.$or = [
106
+ { [F.reference_number]: { $ilike: like } },
107
+ { [F.notes]: { $ilike: like } },
108
+ ]
109
+ }
110
+ return filters
111
+ },
112
+ },
113
+ hooks: {
114
+ afterList: async (payload, ctx) => {
115
+ await Promise.all([
116
+ attachWarehouseLabelsToListItems(payload, ctx),
117
+ attachVendorLabelsToListItems(payload, ctx),
118
+ ])
119
+ },
120
+ },
121
+ actions: {
122
+ create: {
123
+ commandId: 'wms.asns.create',
124
+ schema: rawBodySchema,
125
+ mapInput: async ({ raw, ctx }) => {
126
+ const { translate } = await resolveTranslations()
127
+ // Public create strips sourceKey — procurement/system sets it via command with auth: null.
128
+ return parseScopedCommandInput(asnCreatePublicSchema, raw ?? {}, ctx, translate)
129
+ },
130
+ response: ({ result }) => ({
131
+ id: result?.asnId ?? null,
132
+ lineIds: result?.lineIds ?? [],
133
+ }),
134
+ status: 201,
135
+ },
136
+ update: {
137
+ commandId: 'wms.asns.update',
138
+ schema: rawBodySchema,
139
+ mapInput: async ({ raw, ctx }) => {
140
+ const { translate } = await resolveTranslations()
141
+ return parseScopedCommandInput(asnUpdateSchema, raw ?? {}, ctx, translate)
142
+ },
143
+ response: () => ({ ok: true }),
144
+ },
145
+ delete: {
146
+ commandId: 'wms.asns.delete',
147
+ schema: rawBodySchema,
148
+ mapInput: async ({ parsed, ctx }) => {
149
+ const { translate } = await resolveTranslations()
150
+ return { id: resolveCrudRecordId(parsed, ctx, translate) }
151
+ },
152
+ response: () => ({ ok: true }),
153
+ },
154
+ },
155
+ })
156
+
157
+ export const GET = crud.GET
158
+ export const POST = crud.POST
159
+ export const PUT = crud.PUT
160
+ export const DELETE = crud.DELETE
161
+
162
+ const asnListItemSchema = z.object({
163
+ id: z.string().uuid().nullable().optional(),
164
+ organization_id: z.string().uuid().nullable().optional(),
165
+ tenant_id: z.string().uuid().nullable().optional(),
166
+ warehouse_id: z.string().uuid().nullable().optional(),
167
+ warehouse_name: z.string().nullable().optional(),
168
+ warehouse_code: z.string().nullable().optional(),
169
+ vendor_id: z.string().uuid().nullable().optional(),
170
+ vendor_name: z.string().nullable().optional(),
171
+ status: z.string().nullable().optional(),
172
+ expected_at: z.string().nullable().optional(),
173
+ reference_number: z.string().nullable().optional(),
174
+ notes: z.string().nullable().optional(),
175
+ created_at: z.string().nullable().optional(),
176
+ updated_at: z.string().nullable().optional(),
177
+ })
178
+
179
+ export const openApi = createWmsCrudOpenApi({
180
+ resourceName: 'ASN',
181
+ pluralName: 'ASNs',
182
+ querySchema: listSchema,
183
+ listResponseSchema: createPagedListResponseSchema(asnListItemSchema),
184
+ create: {
185
+ schema: asnCreatePublicSchema,
186
+ description:
187
+ 'Creates an ASN with optional expected receiving lines. `sourceKey` is server-only (procurement/system path) and is not accepted on this public API.',
188
+ },
189
+ update: {
190
+ schema: asnUpdateSchema,
191
+ responseSchema: defaultOkResponseSchema,
192
+ description:
193
+ 'Updates an ASN by id while status is draft or in_transit and no receiving line has receipt/QC activity. Received/closed ASNs, or ASNs with line receipt/QC activity, return 409 invalid_receipt_state (status demotion and warehouse/header changes are not allowed). Status may only be set to draft or in_transit; received/closed are set via receive/complete flows.',
194
+ },
195
+ del: {
196
+ schema: z.object({ id: z.string().uuid() }),
197
+ responseSchema: defaultOkResponseSchema,
198
+ description: 'Soft-deletes an ASN by id.',
199
+ },
200
+ })
@@ -5,13 +5,11 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
5
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
6
6
  import { resolveSingleOrganizationIdOrDeny } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
7
7
  import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
8
+ import { ensureOrganizationScope } from '@open-mercato/shared/lib/commands/scope'
8
9
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
9
10
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
11
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
11
- import {
12
- runCrudMutationGuardAfterSuccess,
13
- validateCrudMutationGuard,
14
- } from '@open-mercato/shared/lib/crud/mutation-guard'
12
+ import { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'
15
13
  import { runCustomRouteAfterInterceptors } from '@open-mercato/shared/lib/crud/custom-route-interceptor'
16
14
  import { createLogger } from '@open-mercato/shared/lib/logger'
17
15
 
@@ -31,6 +29,27 @@ type ExecuteWmsCustomPostRouteOptions<TInput, TResult> = {
31
29
  mapSuccess: (result: TResult) => Record<string, unknown>
32
30
  }
33
31
 
32
+ function resolveBodyOrganizationId(body: Record<string, unknown>): string | null {
33
+ const raw = body.organizationId
34
+ if (typeof raw !== 'string') return null
35
+ const trimmed = raw.trim()
36
+ return trimmed.length > 0 ? trimmed : null
37
+ }
38
+
39
+ /**
40
+ * Shared POST helper for WMS custom write routes.
41
+ *
42
+ * Organization scoping:
43
+ * - When the body includes `organizationId`, pass it through and validate with
44
+ * `ensureOrganizationScope` (allowed org ids / Pattern C). Do not silently
45
+ * overwrite with the session-selected org — that mis-routes multi-org writes.
46
+ * - When the body omits `organizationId`, default to the session selected/auth org.
47
+ * - `tenantId` is always taken from auth (never from the body).
48
+ *
49
+ * Intentional session-only exception: barcode scan resolve/receive/putaway routes
50
+ * force session org onto the request body *before* calling this helper so resolve
51
+ * lookups cannot probe another org. That pre-scope policy stays on those routes.
52
+ */
34
53
  export async function executeWmsCustomPostRoute<TInput, TResult>(
35
54
  options: ExecuteWmsCustomPostRouteOptions<TInput, TResult>,
36
55
  ) {
@@ -54,41 +73,45 @@ export async function executeWmsCustomPostRoute<TInput, TResult>(
54
73
  organizationIds: organizationScope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
55
74
  request: options.request,
56
75
  }
57
- const body = await readJsonSafe<Record<string, unknown>>(options.request, {})
58
- const parsed = options.inputSchema.parse(body)
59
- const resource = options.describeResource(parsed)
60
- const guardResult = await validateCrudMutationGuard(container, {
76
+ const body = (await readJsonSafe<Record<string, unknown>>(options.request, {})) ?? {}
77
+ const sessionOrganizationId = ctx.selectedOrganizationId ?? auth.orgId ?? null
78
+ const organizationId = resolveBodyOrganizationId(body) ?? sessionOrganizationId
79
+ if (!organizationId) {
80
+ throw new CrudHttpError(400, { error: 'organization_scope_required' })
81
+ }
82
+ ensureOrganizationScope(ctx, organizationId)
83
+ const scopedBody = {
84
+ ...body,
61
85
  tenantId: auth.tenantId,
62
- organizationId: ctx.selectedOrganizationId,
63
- userId: auth.sub,
64
- resourceKind: resource.resourceKind,
65
- resourceId: resource.resourceId,
66
- operation: 'custom',
67
- requestMethod: options.request.method,
68
- requestHeaders: options.request.headers,
69
- mutationPayload: parsed as Record<string, unknown>,
70
- })
71
- if (guardResult && !guardResult.ok) {
72
- return NextResponse.json(guardResult.body, { status: guardResult.status })
86
+ organizationId,
73
87
  }
74
- const commandBus = container.resolve('commandBus') as CommandBus
75
- const execution = await commandBus.execute<TInput, TResult>(options.commandId, {
76
- input: parsed,
77
- ctx,
78
- })
79
- if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
80
- await runCrudMutationGuardAfterSuccess(container, {
81
- tenantId: auth.tenantId,
82
- organizationId: ctx.selectedOrganizationId,
88
+ const parsed = options.inputSchema.parse(scopedBody)
89
+ const resource = options.describeResource(parsed)
90
+ const guardResult = await runRouteMutationGuards({
91
+ container,
92
+ req: options.request,
93
+ auth: {
83
94
  userId: auth.sub,
95
+ tenantId: auth.tenantId,
96
+ organizationId,
97
+ },
98
+ input: {
84
99
  resourceKind: resource.resourceKind,
85
100
  resourceId: resource.resourceId,
86
101
  operation: 'custom',
87
- requestMethod: options.request.method,
88
- requestHeaders: options.request.headers,
89
- metadata: guardResult.metadata ?? null,
90
- })
102
+ mutationPayload: parsed as Record<string, unknown>,
103
+ },
104
+ })
105
+ if (!guardResult.ok) {
106
+ return NextResponse.json(guardResult.errorBody, { status: guardResult.errorStatus })
91
107
  }
108
+ const commandInput = (guardResult.modifiedPayload as TInput | undefined) ?? parsed
109
+ const commandBus = container.resolve('commandBus') as CommandBus
110
+ const execution = await commandBus.execute<TInput, TResult>(options.commandId, {
111
+ input: commandInput,
112
+ ctx,
113
+ })
114
+ await guardResult.runAfterSuccess()
92
115
  const responseBody = options.mapSuccess(execution.result)
93
116
  const intercepted = await runCustomRouteAfterInterceptors({
94
117
  routePath: options.routePath,
@@ -96,7 +119,7 @@ export async function executeWmsCustomPostRoute<TInput, TResult>(
96
119
  request: {
97
120
  method: 'POST',
98
121
  url: options.request.url,
99
- body: parsed as Record<string, unknown>,
122
+ body: commandInput as Record<string, unknown>,
100
123
  headers: Object.fromEntries(options.request.headers.entries()),
101
124
  },
102
125
  response: {
@@ -108,7 +131,7 @@ export async function executeWmsCustomPostRoute<TInput, TResult>(
108
131
  em: container.resolve('em'),
109
132
  container,
110
133
  userId: auth.sub,
111
- organizationId: ctx.selectedOrganizationId,
134
+ organizationId,
112
135
  tenantId: auth.tenantId,
113
136
  },
114
137
  })
@@ -36,7 +36,8 @@ export const openApi: OpenApiRouteDoc = {
36
36
  methods: {
37
37
  POST: {
38
38
  summary: 'Receive inventory',
39
- description: 'Records inbound inventory receipt and appends a receipt movement ledger row.',
39
+ description:
40
+ 'Records inbound inventory receipt and appends a receipt movement ledger row. When `organizationId` is present it must be in the caller organization scope; otherwise the session-selected organization is used.',
40
41
  requestBody: { contentType: 'application/json', schema: inventoryReceiveSchema },
41
42
  responses: [{ status: 200, description: 'Inventory received', schema: successSchema }],
42
43
  errors: [
@@ -266,6 +266,44 @@ export async function attachInventoryProfileCatalogLabelsToListItems(
266
266
  ])
267
267
  }
268
268
 
269
+ // ---------------------------------------------------------------------------
270
+ // Customers (ASN vendors)
271
+ // ---------------------------------------------------------------------------
272
+
273
+ type CustomerLookupRow = LookupRow & { display_name?: string | null }
274
+
275
+ const CUSTOMER_FIELDS = ['id', 'display_name']
276
+
277
+ /**
278
+ * Decorate list items with `vendor_name` when they carry a `vendor_id` column
279
+ * referencing `customers.customer_entity` by UUID only (no ORM cross-module
280
+ * relations). Mutates `payload.items` in place.
281
+ */
282
+ export async function attachVendorLabelsToListItems(
283
+ payload: AnyListPayload,
284
+ ctx: CrudCtx,
285
+ ): Promise<void> {
286
+ const items = Array.isArray(payload?.items) ? payload.items : []
287
+ if (items.length === 0) return
288
+ const ids = uniqueIdsFromKeys(items, ['vendor_id'])
289
+ if (ids.length === 0) return
290
+ const map = (await batchLoadById(
291
+ ctx,
292
+ E.customers.customer_entity,
293
+ ids,
294
+ CUSTOMER_FIELDS,
295
+ 'vendor',
296
+ )) as Map<string, CustomerLookupRow>
297
+ if (map.size === 0) return
298
+ for (const item of items) {
299
+ const id = item?.vendor_id
300
+ if (typeof id !== 'string' || !id) continue
301
+ const row = map.get(id)
302
+ if (!row) continue
303
+ decorateOnce(item, 'vendor_name', row.display_name ?? null)
304
+ }
305
+ }
306
+
269
307
  // ---------------------------------------------------------------------------
270
308
  // Reservation sources
271
309
  // ---------------------------------------------------------------------------
@@ -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 { putawayTaskAssignSchema } 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}/assign`,
26
+ inputSchema: putawayTaskAssignSchema,
27
+ commandId: 'wms.putaway-tasks.assign',
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: 'Assign putaway task',
42
+ methods: {
43
+ POST: {
44
+ summary: 'Assign putaway task',
45
+ description: 'Assigns a putaway task to a user.',
46
+ requestBody: { contentType: 'application/json', schema: putawayTaskAssignSchema },
47
+ responses: [{ status: 200, description: 'Putaway task assigned', 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,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 { putawayTaskCancelSchema } 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}/cancel`,
26
+ inputSchema: putawayTaskCancelSchema,
27
+ commandId: 'wms.putaway-tasks.cancel',
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: 'Cancel putaway task',
42
+ methods: {
43
+ POST: {
44
+ summary: 'Cancel putaway task',
45
+ description: 'Cancels an open or in-progress putaway task without moving stock.',
46
+ requestBody: { contentType: 'application/json', schema: putawayTaskCancelSchema },
47
+ responses: [{ status: 200, description: 'Putaway task cancelled', 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
+ }