@open-mercato/core 0.6.7-develop.6669.1.40b669666b → 0.6.7-develop.6671.1.4b925c5096

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 (473) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/inventory_balance/index.js +21 -0
  3. package/dist/generated/entities/inventory_balance/index.js.map +7 -0
  4. package/dist/generated/entities/inventory_lot/index.js +19 -0
  5. package/dist/generated/entities/inventory_lot/index.js.map +7 -0
  6. package/dist/generated/entities/inventory_movement/index.js +35 -0
  7. package/dist/generated/entities/inventory_movement/index.js.map +7 -0
  8. package/dist/generated/entities/inventory_reservation/index.js +23 -0
  9. package/dist/generated/entities/inventory_reservation/index.js.map +7 -0
  10. package/dist/generated/entities/product_inventory_profile/index.js +21 -0
  11. package/dist/generated/entities/product_inventory_profile/index.js.map +7 -0
  12. package/dist/generated/entities/sales_order_warehouse_assignment/index.js +11 -0
  13. package/dist/generated/entities/sales_order_warehouse_assignment/index.js.map +7 -0
  14. package/dist/generated/entities/warehouse/index.js +25 -0
  15. package/dist/generated/entities/warehouse/index.js.map +7 -0
  16. package/dist/generated/entities/warehouse_location/index.js +21 -0
  17. package/dist/generated/entities/warehouse_location/index.js.map +7 -0
  18. package/dist/generated/entities/warehouse_zone/index.js +11 -0
  19. package/dist/generated/entities/warehouse_zone/index.js.map +7 -0
  20. package/dist/generated/entities.ids.generated.js +12 -0
  21. package/dist/generated/entities.ids.generated.js.map +2 -2
  22. package/dist/generated/entity-fields-registry.js +98 -0
  23. package/dist/generated/entity-fields-registry.js.map +2 -2
  24. package/dist/modules/auth/backend/roles/page.js +12 -4
  25. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  26. package/dist/modules/auth/lib/backendChrome.js +1 -0
  27. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  28. package/dist/modules/auth/services/rbacService.js +16 -17
  29. package/dist/modules/auth/services/rbacService.js.map +2 -2
  30. package/dist/modules/catalog/api/products/route.js +3 -0
  31. package/dist/modules/catalog/api/products/route.js.map +2 -2
  32. package/dist/modules/catalog/api/variants/route.js +3 -0
  33. package/dist/modules/catalog/api/variants/route.js.map +2 -2
  34. package/dist/modules/query_index/lib/engine.js +6 -6
  35. package/dist/modules/query_index/lib/engine.js.map +2 -2
  36. package/dist/modules/sales/api/documents/factory.js +1 -0
  37. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  38. package/dist/modules/sales/commands/documents.js +53 -0
  39. package/dist/modules/sales/commands/documents.js.map +2 -2
  40. package/dist/modules/sales/components/documents/ItemsSection.js +51 -2
  41. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  42. package/dist/modules/sales/events.js +2 -0
  43. package/dist/modules/sales/events.js.map +2 -2
  44. package/dist/modules/sales/widgets/injection/order-items-context.js +11 -0
  45. package/dist/modules/sales/widgets/injection/order-items-context.js.map +7 -0
  46. package/dist/modules/wms/acl.js +18 -0
  47. package/dist/modules/wms/acl.js.map +7 -0
  48. package/dist/modules/wms/analytics.js +11 -0
  49. package/dist/modules/wms/analytics.js.map +7 -0
  50. package/dist/modules/wms/api/dashboard/operational/route.js +80 -0
  51. package/dist/modules/wms/api/dashboard/operational/route.js.map +7 -0
  52. package/dist/modules/wms/api/interceptors.js +90 -0
  53. package/dist/modules/wms/api/interceptors.js.map +7 -0
  54. package/dist/modules/wms/api/inventory/adjust/route.js +51 -0
  55. package/dist/modules/wms/api/inventory/adjust/route.js.map +7 -0
  56. package/dist/modules/wms/api/inventory/allocate/route.js +51 -0
  57. package/dist/modules/wms/api/inventory/allocate/route.js.map +7 -0
  58. package/dist/modules/wms/api/inventory/balances/route.js +172 -0
  59. package/dist/modules/wms/api/inventory/balances/route.js.map +7 -0
  60. package/dist/modules/wms/api/inventory/cycle-count/route.js +52 -0
  61. package/dist/modules/wms/api/inventory/cycle-count/route.js.map +7 -0
  62. package/dist/modules/wms/api/inventory/helpers.js +113 -0
  63. package/dist/modules/wms/api/inventory/helpers.js.map +7 -0
  64. package/dist/modules/wms/api/inventory/import/apply/route.js +46 -0
  65. package/dist/modules/wms/api/inventory/import/apply/route.js.map +7 -0
  66. package/dist/modules/wms/api/inventory/import/helpers.js +195 -0
  67. package/dist/modules/wms/api/inventory/import/helpers.js.map +7 -0
  68. package/dist/modules/wms/api/inventory/import/template/route.js +32 -0
  69. package/dist/modules/wms/api/inventory/import/template/route.js.map +7 -0
  70. package/dist/modules/wms/api/inventory/import/validate/route.js +54 -0
  71. package/dist/modules/wms/api/inventory/import/validate/route.js.map +7 -0
  72. package/dist/modules/wms/api/inventory/move/route.js +51 -0
  73. package/dist/modules/wms/api/inventory/move/route.js.map +7 -0
  74. package/dist/modules/wms/api/inventory/movements/route.js +164 -0
  75. package/dist/modules/wms/api/inventory/movements/route.js.map +7 -0
  76. package/dist/modules/wms/api/inventory/receive/route.js +50 -0
  77. package/dist/modules/wms/api/inventory/receive/route.js.map +7 -0
  78. package/dist/modules/wms/api/inventory/release/route.js +44 -0
  79. package/dist/modules/wms/api/inventory/release/route.js.map +7 -0
  80. package/dist/modules/wms/api/inventory/reservations/route.js +128 -0
  81. package/dist/modules/wms/api/inventory/reservations/route.js.map +7 -0
  82. package/dist/modules/wms/api/inventory/reserve/route.js +59 -0
  83. package/dist/modules/wms/api/inventory/reserve/route.js.map +7 -0
  84. package/dist/modules/wms/api/inventory-profiles/route.js +178 -0
  85. package/dist/modules/wms/api/inventory-profiles/route.js.map +7 -0
  86. package/dist/modules/wms/api/listEnrichers.js +181 -0
  87. package/dist/modules/wms/api/listEnrichers.js.map +7 -0
  88. package/dist/modules/wms/api/listSearch.js +77 -0
  89. package/dist/modules/wms/api/listSearch.js.map +7 -0
  90. package/dist/modules/wms/api/locations/route.js +172 -0
  91. package/dist/modules/wms/api/locations/route.js.map +7 -0
  92. package/dist/modules/wms/api/lots/route.js +204 -0
  93. package/dist/modules/wms/api/lots/route.js.map +7 -0
  94. package/dist/modules/wms/api/openapi.js +27 -0
  95. package/dist/modules/wms/api/openapi.js.map +7 -0
  96. package/dist/modules/wms/api/sales-orders/[salesOrderId]/re-run-reservation/route.js +54 -0
  97. package/dist/modules/wms/api/sales-orders/[salesOrderId]/re-run-reservation/route.js.map +7 -0
  98. package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js +279 -0
  99. package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js.map +7 -0
  100. package/dist/modules/wms/api/sales-orders/assign-warehouse/route.js +56 -0
  101. package/dist/modules/wms/api/sales-orders/assign-warehouse/route.js.map +7 -0
  102. package/dist/modules/wms/api/sales-orders/unassign-warehouse/route.js +43 -0
  103. package/dist/modules/wms/api/sales-orders/unassign-warehouse/route.js.map +7 -0
  104. package/dist/modules/wms/api/warehouses/route.js +181 -0
  105. package/dist/modules/wms/api/warehouses/route.js.map +7 -0
  106. package/dist/modules/wms/api/zones/route.js +159 -0
  107. package/dist/modules/wms/api/zones/route.js.map +7 -0
  108. package/dist/modules/wms/backend/config/wms/page.js +9 -0
  109. package/dist/modules/wms/backend/config/wms/page.js.map +7 -0
  110. package/dist/modules/wms/backend/config/wms/page.meta.js +18 -0
  111. package/dist/modules/wms/backend/config/wms/page.meta.js.map +7 -0
  112. package/dist/modules/wms/backend/page.js +9 -0
  113. package/dist/modules/wms/backend/page.js.map +7 -0
  114. package/dist/modules/wms/backend/page.meta.js +16 -0
  115. package/dist/modules/wms/backend/page.meta.js.map +7 -0
  116. package/dist/modules/wms/backend/wms/inventory/page.js +9 -0
  117. package/dist/modules/wms/backend/wms/inventory/page.js.map +7 -0
  118. package/dist/modules/wms/backend/wms/inventory/page.meta.js +18 -0
  119. package/dist/modules/wms/backend/wms/inventory/page.meta.js.map +7 -0
  120. package/dist/modules/wms/backend/wms/location/[id]/page.js +9 -0
  121. package/dist/modules/wms/backend/wms/location/[id]/page.js.map +7 -0
  122. package/dist/modules/wms/backend/wms/location/[id]/page.meta.js +18 -0
  123. package/dist/modules/wms/backend/wms/location/[id]/page.meta.js.map +7 -0
  124. package/dist/modules/wms/backend/wms/locations/page.js +10 -0
  125. package/dist/modules/wms/backend/wms/locations/page.js.map +7 -0
  126. package/dist/modules/wms/backend/wms/locations/page.meta.js +18 -0
  127. package/dist/modules/wms/backend/wms/locations/page.meta.js.map +7 -0
  128. package/dist/modules/wms/backend/wms/lot/[id]/page.js +9 -0
  129. package/dist/modules/wms/backend/wms/lot/[id]/page.js.map +7 -0
  130. package/dist/modules/wms/backend/wms/lot/[id]/page.meta.js +18 -0
  131. package/dist/modules/wms/backend/wms/lot/[id]/page.meta.js.map +7 -0
  132. package/dist/modules/wms/backend/wms/lots/page.js +9 -0
  133. package/dist/modules/wms/backend/wms/lots/page.js.map +7 -0
  134. package/dist/modules/wms/backend/wms/lots/page.meta.js +18 -0
  135. package/dist/modules/wms/backend/wms/lots/page.meta.js.map +7 -0
  136. package/dist/modules/wms/backend/wms/movements/page.js +22 -0
  137. package/dist/modules/wms/backend/wms/movements/page.js.map +7 -0
  138. package/dist/modules/wms/backend/wms/movements/page.meta.js +18 -0
  139. package/dist/modules/wms/backend/wms/movements/page.meta.js.map +7 -0
  140. package/dist/modules/wms/backend/wms/reservations/page.js +23 -0
  141. package/dist/modules/wms/backend/wms/reservations/page.js.map +7 -0
  142. package/dist/modules/wms/backend/wms/reservations/page.meta.js +18 -0
  143. package/dist/modules/wms/backend/wms/reservations/page.meta.js.map +7 -0
  144. package/dist/modules/wms/backend/wms/sku/[id]/page.js +9 -0
  145. package/dist/modules/wms/backend/wms/sku/[id]/page.js.map +7 -0
  146. package/dist/modules/wms/backend/wms/sku/[id]/page.meta.js +18 -0
  147. package/dist/modules/wms/backend/wms/sku/[id]/page.meta.js.map +7 -0
  148. package/dist/modules/wms/backend/wms/warehouses/page.js +10 -0
  149. package/dist/modules/wms/backend/wms/warehouses/page.js.map +7 -0
  150. package/dist/modules/wms/backend/wms/warehouses/page.meta.js +18 -0
  151. package/dist/modules/wms/backend/wms/warehouses/page.meta.js.map +7 -0
  152. package/dist/modules/wms/backend/wms/zones/page.js +10 -0
  153. package/dist/modules/wms/backend/wms/zones/page.js.map +7 -0
  154. package/dist/modules/wms/backend/wms/zones/page.meta.js +18 -0
  155. package/dist/modules/wms/backend/wms/zones/page.meta.js.map +7 -0
  156. package/dist/modules/wms/ce.js +80 -0
  157. package/dist/modules/wms/ce.js.map +7 -0
  158. package/dist/modules/wms/cli.js +83 -0
  159. package/dist/modules/wms/cli.js.map +7 -0
  160. package/dist/modules/wms/commands/configuration.js +1519 -0
  161. package/dist/modules/wms/commands/configuration.js.map +7 -0
  162. package/dist/modules/wms/commands/index.js +4 -0
  163. package/dist/modules/wms/commands/index.js.map +7 -0
  164. package/dist/modules/wms/commands/inventory-actions.js +1539 -0
  165. package/dist/modules/wms/commands/inventory-actions.js.map +7 -0
  166. package/dist/modules/wms/commands/sales-order-assignment.js +277 -0
  167. package/dist/modules/wms/commands/sales-order-assignment.js.map +7 -0
  168. package/dist/modules/wms/commands/shared.js +82 -0
  169. package/dist/modules/wms/commands/shared.js.map +7 -0
  170. package/dist/modules/wms/components/backend/AdjustInventoryDialog.js +755 -0
  171. package/dist/modules/wms/components/backend/AdjustInventoryDialog.js.map +7 -0
  172. package/dist/modules/wms/components/backend/ChangeLotStatusDialog.js +269 -0
  173. package/dist/modules/wms/components/backend/ChangeLotStatusDialog.js.map +7 -0
  174. package/dist/modules/wms/components/backend/CycleCountWizardDialog.js +1783 -0
  175. package/dist/modules/wms/components/backend/CycleCountWizardDialog.js.map +7 -0
  176. package/dist/modules/wms/components/backend/ImportInventoryDialog.js +766 -0
  177. package/dist/modules/wms/components/backend/ImportInventoryDialog.js.map +7 -0
  178. package/dist/modules/wms/components/backend/InventoryOperationsSection.js +119 -0
  179. package/dist/modules/wms/components/backend/InventoryOperationsSection.js.map +7 -0
  180. package/dist/modules/wms/components/backend/LocationEditDialog.js +140 -0
  181. package/dist/modules/wms/components/backend/LocationEditDialog.js.map +7 -0
  182. package/dist/modules/wms/components/backend/MoveInventoryDialog.js +791 -0
  183. package/dist/modules/wms/components/backend/MoveInventoryDialog.js.map +7 -0
  184. package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js +656 -0
  185. package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js.map +7 -0
  186. package/dist/modules/wms/components/backend/ReleaseReservationDialog.js +344 -0
  187. package/dist/modules/wms/components/backend/ReleaseReservationDialog.js.map +7 -0
  188. package/dist/modules/wms/components/backend/ReserveInventoryDialog.js +475 -0
  189. package/dist/modules/wms/components/backend/ReserveInventoryDialog.js.map +7 -0
  190. package/dist/modules/wms/components/backend/WmsConfigurationPage.js +1112 -0
  191. package/dist/modules/wms/components/backend/WmsConfigurationPage.js.map +7 -0
  192. package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js +908 -0
  193. package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js.map +7 -0
  194. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +1015 -0
  195. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +7 -0
  196. package/dist/modules/wms/components/backend/WmsLotDetailPage.js +1049 -0
  197. package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +7 -0
  198. package/dist/modules/wms/components/backend/WmsLotsListPage.js +297 -0
  199. package/dist/modules/wms/components/backend/WmsLotsListPage.js.map +7 -0
  200. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js +757 -0
  201. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +7 -0
  202. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +975 -0
  203. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +7 -0
  204. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +710 -0
  205. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +7 -0
  206. package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js +79 -0
  207. package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js.map +7 -0
  208. package/dist/modules/wms/components/backend/useWmsInventoryScopeFromSearchParams.js +24 -0
  209. package/dist/modules/wms/components/backend/useWmsInventoryScopeFromSearchParams.js.map +7 -0
  210. package/dist/modules/wms/components/backend/wmsLookupLoaders.js +55 -0
  211. package/dist/modules/wms/components/backend/wmsLookupLoaders.js.map +7 -0
  212. package/dist/modules/wms/data/enrichers.js +501 -0
  213. package/dist/modules/wms/data/enrichers.js.map +7 -0
  214. package/dist/modules/wms/data/entities.js +467 -0
  215. package/dist/modules/wms/data/entities.js.map +7 -0
  216. package/dist/modules/wms/data/validators.js +399 -0
  217. package/dist/modules/wms/data/validators.js.map +7 -0
  218. package/dist/modules/wms/di.js +27 -0
  219. package/dist/modules/wms/di.js.map +7 -0
  220. package/dist/modules/wms/events.js +42 -0
  221. package/dist/modules/wms/events.js.map +7 -0
  222. package/dist/modules/wms/index.js +17 -0
  223. package/dist/modules/wms/index.js.map +7 -0
  224. package/dist/modules/wms/lib/catalogInventoryProfileIntent.js +54 -0
  225. package/dist/modules/wms/lib/catalogInventoryProfileIntent.js.map +7 -0
  226. package/dist/modules/wms/lib/catalogInventoryProfileSync.js +144 -0
  227. package/dist/modules/wms/lib/catalogInventoryProfileSync.js.map +7 -0
  228. package/dist/modules/wms/lib/expiry.js +29 -0
  229. package/dist/modules/wms/lib/expiry.js.map +7 -0
  230. package/dist/modules/wms/lib/flashMutationError.js +51 -0
  231. package/dist/modules/wms/lib/flashMutationError.js.map +7 -0
  232. package/dist/modules/wms/lib/inventoryDisplayUi.js +188 -0
  233. package/dist/modules/wms/lib/inventoryDisplayUi.js.map +7 -0
  234. package/dist/modules/wms/lib/inventoryIdempotency.js +37 -0
  235. package/dist/modules/wms/lib/inventoryIdempotency.js.map +7 -0
  236. package/dist/modules/wms/lib/inventoryImportCsv.js +173 -0
  237. package/dist/modules/wms/lib/inventoryImportCsv.js.map +7 -0
  238. package/dist/modules/wms/lib/inventoryImportService.js +468 -0
  239. package/dist/modules/wms/lib/inventoryImportService.js.map +7 -0
  240. package/dist/modules/wms/lib/inventoryMutationUi.js +25 -0
  241. package/dist/modules/wms/lib/inventoryMutationUi.js.map +7 -0
  242. package/dist/modules/wms/lib/inventoryPolicy.js +61 -0
  243. package/dist/modules/wms/lib/inventoryPolicy.js.map +7 -0
  244. package/dist/modules/wms/lib/inventoryReconciliation.js +193 -0
  245. package/dist/modules/wms/lib/inventoryReconciliation.js.map +7 -0
  246. package/dist/modules/wms/lib/inventoryTrackingValidation.js +13 -0
  247. package/dist/modules/wms/lib/inventoryTrackingValidation.js.map +7 -0
  248. package/dist/modules/wms/lib/loadOperationalDashboard.js +580 -0
  249. package/dist/modules/wms/lib/loadOperationalDashboard.js.map +7 -0
  250. package/dist/modules/wms/lib/lowStockBalanceFilter.js +49 -0
  251. package/dist/modules/wms/lib/lowStockBalanceFilter.js.map +7 -0
  252. package/dist/modules/wms/lib/primaryWarehousePolicy.js +32 -0
  253. package/dist/modules/wms/lib/primaryWarehousePolicy.js.map +7 -0
  254. package/dist/modules/wms/lib/roleFeatures.js +30 -0
  255. package/dist/modules/wms/lib/roleFeatures.js.map +7 -0
  256. package/dist/modules/wms/lib/salesOrderInventoryAutomation.js +291 -0
  257. package/dist/modules/wms/lib/salesOrderInventoryAutomation.js.map +7 -0
  258. package/dist/modules/wms/lib/salesOrderWarehouseAssignment.js +54 -0
  259. package/dist/modules/wms/lib/salesOrderWarehouseAssignment.js.map +7 -0
  260. package/dist/modules/wms/lib/wmsIntegrationToggles.js +71 -0
  261. package/dist/modules/wms/lib/wmsIntegrationToggles.js.map +7 -0
  262. package/dist/modules/wms/migrations/Migration20260428110546.js +78 -0
  263. package/dist/modules/wms/migrations/Migration20260428110546.js.map +7 -0
  264. package/dist/modules/wms/migrations/Migration20260527120000.js +15 -0
  265. package/dist/modules/wms/migrations/Migration20260527120000.js.map +7 -0
  266. package/dist/modules/wms/migrations/Migration20260527140000.js +37 -0
  267. package/dist/modules/wms/migrations/Migration20260527140000.js.map +7 -0
  268. package/dist/modules/wms/migrations/Migration20260613120000.js +23 -0
  269. package/dist/modules/wms/migrations/Migration20260613120000.js.map +7 -0
  270. package/dist/modules/wms/migrations/Migration20260616090000.js +13 -0
  271. package/dist/modules/wms/migrations/Migration20260616090000.js.map +7 -0
  272. package/dist/modules/wms/migrations/Migration20260707180000.js +15 -0
  273. package/dist/modules/wms/migrations/Migration20260707180000.js.map +7 -0
  274. package/dist/modules/wms/notifications.client.js +18 -0
  275. package/dist/modules/wms/notifications.client.js.map +7 -0
  276. package/dist/modules/wms/notifications.js +53 -0
  277. package/dist/modules/wms/notifications.js.map +7 -0
  278. package/dist/modules/wms/search.js +196 -0
  279. package/dist/modules/wms/search.js.map +7 -0
  280. package/dist/modules/wms/setup.js +30 -0
  281. package/dist/modules/wms/setup.js.map +7 -0
  282. package/dist/modules/wms/subscribers/low-stock-notification.js +40 -0
  283. package/dist/modules/wms/subscribers/low-stock-notification.js.map +7 -0
  284. package/dist/modules/wms/subscribers/reservation-shortfall-notification.js +69 -0
  285. package/dist/modules/wms/subscribers/reservation-shortfall-notification.js.map +7 -0
  286. package/dist/modules/wms/subscribers/sales-order-cancelled-release.js +14 -0
  287. package/dist/modules/wms/subscribers/sales-order-cancelled-release.js.map +7 -0
  288. package/dist/modules/wms/subscribers/sales-order-confirmed-reserve.js +14 -0
  289. package/dist/modules/wms/subscribers/sales-order-confirmed-reserve.js.map +7 -0
  290. package/dist/modules/wms/translations.js +10 -0
  291. package/dist/modules/wms/translations.js.map +7 -0
  292. package/dist/modules/wms/vector.js +9 -0
  293. package/dist/modules/wms/vector.js.map +7 -0
  294. package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js +200 -0
  295. package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js.map +7 -0
  296. package/dist/modules/wms/widgets/injection/order-items-stock-column/widget.js +104 -0
  297. package/dist/modules/wms/widgets/injection/order-items-stock-column/widget.js.map +7 -0
  298. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js +389 -0
  299. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js.map +7 -0
  300. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.js +16 -0
  301. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.js.map +7 -0
  302. package/dist/modules/wms/widgets/injection-table.js +36 -0
  303. package/dist/modules/wms/widgets/injection-table.js.map +7 -0
  304. package/dist/modules/wms/widgets/notifications/WmsLowStockRenderer.js +150 -0
  305. package/dist/modules/wms/widgets/notifications/WmsLowStockRenderer.js.map +7 -0
  306. package/dist/modules/wms/widgets/notifications/WmsReservationShortfallRenderer.js +186 -0
  307. package/dist/modules/wms/widgets/notifications/WmsReservationShortfallRenderer.js.map +7 -0
  308. package/dist/modules/wms/widgets/notifications/index.js +7 -0
  309. package/dist/modules/wms/widgets/notifications/index.js.map +7 -0
  310. package/generated/entities/inventory_balance/index.ts +9 -0
  311. package/generated/entities/inventory_lot/index.ts +8 -0
  312. package/generated/entities/inventory_movement/index.ts +16 -0
  313. package/generated/entities/inventory_reservation/index.ts +10 -0
  314. package/generated/entities/product_inventory_profile/index.ts +9 -0
  315. package/generated/entities/sales_order_warehouse_assignment/index.ts +4 -0
  316. package/generated/entities/warehouse/index.ts +11 -0
  317. package/generated/entities/warehouse_location/index.ts +9 -0
  318. package/generated/entities/warehouse_zone/index.ts +4 -0
  319. package/generated/entities.ids.generated.ts +12 -0
  320. package/generated/entity-fields-registry.ts +98 -0
  321. package/package.json +7 -7
  322. package/src/modules/auth/backend/roles/page.tsx +13 -4
  323. package/src/modules/auth/i18n/de.json +2 -0
  324. package/src/modules/auth/i18n/en.json +2 -0
  325. package/src/modules/auth/i18n/es.json +2 -0
  326. package/src/modules/auth/i18n/pl.json +2 -0
  327. package/src/modules/auth/lib/backendChrome.tsx +1 -0
  328. package/src/modules/auth/services/rbacService.ts +25 -21
  329. package/src/modules/catalog/api/products/route.ts +3 -0
  330. package/src/modules/catalog/api/variants/route.ts +3 -0
  331. package/src/modules/query_index/lib/engine.ts +18 -6
  332. package/src/modules/sales/api/documents/factory.ts +1 -0
  333. package/src/modules/sales/commands/documents.ts +71 -0
  334. package/src/modules/sales/components/documents/ItemsSection.tsx +58 -0
  335. package/src/modules/sales/events.ts +2 -0
  336. package/src/modules/sales/widgets/injection/order-items-context.ts +15 -0
  337. package/src/modules/wms/acl.ts +14 -0
  338. package/src/modules/wms/analytics.ts +9 -0
  339. package/src/modules/wms/api/dashboard/operational/route.ts +83 -0
  340. package/src/modules/wms/api/interceptors.ts +108 -0
  341. package/src/modules/wms/api/inventory/adjust/route.ts +50 -0
  342. package/src/modules/wms/api/inventory/allocate/route.ts +50 -0
  343. package/src/modules/wms/api/inventory/balances/route.ts +178 -0
  344. package/src/modules/wms/api/inventory/cycle-count/route.ts +51 -0
  345. package/src/modules/wms/api/inventory/helpers.ts +128 -0
  346. package/src/modules/wms/api/inventory/import/apply/route.ts +47 -0
  347. package/src/modules/wms/api/inventory/import/helpers.ts +223 -0
  348. package/src/modules/wms/api/inventory/import/template/route.ts +31 -0
  349. package/src/modules/wms/api/inventory/import/validate/route.ts +56 -0
  350. package/src/modules/wms/api/inventory/move/route.ts +50 -0
  351. package/src/modules/wms/api/inventory/movements/route.ts +164 -0
  352. package/src/modules/wms/api/inventory/receive/route.ts +49 -0
  353. package/src/modules/wms/api/inventory/release/route.ts +43 -0
  354. package/src/modules/wms/api/inventory/reservations/route.ts +128 -0
  355. package/src/modules/wms/api/inventory/reserve/route.ts +59 -0
  356. package/src/modules/wms/api/inventory-profiles/route.ts +177 -0
  357. package/src/modules/wms/api/listEnrichers.ts +312 -0
  358. package/src/modules/wms/api/listSearch.ts +121 -0
  359. package/src/modules/wms/api/locations/route.ts +171 -0
  360. package/src/modules/wms/api/lots/route.ts +205 -0
  361. package/src/modules/wms/api/openapi.ts +28 -0
  362. package/src/modules/wms/api/sales-orders/[salesOrderId]/re-run-reservation/route.ts +58 -0
  363. package/src/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.ts +306 -0
  364. package/src/modules/wms/api/sales-orders/assign-warehouse/route.ts +57 -0
  365. package/src/modules/wms/api/sales-orders/unassign-warehouse/route.ts +43 -0
  366. package/src/modules/wms/api/warehouses/route.ts +181 -0
  367. package/src/modules/wms/api/zones/route.ts +158 -0
  368. package/src/modules/wms/backend/config/wms/page.meta.ts +14 -0
  369. package/src/modules/wms/backend/config/wms/page.tsx +5 -0
  370. package/src/modules/wms/backend/page.meta.ts +12 -0
  371. package/src/modules/wms/backend/page.tsx +5 -0
  372. package/src/modules/wms/backend/wms/inventory/page.meta.ts +14 -0
  373. package/src/modules/wms/backend/wms/inventory/page.tsx +5 -0
  374. package/src/modules/wms/backend/wms/location/[id]/page.meta.ts +14 -0
  375. package/src/modules/wms/backend/wms/location/[id]/page.tsx +5 -0
  376. package/src/modules/wms/backend/wms/locations/page.meta.ts +14 -0
  377. package/src/modules/wms/backend/wms/locations/page.tsx +12 -0
  378. package/src/modules/wms/backend/wms/lot/[id]/page.meta.ts +14 -0
  379. package/src/modules/wms/backend/wms/lot/[id]/page.tsx +5 -0
  380. package/src/modules/wms/backend/wms/lots/page.meta.ts +14 -0
  381. package/src/modules/wms/backend/wms/lots/page.tsx +5 -0
  382. package/src/modules/wms/backend/wms/movements/page.meta.ts +14 -0
  383. package/src/modules/wms/backend/wms/movements/page.tsx +23 -0
  384. package/src/modules/wms/backend/wms/reservations/page.meta.ts +14 -0
  385. package/src/modules/wms/backend/wms/reservations/page.tsx +24 -0
  386. package/src/modules/wms/backend/wms/sku/[id]/page.meta.ts +14 -0
  387. package/src/modules/wms/backend/wms/sku/[id]/page.tsx +5 -0
  388. package/src/modules/wms/backend/wms/warehouses/page.meta.ts +14 -0
  389. package/src/modules/wms/backend/wms/warehouses/page.tsx +12 -0
  390. package/src/modules/wms/backend/wms/zones/page.meta.ts +14 -0
  391. package/src/modules/wms/backend/wms/zones/page.tsx +12 -0
  392. package/src/modules/wms/ce.ts +76 -0
  393. package/src/modules/wms/cli.ts +96 -0
  394. package/src/modules/wms/commands/configuration.ts +1844 -0
  395. package/src/modules/wms/commands/index.ts +3 -0
  396. package/src/modules/wms/commands/inventory-actions.ts +1866 -0
  397. package/src/modules/wms/commands/sales-order-assignment.ts +346 -0
  398. package/src/modules/wms/commands/shared.ts +90 -0
  399. package/src/modules/wms/components/backend/AdjustInventoryDialog.tsx +820 -0
  400. package/src/modules/wms/components/backend/ChangeLotStatusDialog.tsx +312 -0
  401. package/src/modules/wms/components/backend/CycleCountWizardDialog.tsx +2007 -0
  402. package/src/modules/wms/components/backend/ImportInventoryDialog.tsx +1018 -0
  403. package/src/modules/wms/components/backend/InventoryOperationsSection.tsx +159 -0
  404. package/src/modules/wms/components/backend/LocationEditDialog.tsx +185 -0
  405. package/src/modules/wms/components/backend/MoveInventoryDialog.tsx +907 -0
  406. package/src/modules/wms/components/backend/ReceiveInventoryDialog.tsx +695 -0
  407. package/src/modules/wms/components/backend/ReleaseReservationDialog.tsx +424 -0
  408. package/src/modules/wms/components/backend/ReserveInventoryDialog.tsx +511 -0
  409. package/src/modules/wms/components/backend/WmsConfigurationPage.tsx +1340 -0
  410. package/src/modules/wms/components/backend/WmsInventoryConsolePage.tsx +1132 -0
  411. package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +1310 -0
  412. package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +1355 -0
  413. package/src/modules/wms/components/backend/WmsLotsListPage.tsx +359 -0
  414. package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +958 -0
  415. package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +1245 -0
  416. package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +960 -0
  417. package/src/modules/wms/components/backend/useWmsInventoryMutationAccess.ts +89 -0
  418. package/src/modules/wms/components/backend/useWmsInventoryScopeFromSearchParams.ts +32 -0
  419. package/src/modules/wms/components/backend/wmsLookupLoaders.ts +78 -0
  420. package/src/modules/wms/data/enrichers.ts +718 -0
  421. package/src/modules/wms/data/entities.ts +436 -0
  422. package/src/modules/wms/data/validators.ts +463 -0
  423. package/src/modules/wms/di.ts +25 -0
  424. package/src/modules/wms/events.ts +41 -0
  425. package/src/modules/wms/i18n/de.json +1051 -0
  426. package/src/modules/wms/i18n/en.json +1051 -0
  427. package/src/modules/wms/i18n/es.json +1051 -0
  428. package/src/modules/wms/i18n/pl.json +1051 -0
  429. package/src/modules/wms/index.ts +15 -0
  430. package/src/modules/wms/lib/catalogInventoryProfileIntent.ts +65 -0
  431. package/src/modules/wms/lib/catalogInventoryProfileSync.ts +199 -0
  432. package/src/modules/wms/lib/expiry.ts +30 -0
  433. package/src/modules/wms/lib/flashMutationError.ts +74 -0
  434. package/src/modules/wms/lib/inventoryDisplayUi.ts +249 -0
  435. package/src/modules/wms/lib/inventoryIdempotency.ts +54 -0
  436. package/src/modules/wms/lib/inventoryImportCsv.ts +212 -0
  437. package/src/modules/wms/lib/inventoryImportService.ts +629 -0
  438. package/src/modules/wms/lib/inventoryMutationUi.ts +21 -0
  439. package/src/modules/wms/lib/inventoryPolicy.ts +111 -0
  440. package/src/modules/wms/lib/inventoryReconciliation.ts +263 -0
  441. package/src/modules/wms/lib/inventoryTrackingValidation.ts +23 -0
  442. package/src/modules/wms/lib/loadOperationalDashboard.ts +776 -0
  443. package/src/modules/wms/lib/lowStockBalanceFilter.ts +74 -0
  444. package/src/modules/wms/lib/primaryWarehousePolicy.ts +46 -0
  445. package/src/modules/wms/lib/roleFeatures.ts +32 -0
  446. package/src/modules/wms/lib/salesOrderInventoryAutomation.ts +390 -0
  447. package/src/modules/wms/lib/salesOrderWarehouseAssignment.ts +78 -0
  448. package/src/modules/wms/lib/wmsIntegrationToggles.ts +81 -0
  449. package/src/modules/wms/migrations/.snapshot-open-mercato.json +2880 -0
  450. package/src/modules/wms/migrations/Migration20260428110546.ts +91 -0
  451. package/src/modules/wms/migrations/Migration20260527120000.ts +15 -0
  452. package/src/modules/wms/migrations/Migration20260527140000.ts +35 -0
  453. package/src/modules/wms/migrations/Migration20260613120000.ts +21 -0
  454. package/src/modules/wms/migrations/Migration20260616090000.ts +11 -0
  455. package/src/modules/wms/migrations/Migration20260707180000.ts +13 -0
  456. package/src/modules/wms/notifications.client.ts +18 -0
  457. package/src/modules/wms/notifications.ts +51 -0
  458. package/src/modules/wms/search.ts +230 -0
  459. package/src/modules/wms/setup.ts +29 -0
  460. package/src/modules/wms/subscribers/low-stock-notification.ts +54 -0
  461. package/src/modules/wms/subscribers/reservation-shortfall-notification.ts +98 -0
  462. package/src/modules/wms/subscribers/sales-order-cancelled-release.ts +21 -0
  463. package/src/modules/wms/subscribers/sales-order-confirmed-reserve.ts +21 -0
  464. package/src/modules/wms/translations.ts +6 -0
  465. package/src/modules/wms/vector.ts +7 -0
  466. package/src/modules/wms/widgets/injection/catalog-inventory-profile/widget.ts +238 -0
  467. package/src/modules/wms/widgets/injection/order-items-stock-column/widget.tsx +153 -0
  468. package/src/modules/wms/widgets/injection/sales-order-stock-context/widget.client.tsx +476 -0
  469. package/src/modules/wms/widgets/injection/sales-order-stock-context/widget.ts +15 -0
  470. package/src/modules/wms/widgets/injection-table.ts +34 -0
  471. package/src/modules/wms/widgets/notifications/WmsLowStockRenderer.tsx +153 -0
  472. package/src/modules/wms/widgets/notifications/WmsReservationShortfallRenderer.tsx +194 -0
  473. package/src/modules/wms/widgets/notifications/index.ts +2 -0
@@ -0,0 +1,1539 @@
1
+ import { LockMode } from "@mikro-orm/core";
2
+ import { randomUUID } from "crypto";
3
+ import { registerCommand } from "@open-mercato/shared/lib/commands";
4
+ import { emitCrudSideEffects } from "@open-mercato/shared/lib/commands/helpers";
5
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
6
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
7
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
+ import { emitWmsEvent } from "../events.js";
9
+ import { E } from "../../../generated/entities.ids.generated.js";
10
+ import {
11
+ InventoryBalance,
12
+ InventoryLot,
13
+ InventoryMovement,
14
+ InventoryReservation,
15
+ ProductInventoryProfile,
16
+ Warehouse,
17
+ WarehouseLocation
18
+ } from "../data/entities.js";
19
+ import {
20
+ inventoryAdjustSchema,
21
+ inventoryCycleCountSchema,
22
+ inventoryMoveSchema,
23
+ inventoryReceiveSchema,
24
+ inventoryReservationAllocateSchema,
25
+ inventoryReservationCreateSchema,
26
+ inventoryReservationReleaseSchema
27
+ } from "../data/validators.js";
28
+ import {
29
+ evaluateLowStock,
30
+ isLotEligible,
31
+ resolveReservationStrategyFromProfile,
32
+ sortBucketsForStrategy
33
+ } from "../lib/inventoryPolicy.js";
34
+ import { enforceInventoryTrackingRequirements } from "../lib/inventoryTrackingValidation.js";
35
+ import {
36
+ buildMovementIdempotencyKey,
37
+ buildReservationIdempotencyKey
38
+ } from "../lib/inventoryIdempotency.js";
39
+ import {
40
+ ensureOrganizationScope,
41
+ ensureTenantScope,
42
+ inventoryBalanceCrudEvents,
43
+ inventoryBalanceCrudIndexer,
44
+ inventoryMovementCrudEvents,
45
+ inventoryMovementCrudIndexer,
46
+ inventoryReservationCrudEvents,
47
+ inventoryReservationCrudIndexer,
48
+ toNumericString,
49
+ WMS_INVENTORY_BALANCE_RESOURCE,
50
+ WMS_INVENTORY_MOVEMENT_RESOURCE,
51
+ WMS_INVENTORY_RESERVATION_RESOURCE
52
+ } from "./shared.js";
53
+ async function emitInventorySideEffects(ctx, affected) {
54
+ let de = null;
55
+ try {
56
+ de = ctx.container.resolve("dataEngine");
57
+ } catch {
58
+ de = null;
59
+ }
60
+ if (!de) return;
61
+ for (const { entity, action } of affected.reservations ?? []) {
62
+ await emitCrudSideEffects({
63
+ dataEngine: de,
64
+ action,
65
+ entity,
66
+ identifiers: {
67
+ id: entity.id,
68
+ organizationId: entity.organizationId,
69
+ tenantId: entity.tenantId
70
+ },
71
+ indexer: inventoryReservationCrudIndexer,
72
+ events: inventoryReservationCrudEvents
73
+ });
74
+ }
75
+ for (const { entity, action } of affected.movements ?? []) {
76
+ await emitCrudSideEffects({
77
+ dataEngine: de,
78
+ action,
79
+ entity,
80
+ identifiers: {
81
+ id: entity.id,
82
+ organizationId: entity.organizationId,
83
+ tenantId: entity.tenantId
84
+ },
85
+ indexer: inventoryMovementCrudIndexer,
86
+ events: inventoryMovementCrudEvents
87
+ });
88
+ }
89
+ for (const { entity, action } of affected.balances ?? []) {
90
+ await emitCrudSideEffects({
91
+ dataEngine: de,
92
+ action,
93
+ entity,
94
+ identifiers: {
95
+ id: entity.id,
96
+ organizationId: entity.organizationId,
97
+ tenantId: entity.tenantId
98
+ },
99
+ indexer: inventoryBalanceCrudIndexer,
100
+ events: inventoryBalanceCrudEvents
101
+ });
102
+ }
103
+ }
104
+ function resolveScope(ctx, fallback) {
105
+ const tenantId = fallback?.tenantId ?? ctx.auth?.tenantId ?? null;
106
+ const organizationId = fallback?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
107
+ if (!tenantId || !organizationId) {
108
+ throw new CrudHttpError(400, { error: "Tenant and organization scope are required." });
109
+ }
110
+ return { tenantId, organizationId };
111
+ }
112
+ function resolveEm(ctx) {
113
+ return ctx.container.resolve("em").fork();
114
+ }
115
+ async function runInTransaction(em, operation) {
116
+ const transactionalEm = em;
117
+ if (typeof transactionalEm.transactional === "function") {
118
+ return transactionalEm.transactional((trx) => operation(trx));
119
+ }
120
+ return operation(em);
121
+ }
122
+ function toNumber(value) {
123
+ if (typeof value === "number" && Number.isFinite(value)) return value;
124
+ if (typeof value === "string" && value.trim().length > 0) {
125
+ const parsed = Number(value);
126
+ if (Number.isFinite(parsed)) return parsed;
127
+ }
128
+ return 0;
129
+ }
130
+ function isUniqueConstraintError(error) {
131
+ if (!error || typeof error !== "object") return false;
132
+ const code = error.code;
133
+ if (code === "23505") return true;
134
+ const name = error.name;
135
+ return name === "UniqueConstraintViolationException";
136
+ }
137
+ async function findExistingMovementByIdempotencyKey(em, scope, idempotencyKey) {
138
+ return findOneWithDecryption(
139
+ em,
140
+ InventoryMovement,
141
+ {
142
+ organizationId: scope.organizationId,
143
+ idempotencyKey,
144
+ deletedAt: null
145
+ },
146
+ void 0,
147
+ scope
148
+ );
149
+ }
150
+ async function findExistingActiveReservationByIdempotencyKey(em, scope, idempotencyKey) {
151
+ return findOneWithDecryption(
152
+ em,
153
+ InventoryReservation,
154
+ {
155
+ organizationId: scope.organizationId,
156
+ idempotencyKey,
157
+ status: "active",
158
+ deletedAt: null
159
+ },
160
+ void 0,
161
+ scope
162
+ );
163
+ }
164
+ function resolveLotFromBalance(balance) {
165
+ const lotRaw = balance.lot ?? null;
166
+ if (!lotRaw || typeof lotRaw === "string") return null;
167
+ return lotRaw;
168
+ }
169
+ function balanceHasEligibleLot(balance, now) {
170
+ const lot = resolveLotFromBalance(balance);
171
+ return isLotEligible(lot, now);
172
+ }
173
+ function setNumeric(target, key, value) {
174
+ target[key] = toNumericString(value);
175
+ }
176
+ function getAvailableQuantity(balance) {
177
+ return toNumber(balance.quantityOnHand) - toNumber(balance.quantityReserved) - toNumber(balance.quantityAllocated);
178
+ }
179
+ function extractReservationMetadata(reservation) {
180
+ if (!reservation.metadata || typeof reservation.metadata !== "object" || Array.isArray(reservation.metadata)) {
181
+ return {};
182
+ }
183
+ return { ...reservation.metadata };
184
+ }
185
+ function buildBucketKey(input) {
186
+ return [
187
+ input.warehouseId,
188
+ input.locationId,
189
+ input.catalogVariantId,
190
+ input.lotId ?? "",
191
+ input.serialNumber ?? ""
192
+ ].join("::");
193
+ }
194
+ function serializeAllocationBuckets(buckets) {
195
+ return buckets.map((bucket) => ({
196
+ locationId: bucket.locationId,
197
+ lotId: bucket.lotId,
198
+ quantity: toNumericString(bucket.quantity)
199
+ }));
200
+ }
201
+ async function buildMutationLog(input) {
202
+ const { translate } = await resolveTranslations();
203
+ const aliases = Array.from(
204
+ new Set((input.cacheAliases ?? []).filter((alias) => typeof alias === "string" && alias.length > 0))
205
+ );
206
+ return {
207
+ actionLabel: translate(input.actionKey, input.fallbackLabel),
208
+ resourceKind: input.resourceKind,
209
+ resourceId: input.resourceId,
210
+ parentResourceKind: input.parentResourceId ? "wms.inventory" : null,
211
+ parentResourceId: input.parentResourceId ?? null,
212
+ tenantId: input.tenantId,
213
+ organizationId: input.organizationId,
214
+ ...aliases.length ? { context: { cacheAliases: aliases } } : {}
215
+ };
216
+ }
217
+ async function requireWarehouse(em, ctx, warehouseId, scope) {
218
+ const resolvedScope = scope ?? resolveScope(ctx);
219
+ const warehouse = await findOneWithDecryption(
220
+ em,
221
+ Warehouse,
222
+ { id: warehouseId, deletedAt: null },
223
+ void 0,
224
+ resolvedScope
225
+ );
226
+ if (!warehouse) throw new CrudHttpError(404, { error: "Warehouse not found." });
227
+ ensureTenantScope(ctx, warehouse.tenantId);
228
+ ensureOrganizationScope(ctx, warehouse.organizationId);
229
+ return warehouse;
230
+ }
231
+ async function requireLocation(em, ctx, locationId, scope) {
232
+ const resolvedScope = scope ?? resolveScope(ctx);
233
+ const location = await findOneWithDecryption(
234
+ em,
235
+ WarehouseLocation,
236
+ { id: locationId, deletedAt: null },
237
+ void 0,
238
+ resolvedScope
239
+ );
240
+ if (!location) throw new CrudHttpError(404, { error: "Warehouse location not found." });
241
+ ensureTenantScope(ctx, location.tenantId);
242
+ ensureOrganizationScope(ctx, location.organizationId);
243
+ return location;
244
+ }
245
+ async function requireReservation(em, ctx, reservationId, scope, lock = false) {
246
+ const resolvedScope = scope ?? resolveScope(ctx);
247
+ const reservation = await findOneWithDecryption(
248
+ em,
249
+ InventoryReservation,
250
+ { id: reservationId, deletedAt: null },
251
+ lock ? { lockMode: LockMode.PESSIMISTIC_WRITE } : void 0,
252
+ resolvedScope
253
+ );
254
+ if (!reservation) throw new CrudHttpError(404, { error: "Inventory reservation not found." });
255
+ ensureTenantScope(ctx, reservation.tenantId);
256
+ ensureOrganizationScope(ctx, reservation.organizationId);
257
+ return reservation;
258
+ }
259
+ async function loadProfileForVariant(em, ctx, catalogVariantId, scope) {
260
+ const resolvedScope = scope ?? resolveScope(ctx);
261
+ const profile = await findOneWithDecryption(
262
+ em,
263
+ ProductInventoryProfile,
264
+ { catalogVariantId, deletedAt: null },
265
+ void 0,
266
+ resolvedScope
267
+ );
268
+ if (!profile) return null;
269
+ ensureTenantScope(ctx, profile.tenantId);
270
+ ensureOrganizationScope(ctx, profile.organizationId);
271
+ return profile;
272
+ }
273
+ async function resolveReceiveLotId(em, ctx, scope, input) {
274
+ if (input.lotId?.trim()) return input.lotId.trim();
275
+ const lotNumber = input.lotNumber?.trim();
276
+ if (!lotNumber) return void 0;
277
+ const existing = await findOneWithDecryption(
278
+ em,
279
+ InventoryLot,
280
+ {
281
+ catalogVariantId: input.catalogVariantId,
282
+ lotNumber,
283
+ deletedAt: null
284
+ },
285
+ void 0,
286
+ scope
287
+ );
288
+ if (existing) return existing.id;
289
+ const queryEngine = ctx.container.resolve("queryEngine");
290
+ const variantResult = await queryEngine.query(E.catalog.catalog_product_variant, {
291
+ tenantId: scope.tenantId,
292
+ organizationId: scope.organizationId,
293
+ filters: { id: { $eq: input.catalogVariantId } },
294
+ fields: ["id", "sku"],
295
+ page: { page: 1, pageSize: 1 }
296
+ });
297
+ const firstItem = variantResult.items?.[0];
298
+ const sku = typeof firstItem === "object" && firstItem !== null ? String(firstItem.sku ?? "").trim() : "";
299
+ if (!sku) {
300
+ throw new CrudHttpError(422, { error: "variant_sku_missing" });
301
+ }
302
+ const lot = em.create(InventoryLot, {
303
+ id: randomUUID(),
304
+ organizationId: scope.organizationId,
305
+ tenantId: scope.tenantId,
306
+ catalogVariantId: input.catalogVariantId,
307
+ sku,
308
+ lotNumber,
309
+ status: "available"
310
+ });
311
+ em.persist(lot);
312
+ return lot.id;
313
+ }
314
+ async function listCandidateBalances(em, ctx, input, scope) {
315
+ const where = {
316
+ warehouse: input.warehouseId,
317
+ catalogVariantId: input.catalogVariantId,
318
+ deletedAt: null
319
+ };
320
+ if (input.lotId) where.lot = input.lotId;
321
+ if (input.serialNumber) where.serialNumber = input.serialNumber;
322
+ const balances = await findWithDecryption(
323
+ em,
324
+ InventoryBalance,
325
+ where,
326
+ {
327
+ populate: ["lot", "location"],
328
+ lockMode: LockMode.PESSIMISTIC_WRITE,
329
+ orderBy: { createdAt: "asc", id: "asc" }
330
+ },
331
+ scope
332
+ );
333
+ for (const balance of balances) {
334
+ ensureTenantScope(ctx, balance.tenantId);
335
+ ensureOrganizationScope(ctx, balance.organizationId);
336
+ }
337
+ return balances;
338
+ }
339
+ async function listBalancesForVariant(em, ctx, catalogVariantId, scope) {
340
+ const balances = await findWithDecryption(
341
+ em,
342
+ InventoryBalance,
343
+ {
344
+ catalogVariantId,
345
+ deletedAt: null
346
+ },
347
+ void 0,
348
+ scope
349
+ );
350
+ for (const balance of balances) {
351
+ ensureTenantScope(ctx, balance.tenantId);
352
+ ensureOrganizationScope(ctx, balance.organizationId);
353
+ }
354
+ return balances;
355
+ }
356
+ async function emitLowStockEventIfNeeded(em, ctx, scope, catalogVariantId) {
357
+ const profile = await loadProfileForVariant(em, ctx, catalogVariantId, scope);
358
+ if (!profile) return;
359
+ const balances = await listBalancesForVariant(em, ctx, catalogVariantId, scope);
360
+ const availableQuantity = balances.reduce((total, balance) => total + getAvailableQuantity(balance), 0);
361
+ const lowStock = evaluateLowStock(profile, availableQuantity);
362
+ if (!lowStock) return;
363
+ await emitWmsEvent("wms.inventory.low_stock", {
364
+ id: catalogVariantId,
365
+ catalogVariantId,
366
+ availableQuantity: lowStock.availableQuantity,
367
+ reorderPoint: lowStock.reorderPoint,
368
+ safetyStock: lowStock.safetyStock,
369
+ state: lowStock.state,
370
+ tenantId: scope.tenantId,
371
+ organizationId: scope.organizationId
372
+ });
373
+ }
374
+ async function loadReceivedAtByBucket(em, balances, scope) {
375
+ if (balances.length === 0) return /* @__PURE__ */ new Map();
376
+ const locationIds = Array.from(
377
+ new Set(
378
+ balances.map((balance) => typeof balance.location === "string" ? balance.location : balance.location.id)
379
+ )
380
+ );
381
+ const warehouseId = typeof balances[0].warehouse === "string" ? balances[0].warehouse : balances[0].warehouse.id;
382
+ const catalogVariantId = balances[0].catalogVariantId;
383
+ const movements = await findWithDecryption(
384
+ em,
385
+ InventoryMovement,
386
+ {
387
+ warehouse: warehouseId,
388
+ catalogVariantId,
389
+ deletedAt: null,
390
+ $or: [
391
+ { locationFrom: { $in: locationIds } },
392
+ { locationTo: { $in: locationIds } }
393
+ ]
394
+ },
395
+ {
396
+ orderBy: { receivedAt: "asc" }
397
+ },
398
+ scope
399
+ );
400
+ const map = /* @__PURE__ */ new Map();
401
+ for (const movement of movements) {
402
+ const locationIdRaw = movement.locationTo ?? movement.locationFrom ?? null;
403
+ const locationId = typeof locationIdRaw === "string" ? locationIdRaw : locationIdRaw?.id ?? null;
404
+ if (!locationId) continue;
405
+ const lotIdRaw = movement.lot ?? null;
406
+ const lotId = typeof lotIdRaw === "string" ? lotIdRaw : lotIdRaw?.id ?? null;
407
+ const key = buildBucketKey({
408
+ warehouseId,
409
+ locationId,
410
+ catalogVariantId: movement.catalogVariantId,
411
+ lotId,
412
+ serialNumber: movement.serialNumber ?? null
413
+ });
414
+ if (!map.has(key)) {
415
+ map.set(key, movement.receivedAt);
416
+ }
417
+ }
418
+ return map;
419
+ }
420
+ function sortBalancesForStrategy(balances, strategy, receiptMap) {
421
+ const rows = balances.map((balance) => {
422
+ const warehouseId = typeof balance.warehouse === "string" ? balance.warehouse : balance.warehouse.id;
423
+ const locationId = typeof balance.location === "string" ? balance.location : balance.location.id;
424
+ const lotIdRaw = balance.lot ?? null;
425
+ const lotId = typeof lotIdRaw === "string" ? lotIdRaw : lotIdRaw?.id ?? null;
426
+ const lot = typeof balance.lot === "string" ? null : balance.lot;
427
+ return {
428
+ balance,
429
+ warehouseId,
430
+ locationId,
431
+ catalogVariantId: balance.catalogVariantId,
432
+ createdAt: balance.createdAt,
433
+ lotId,
434
+ lotExpiresAt: lot?.expiresAt ?? null,
435
+ serialNumber: balance.serialNumber ?? null
436
+ };
437
+ });
438
+ const sorted = sortBucketsForStrategy(rows, strategy, receiptMap);
439
+ return sorted.map((row) => row.balance);
440
+ }
441
+ async function resolveReservationStrategy(em, ctx, input, scope) {
442
+ const profile = await loadProfileForVariant(em, ctx, input.catalogVariantId, scope);
443
+ return resolveReservationStrategyFromProfile(profile, input.strategy);
444
+ }
445
+ function requireSufficientAvailability(remaining) {
446
+ if (remaining > 1e-6) {
447
+ throw new CrudHttpError(409, { error: "insufficient_stock" });
448
+ }
449
+ }
450
+ async function findExactBalanceForUpdate(em, scope, input) {
451
+ return findOneWithDecryption(
452
+ em,
453
+ InventoryBalance,
454
+ {
455
+ warehouse: input.warehouseId,
456
+ location: input.locationId,
457
+ catalogVariantId: input.catalogVariantId,
458
+ lot: input.lotId ?? null,
459
+ serialNumber: input.serialNumber ?? null,
460
+ deletedAt: null
461
+ },
462
+ { lockMode: LockMode.PESSIMISTIC_WRITE },
463
+ scope
464
+ );
465
+ }
466
+ async function findReservationBucketBalance(em, scope, reservation, bucket) {
467
+ const warehouseId = typeof reservation.warehouse === "string" ? reservation.warehouse : reservation.warehouse.id;
468
+ if (typeof bucket.balanceId === "string" && bucket.balanceId.length > 0) {
469
+ const balance = await findOneWithDecryption(
470
+ em,
471
+ InventoryBalance,
472
+ { id: bucket.balanceId, deletedAt: null },
473
+ { lockMode: LockMode.PESSIMISTIC_WRITE },
474
+ scope
475
+ );
476
+ if (balance) return balance;
477
+ }
478
+ return findExactBalanceForUpdate(em, scope, {
479
+ warehouseId,
480
+ locationId: bucket.locationId,
481
+ catalogVariantId: reservation.catalogVariantId,
482
+ lotId: bucket.lotId ?? void 0,
483
+ serialNumber: bucket.serialNumber ?? void 0
484
+ });
485
+ }
486
+ async function upsertBalanceBucket(em, scope, input) {
487
+ const existing = await findExactBalanceForUpdate(em, scope, input);
488
+ if (existing) return { balance: existing, created: false };
489
+ const balance = em.create(InventoryBalance, {
490
+ organizationId: scope.organizationId,
491
+ tenantId: scope.tenantId,
492
+ warehouse: em.getReference(Warehouse, input.warehouseId),
493
+ location: em.getReference(WarehouseLocation, input.locationId),
494
+ catalogVariantId: input.catalogVariantId,
495
+ lot: input.lotId ? em.getReference(InventoryLot, input.lotId) : null,
496
+ serialNumber: input.serialNumber ?? null,
497
+ quantityOnHand: "0",
498
+ quantityReserved: "0",
499
+ quantityAllocated: "0",
500
+ metadata: null
501
+ });
502
+ em.persist(balance);
503
+ await em.flush();
504
+ return { balance, created: true };
505
+ }
506
+ async function resolveReceivedAtForBalance(em, balance, scope, fallback) {
507
+ if (!balance) return fallback;
508
+ const warehouseId = typeof balance.warehouse === "string" ? balance.warehouse : balance.warehouse.id;
509
+ const locationId = typeof balance.location === "string" ? balance.location : balance.location.id;
510
+ const lotIdRaw = balance.lot ?? null;
511
+ const lotId = typeof lotIdRaw === "string" ? lotIdRaw : lotIdRaw?.id ?? null;
512
+ const movement = await findOneWithDecryption(
513
+ em,
514
+ InventoryMovement,
515
+ {
516
+ warehouse: warehouseId,
517
+ catalogVariantId: balance.catalogVariantId,
518
+ lot: lotId,
519
+ serialNumber: balance.serialNumber ?? null,
520
+ deletedAt: null,
521
+ $or: [
522
+ { locationFrom: locationId },
523
+ { locationTo: locationId }
524
+ ]
525
+ },
526
+ { orderBy: { receivedAt: "asc" } },
527
+ scope
528
+ );
529
+ return movement?.receivedAt ?? fallback;
530
+ }
531
+ async function createMovement(em, scope, input) {
532
+ const movement = em.create(InventoryMovement, {
533
+ organizationId: scope.organizationId,
534
+ tenantId: scope.tenantId,
535
+ warehouse: em.getReference(Warehouse, input.warehouseId),
536
+ locationFrom: input.locationFromId ? em.getReference(WarehouseLocation, input.locationFromId) : null,
537
+ locationTo: input.locationToId ? em.getReference(WarehouseLocation, input.locationToId) : null,
538
+ catalogVariantId: input.catalogVariantId,
539
+ lot: input.lotId ? em.getReference(InventoryLot, input.lotId) : null,
540
+ serialNumber: input.serialNumber ?? null,
541
+ quantity: toNumericString(input.quantity),
542
+ type: input.type,
543
+ referenceType: input.referenceType,
544
+ referenceId: input.referenceId,
545
+ performedBy: input.performedBy,
546
+ performedAt: input.performedAt,
547
+ receivedAt: input.receivedAt,
548
+ reason: input.reason ?? null,
549
+ reasonCode: input.reasonCode ?? null,
550
+ metadata: input.metadata ?? null,
551
+ idempotencyKey: input.idempotencyKey
552
+ });
553
+ em.persist(movement);
554
+ return movement;
555
+ }
556
+ async function persistMovementWithIdempotency(em, scope, input) {
557
+ const idempotencyKey = buildMovementIdempotencyKey({
558
+ referenceType: input.referenceType,
559
+ referenceId: input.referenceId,
560
+ type: input.type,
561
+ warehouseId: input.warehouseId,
562
+ locationFromId: input.locationFromId,
563
+ locationToId: input.locationToId,
564
+ catalogVariantId: input.catalogVariantId,
565
+ lotId: input.lotId,
566
+ serialNumber: input.serialNumber,
567
+ quantity: input.quantity
568
+ });
569
+ const existing = await findExistingMovementByIdempotencyKey(em, scope, idempotencyKey);
570
+ if (existing) {
571
+ return { movement: existing, idempotentReplay: true };
572
+ }
573
+ try {
574
+ const movement = await createMovement(em, scope, {
575
+ ...input,
576
+ idempotencyKey
577
+ });
578
+ return { movement, idempotentReplay: false };
579
+ } catch (error) {
580
+ if (!isUniqueConstraintError(error)) throw error;
581
+ const raced = await findExistingMovementByIdempotencyKey(em, scope, idempotencyKey);
582
+ if (!raced) throw error;
583
+ return { movement: raced, idempotentReplay: true };
584
+ }
585
+ }
586
+ async function emitBalanceDriftIfNeeded(balance, field, attemptedValue, scope, reservationId) {
587
+ if (attemptedValue >= -1e-6) return;
588
+ await emitWmsEvent("wms.inventory.balance_drift", {
589
+ id: balance.id,
590
+ balanceId: balance.id,
591
+ reservationId,
592
+ field,
593
+ attemptedValue: toNumericString(attemptedValue),
594
+ clampedValue: "0",
595
+ tenantId: scope.tenantId,
596
+ organizationId: scope.organizationId
597
+ }).catch(() => void 0);
598
+ }
599
+ function enforceNonNegativeBalance(balance, field, nextValue, reservationId, scope) {
600
+ if (nextValue < -1e-6) {
601
+ void emitBalanceDriftIfNeeded(balance, field, nextValue, scope, reservationId);
602
+ throw new CrudHttpError(409, { error: "balance_integrity_violation" });
603
+ }
604
+ setNumeric(balance, field, nextValue);
605
+ }
606
+ const reserveInventoryCommand = {
607
+ id: "wms.inventory.reserve",
608
+ // See "WMS Inventory Mutation Commands — Undo Policy" at top of file.
609
+ isUndoable: false,
610
+ async execute(rawInput, ctx) {
611
+ const input = inventoryReservationCreateSchema.parse(rawInput ?? {});
612
+ ensureTenantScope(ctx, input.tenantId);
613
+ ensureOrganizationScope(ctx, input.organizationId);
614
+ const em = resolveEm(ctx);
615
+ const result = await runInTransaction(em, async (trx) => {
616
+ const scope = resolveScope(ctx, input);
617
+ await requireWarehouse(trx, ctx, input.warehouseId, scope);
618
+ const reservationIdempotencyKey = buildReservationIdempotencyKey({
619
+ sourceType: input.sourceType,
620
+ sourceId: input.sourceId,
621
+ catalogVariantId: input.catalogVariantId,
622
+ warehouseId: input.warehouseId,
623
+ quantity: input.quantity
624
+ });
625
+ const existingReservation = await findExistingActiveReservationByIdempotencyKey(
626
+ trx,
627
+ scope,
628
+ reservationIdempotencyKey
629
+ );
630
+ if (existingReservation) {
631
+ const metadata = extractReservationMetadata(existingReservation);
632
+ const buckets2 = Array.isArray(metadata.allocatedBuckets) ? metadata.allocatedBuckets : [];
633
+ return {
634
+ reservationId: existingReservation.id,
635
+ allocatedBuckets: serializeAllocationBuckets(buckets2),
636
+ tenantId: scope.tenantId,
637
+ organizationId: scope.organizationId,
638
+ warehouseId: input.warehouseId,
639
+ catalogVariantId: input.catalogVariantId,
640
+ quantity: input.quantity,
641
+ reservationEntity: existingReservation,
642
+ touchedBalances: [],
643
+ idempotentReplay: true
644
+ };
645
+ }
646
+ const strategy = await resolveReservationStrategy(trx, ctx, input, scope);
647
+ const balances = await listCandidateBalances(trx, ctx, input, scope);
648
+ const receiptMap = await loadReceivedAtByBucket(trx, balances, scope);
649
+ const now = /* @__PURE__ */ new Date();
650
+ const ordered = sortBalancesForStrategy(
651
+ balances.filter(
652
+ (balance) => balanceHasEligibleLot(balance, now) && getAvailableQuantity(balance) > 0
653
+ ),
654
+ strategy,
655
+ receiptMap
656
+ );
657
+ let remaining = input.quantity;
658
+ const buckets = [];
659
+ const touchedBalances = [];
660
+ for (const balance of ordered) {
661
+ if (remaining <= 0) break;
662
+ const available = getAvailableQuantity(balance);
663
+ if (available <= 0) continue;
664
+ const quantity = Math.min(available, remaining);
665
+ const locationId = typeof balance.location === "string" ? balance.location : balance.location.id;
666
+ const lotIdRaw = balance.lot ?? null;
667
+ const lotId = typeof lotIdRaw === "string" ? lotIdRaw : lotIdRaw?.id ?? null;
668
+ const persistedBalance = await findExactBalanceForUpdate(trx, scope, {
669
+ warehouseId: input.warehouseId,
670
+ locationId,
671
+ catalogVariantId: input.catalogVariantId,
672
+ lotId: lotId ?? void 0,
673
+ serialNumber: balance.serialNumber ?? void 0
674
+ });
675
+ if (!persistedBalance) {
676
+ throw new CrudHttpError(409, { error: "invalid_tracking_state" });
677
+ }
678
+ setNumeric(
679
+ persistedBalance,
680
+ "quantityReserved",
681
+ toNumber(persistedBalance.quantityReserved) + quantity
682
+ );
683
+ touchedBalances.push(persistedBalance);
684
+ buckets.push({
685
+ balanceId: persistedBalance.id,
686
+ locationId,
687
+ lotId,
688
+ serialNumber: balance.serialNumber ?? null,
689
+ quantity
690
+ });
691
+ remaining -= quantity;
692
+ }
693
+ requireSufficientAvailability(remaining);
694
+ let reservation;
695
+ try {
696
+ reservation = trx.create(InventoryReservation, {
697
+ organizationId: scope.organizationId,
698
+ tenantId: scope.tenantId,
699
+ warehouse: trx.getReference(Warehouse, input.warehouseId),
700
+ catalogVariantId: input.catalogVariantId,
701
+ lot: buckets.length === 1 && buckets[0].lotId ? trx.getReference(InventoryLot, buckets[0].lotId) : null,
702
+ serialNumber: buckets.length === 1 ? buckets[0].serialNumber : null,
703
+ quantity: toNumericString(input.quantity),
704
+ sourceType: input.sourceType,
705
+ sourceId: input.sourceId,
706
+ expiresAt: input.expiresAt ?? null,
707
+ status: "active",
708
+ idempotencyKey: reservationIdempotencyKey,
709
+ metadata: {
710
+ ...input.metadata ?? {},
711
+ allocatedBuckets: buckets,
712
+ allocationState: "reserved",
713
+ strategy
714
+ }
715
+ });
716
+ trx.persist(reservation);
717
+ await trx.flush();
718
+ } catch (error) {
719
+ if (!isUniqueConstraintError(error)) throw error;
720
+ const raced = await findExistingActiveReservationByIdempotencyKey(
721
+ trx,
722
+ scope,
723
+ reservationIdempotencyKey
724
+ );
725
+ if (!raced) throw error;
726
+ const metadata = extractReservationMetadata(raced);
727
+ const racedBuckets = Array.isArray(metadata.allocatedBuckets) ? metadata.allocatedBuckets : [];
728
+ return {
729
+ reservationId: raced.id,
730
+ allocatedBuckets: serializeAllocationBuckets(racedBuckets),
731
+ tenantId: scope.tenantId,
732
+ organizationId: scope.organizationId,
733
+ warehouseId: input.warehouseId,
734
+ catalogVariantId: input.catalogVariantId,
735
+ quantity: input.quantity,
736
+ reservationEntity: raced,
737
+ touchedBalances: [],
738
+ idempotentReplay: true
739
+ };
740
+ }
741
+ return {
742
+ reservationId: reservation.id,
743
+ allocatedBuckets: serializeAllocationBuckets(buckets),
744
+ tenantId: scope.tenantId,
745
+ organizationId: scope.organizationId,
746
+ warehouseId: input.warehouseId,
747
+ catalogVariantId: input.catalogVariantId,
748
+ quantity: input.quantity,
749
+ reservationEntity: reservation,
750
+ touchedBalances,
751
+ idempotentReplay: false
752
+ };
753
+ });
754
+ if (!result.idempotentReplay) {
755
+ await emitInventorySideEffects(ctx, {
756
+ reservations: [{ entity: result.reservationEntity, action: "created" }],
757
+ balances: result.touchedBalances.map((entity) => ({ entity, action: "updated" }))
758
+ });
759
+ void emitWmsEvent("wms.inventory.reserved", {
760
+ id: result.reservationId,
761
+ reservationId: result.reservationId,
762
+ warehouseId: result.warehouseId,
763
+ catalogVariantId: result.catalogVariantId,
764
+ quantity: toNumericString(result.quantity),
765
+ tenantId: result.tenantId,
766
+ organizationId: result.organizationId
767
+ }).catch(() => void 0);
768
+ void emitLowStockEventIfNeeded(
769
+ resolveEm(ctx),
770
+ ctx,
771
+ { tenantId: result.tenantId, organizationId: result.organizationId },
772
+ result.catalogVariantId
773
+ ).catch(() => void 0);
774
+ }
775
+ return {
776
+ reservationId: result.reservationId,
777
+ allocatedBuckets: result.allocatedBuckets
778
+ };
779
+ },
780
+ buildLog: async ({ input, result, ctx }) => buildMutationLog({
781
+ actionKey: "wms.audit.inventory.reserve",
782
+ fallbackLabel: "Reserve inventory",
783
+ resourceKind: WMS_INVENTORY_RESERVATION_RESOURCE,
784
+ resourceId: result?.reservationId ?? null,
785
+ parentResourceId: input?.warehouseId && input?.catalogVariantId ? `${input.warehouseId}:${input.catalogVariantId}` : null,
786
+ tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,
787
+ organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
788
+ cacheAliases: [WMS_INVENTORY_BALANCE_RESOURCE]
789
+ })
790
+ };
791
+ const releaseInventoryReservationCommand = {
792
+ id: "wms.inventory.release",
793
+ // See "WMS Inventory Mutation Commands — Undo Policy" at top of file.
794
+ isUndoable: false,
795
+ async execute(rawInput, ctx) {
796
+ const input = inventoryReservationReleaseSchema.parse(rawInput ?? {});
797
+ ensureTenantScope(ctx, input.tenantId);
798
+ ensureOrganizationScope(ctx, input.organizationId);
799
+ const em = resolveEm(ctx);
800
+ const result = await runInTransaction(em, async (trx) => {
801
+ const scope = resolveScope(ctx, input);
802
+ const reservation = await requireReservation(trx, ctx, input.reservationId, scope, true);
803
+ const metadata = extractReservationMetadata(reservation);
804
+ const buckets = Array.isArray(metadata.allocatedBuckets) ? metadata.allocatedBuckets : [];
805
+ const allocationState = metadata.allocationState ?? "reserved";
806
+ const touchedBalances = [];
807
+ for (const bucket of buckets) {
808
+ const balance = await findReservationBucketBalance(
809
+ trx,
810
+ scope,
811
+ reservation,
812
+ bucket
813
+ );
814
+ if (!balance) continue;
815
+ if (allocationState === "allocated") {
816
+ enforceNonNegativeBalance(
817
+ balance,
818
+ "quantityAllocated",
819
+ toNumber(balance.quantityAllocated) - bucket.quantity,
820
+ reservation.id,
821
+ scope
822
+ );
823
+ } else {
824
+ enforceNonNegativeBalance(
825
+ balance,
826
+ "quantityReserved",
827
+ toNumber(balance.quantityReserved) - bucket.quantity,
828
+ reservation.id,
829
+ scope
830
+ );
831
+ }
832
+ touchedBalances.push(balance);
833
+ }
834
+ reservation.status = "released";
835
+ reservation.metadata = {
836
+ ...metadata,
837
+ releasedReason: input.reason,
838
+ releasedAt: (/* @__PURE__ */ new Date()).toISOString()
839
+ };
840
+ await trx.flush();
841
+ return {
842
+ reservationId: reservation.id,
843
+ warehouseId: typeof reservation.warehouse === "string" ? reservation.warehouse : reservation.warehouse.id,
844
+ catalogVariantId: reservation.catalogVariantId,
845
+ quantity: reservation.quantity,
846
+ tenantId: reservation.tenantId,
847
+ organizationId: reservation.organizationId,
848
+ reservationEntity: reservation,
849
+ touchedBalances
850
+ };
851
+ });
852
+ await emitInventorySideEffects(ctx, {
853
+ reservations: [{ entity: result.reservationEntity, action: "updated" }],
854
+ balances: result.touchedBalances.map((entity) => ({ entity, action: "updated" }))
855
+ });
856
+ void emitWmsEvent("wms.inventory.released", {
857
+ id: result.reservationId,
858
+ reservationId: result.reservationId,
859
+ warehouseId: result.warehouseId,
860
+ catalogVariantId: result.catalogVariantId,
861
+ quantity: result.quantity,
862
+ tenantId: result.tenantId,
863
+ organizationId: result.organizationId
864
+ }).catch(() => void 0);
865
+ void emitLowStockEventIfNeeded(
866
+ resolveEm(ctx),
867
+ ctx,
868
+ { tenantId: result.tenantId, organizationId: result.organizationId },
869
+ result.catalogVariantId
870
+ ).catch(() => void 0);
871
+ return { reservationId: result.reservationId };
872
+ },
873
+ buildLog: async ({ input, result, ctx }) => buildMutationLog({
874
+ actionKey: "wms.audit.inventory.release",
875
+ fallbackLabel: "Release inventory reservation",
876
+ resourceKind: WMS_INVENTORY_RESERVATION_RESOURCE,
877
+ resourceId: result?.reservationId ?? input?.reservationId ?? null,
878
+ tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,
879
+ organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
880
+ cacheAliases: [WMS_INVENTORY_BALANCE_RESOURCE]
881
+ })
882
+ };
883
+ const allocateInventoryReservationCommand = {
884
+ id: "wms.inventory.allocate",
885
+ // See "WMS Inventory Mutation Commands — Undo Policy" at top of file.
886
+ isUndoable: false,
887
+ async execute(rawInput, ctx) {
888
+ const input = inventoryReservationAllocateSchema.parse(rawInput ?? {});
889
+ ensureTenantScope(ctx, input.tenantId);
890
+ ensureOrganizationScope(ctx, input.organizationId);
891
+ const em = resolveEm(ctx);
892
+ const result = await runInTransaction(em, async (trx) => {
893
+ const scope = resolveScope(ctx, input);
894
+ const reservation = await requireReservation(trx, ctx, input.reservationId, scope, true);
895
+ const metadata = extractReservationMetadata(reservation);
896
+ if (metadata.allocationState === "allocated") {
897
+ return {
898
+ reservationId: reservation.id,
899
+ allocationState: "allocated",
900
+ warehouseId: typeof reservation.warehouse === "string" ? reservation.warehouse : reservation.warehouse.id,
901
+ catalogVariantId: reservation.catalogVariantId,
902
+ quantity: reservation.quantity,
903
+ tenantId: reservation.tenantId,
904
+ organizationId: reservation.organizationId,
905
+ reservationEntity: reservation,
906
+ touchedBalances: []
907
+ };
908
+ }
909
+ const buckets = Array.isArray(metadata.allocatedBuckets) ? metadata.allocatedBuckets : [];
910
+ const touchedBalances = [];
911
+ for (const bucket of buckets) {
912
+ const balance = await findReservationBucketBalance(
913
+ trx,
914
+ scope,
915
+ reservation,
916
+ bucket
917
+ );
918
+ if (!balance) throw new CrudHttpError(409, { error: "invalid_tracking_state" });
919
+ if (toNumber(balance.quantityReserved) < bucket.quantity - 1e-6) {
920
+ throw new CrudHttpError(409, { error: "invalid_tracking_state" });
921
+ }
922
+ setNumeric(
923
+ balance,
924
+ "quantityReserved",
925
+ toNumber(balance.quantityReserved) - bucket.quantity
926
+ );
927
+ setNumeric(
928
+ balance,
929
+ "quantityAllocated",
930
+ toNumber(balance.quantityAllocated) + bucket.quantity
931
+ );
932
+ touchedBalances.push(balance);
933
+ }
934
+ reservation.metadata = {
935
+ ...metadata,
936
+ allocationState: "allocated",
937
+ allocatedAt: (/* @__PURE__ */ new Date()).toISOString()
938
+ };
939
+ await trx.flush();
940
+ return {
941
+ reservationId: reservation.id,
942
+ allocationState: "allocated",
943
+ warehouseId: typeof reservation.warehouse === "string" ? reservation.warehouse : reservation.warehouse.id,
944
+ catalogVariantId: reservation.catalogVariantId,
945
+ quantity: reservation.quantity,
946
+ tenantId: reservation.tenantId,
947
+ organizationId: reservation.organizationId,
948
+ reservationEntity: reservation,
949
+ touchedBalances
950
+ };
951
+ });
952
+ await emitInventorySideEffects(ctx, {
953
+ reservations: [{ entity: result.reservationEntity, action: "updated" }],
954
+ balances: result.touchedBalances.map((entity) => ({ entity, action: "updated" }))
955
+ });
956
+ void emitWmsEvent("wms.inventory.allocated", {
957
+ id: result.reservationId,
958
+ reservationId: result.reservationId,
959
+ warehouseId: result.warehouseId,
960
+ catalogVariantId: result.catalogVariantId,
961
+ quantity: result.quantity,
962
+ tenantId: result.tenantId,
963
+ organizationId: result.organizationId
964
+ }).catch(() => void 0);
965
+ return { reservationId: result.reservationId, allocationState: result.allocationState };
966
+ },
967
+ buildLog: async ({ input, result, ctx }) => buildMutationLog({
968
+ actionKey: "wms.audit.inventory.allocate",
969
+ fallbackLabel: "Allocate inventory reservation",
970
+ resourceKind: WMS_INVENTORY_RESERVATION_RESOURCE,
971
+ resourceId: result?.reservationId ?? input?.reservationId ?? null,
972
+ tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,
973
+ organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
974
+ cacheAliases: [WMS_INVENTORY_BALANCE_RESOURCE]
975
+ })
976
+ };
977
+ const adjustInventoryCommand = {
978
+ id: "wms.inventory.adjust",
979
+ // See "WMS Inventory Mutation Commands — Undo Policy" at top of file.
980
+ isUndoable: false,
981
+ async execute(rawInput, ctx) {
982
+ const input = inventoryAdjustSchema.parse(rawInput ?? {});
983
+ ensureTenantScope(ctx, input.tenantId);
984
+ ensureOrganizationScope(ctx, input.organizationId);
985
+ const em = resolveEm(ctx);
986
+ const result = await runInTransaction(em, async (trx) => {
987
+ const scope = resolveScope(ctx, input);
988
+ await requireWarehouse(trx, ctx, input.warehouseId, scope);
989
+ const location = await requireLocation(trx, ctx, input.locationId, scope);
990
+ const locationWarehouseId = typeof location.warehouse === "string" ? location.warehouse : location.warehouse.id;
991
+ if (locationWarehouseId !== input.warehouseId) {
992
+ throw new CrudHttpError(422, { error: "invalid_location" });
993
+ }
994
+ const { balance, created: balanceWasNew } = await upsertBalanceBucket(trx, scope, {
995
+ warehouseId: input.warehouseId,
996
+ locationId: input.locationId,
997
+ catalogVariantId: input.catalogVariantId,
998
+ lotId: input.lotId,
999
+ serialNumber: input.serialNumber
1000
+ });
1001
+ const delta = input.delta;
1002
+ const performedAt = input.performedAt ?? /* @__PURE__ */ new Date();
1003
+ const movementInput = {
1004
+ warehouseId: input.warehouseId,
1005
+ locationToId: input.locationId,
1006
+ catalogVariantId: input.catalogVariantId,
1007
+ lotId: input.lotId ?? null,
1008
+ serialNumber: input.serialNumber ?? null,
1009
+ quantity: delta,
1010
+ type: "adjust",
1011
+ referenceType: input.referenceType,
1012
+ referenceId: input.referenceId,
1013
+ performedBy: input.performedBy,
1014
+ performedAt,
1015
+ receivedAt: performedAt,
1016
+ reason: input.reason,
1017
+ reasonCode: input.reasonCode ?? null,
1018
+ metadata: input.metadata ?? null
1019
+ };
1020
+ const idempotencyKey = buildMovementIdempotencyKey({
1021
+ referenceType: movementInput.referenceType,
1022
+ referenceId: movementInput.referenceId,
1023
+ type: movementInput.type,
1024
+ warehouseId: movementInput.warehouseId,
1025
+ locationFromId: movementInput.locationFromId,
1026
+ locationToId: movementInput.locationToId,
1027
+ catalogVariantId: movementInput.catalogVariantId,
1028
+ lotId: movementInput.lotId,
1029
+ serialNumber: movementInput.serialNumber,
1030
+ quantity: movementInput.quantity
1031
+ });
1032
+ const existingMovement = await findExistingMovementByIdempotencyKey(trx, scope, idempotencyKey);
1033
+ if (existingMovement) {
1034
+ return {
1035
+ movementId: existingMovement.id,
1036
+ warehouseId: input.warehouseId,
1037
+ catalogVariantId: input.catalogVariantId,
1038
+ quantity: delta,
1039
+ tenantId: scope.tenantId,
1040
+ organizationId: scope.organizationId,
1041
+ movementEntity: existingMovement,
1042
+ balanceEntity: balance,
1043
+ balanceAction: "updated",
1044
+ idempotentReplay: true
1045
+ };
1046
+ }
1047
+ if (delta < 0 && getAvailableQuantity(balance) < Math.abs(delta) - 1e-6) {
1048
+ throw new CrudHttpError(409, { error: "insufficient_stock" });
1049
+ }
1050
+ setNumeric(
1051
+ balance,
1052
+ "quantityOnHand",
1053
+ toNumber(balance.quantityOnHand) + delta
1054
+ );
1055
+ const { movement } = await persistMovementWithIdempotency(trx, scope, movementInput);
1056
+ await trx.flush();
1057
+ return {
1058
+ movementId: movement.id,
1059
+ warehouseId: input.warehouseId,
1060
+ catalogVariantId: input.catalogVariantId,
1061
+ quantity: delta,
1062
+ tenantId: scope.tenantId,
1063
+ organizationId: scope.organizationId,
1064
+ movementEntity: movement,
1065
+ balanceEntity: balance,
1066
+ balanceAction: balanceWasNew ? "created" : "updated",
1067
+ idempotentReplay: false
1068
+ };
1069
+ });
1070
+ if (!result.idempotentReplay) {
1071
+ await emitInventorySideEffects(ctx, {
1072
+ movements: [{ entity: result.movementEntity, action: "created" }],
1073
+ balances: [{ entity: result.balanceEntity, action: result.balanceAction }]
1074
+ });
1075
+ void emitWmsEvent("wms.inventory.adjusted", {
1076
+ id: result.movementId,
1077
+ movementId: result.movementId,
1078
+ warehouseId: result.warehouseId,
1079
+ catalogVariantId: result.catalogVariantId,
1080
+ quantity: toNumericString(result.quantity),
1081
+ tenantId: result.tenantId,
1082
+ organizationId: result.organizationId
1083
+ }).catch(() => void 0);
1084
+ void emitLowStockEventIfNeeded(
1085
+ resolveEm(ctx),
1086
+ ctx,
1087
+ { tenantId: result.tenantId, organizationId: result.organizationId },
1088
+ result.catalogVariantId
1089
+ ).catch(() => void 0);
1090
+ }
1091
+ return { movementId: result.movementId };
1092
+ },
1093
+ buildLog: async ({ input, result, ctx }) => buildMutationLog({
1094
+ actionKey: "wms.audit.inventory.adjust",
1095
+ fallbackLabel: "Adjust inventory",
1096
+ resourceKind: WMS_INVENTORY_MOVEMENT_RESOURCE,
1097
+ resourceId: result?.movementId ?? null,
1098
+ parentResourceId: input?.warehouseId && input?.catalogVariantId ? `${input.warehouseId}:${input.catalogVariantId}` : null,
1099
+ tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,
1100
+ organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
1101
+ cacheAliases: [WMS_INVENTORY_BALANCE_RESOURCE]
1102
+ })
1103
+ };
1104
+ const receiveInventoryCommand = {
1105
+ id: "wms.inventory.receive",
1106
+ // See "WMS Inventory Mutation Commands — Undo Policy" at top of file.
1107
+ isUndoable: false,
1108
+ async execute(rawInput, ctx) {
1109
+ const input = inventoryReceiveSchema.parse(rawInput ?? {});
1110
+ ensureTenantScope(ctx, input.tenantId);
1111
+ ensureOrganizationScope(ctx, input.organizationId);
1112
+ const em = resolveEm(ctx);
1113
+ const result = await runInTransaction(em, async (trx) => {
1114
+ const scope = resolveScope(ctx, input);
1115
+ await requireWarehouse(trx, ctx, input.warehouseId, scope);
1116
+ const location = await requireLocation(trx, ctx, input.locationId, scope);
1117
+ const locationWarehouseId = typeof location.warehouse === "string" ? location.warehouse : location.warehouse.id;
1118
+ if (locationWarehouseId !== input.warehouseId) {
1119
+ throw new CrudHttpError(422, { error: "invalid_location" });
1120
+ }
1121
+ const profile = await loadProfileForVariant(trx, ctx, input.catalogVariantId, scope);
1122
+ const resolvedLotId = await resolveReceiveLotId(trx, ctx, scope, input);
1123
+ enforceInventoryTrackingRequirements(profile, {
1124
+ lotId: resolvedLotId ?? null,
1125
+ serialNumber: input.serialNumber ?? null
1126
+ });
1127
+ const { balance, created: balanceWasNew } = await upsertBalanceBucket(trx, scope, {
1128
+ warehouseId: input.warehouseId,
1129
+ locationId: input.locationId,
1130
+ catalogVariantId: input.catalogVariantId,
1131
+ lotId: resolvedLotId,
1132
+ serialNumber: input.serialNumber
1133
+ });
1134
+ const receivedAt = input.receivedAt ?? input.performedAt ?? /* @__PURE__ */ new Date();
1135
+ const performedAt = input.performedAt ?? receivedAt;
1136
+ const movementInput = {
1137
+ warehouseId: input.warehouseId,
1138
+ locationToId: input.locationId,
1139
+ catalogVariantId: input.catalogVariantId,
1140
+ lotId: resolvedLotId ?? null,
1141
+ serialNumber: input.serialNumber ?? null,
1142
+ quantity: input.quantity,
1143
+ type: "receipt",
1144
+ referenceType: input.referenceType,
1145
+ referenceId: input.referenceId,
1146
+ performedBy: input.performedBy,
1147
+ performedAt,
1148
+ receivedAt,
1149
+ reason: input.reason,
1150
+ metadata: input.metadata ?? null
1151
+ };
1152
+ const idempotencyKey = buildMovementIdempotencyKey({
1153
+ referenceType: movementInput.referenceType,
1154
+ referenceId: movementInput.referenceId,
1155
+ type: movementInput.type,
1156
+ warehouseId: movementInput.warehouseId,
1157
+ locationFromId: movementInput.locationFromId,
1158
+ locationToId: movementInput.locationToId,
1159
+ catalogVariantId: movementInput.catalogVariantId,
1160
+ lotId: movementInput.lotId,
1161
+ serialNumber: movementInput.serialNumber,
1162
+ quantity: movementInput.quantity
1163
+ });
1164
+ const existingMovement = await findExistingMovementByIdempotencyKey(trx, scope, idempotencyKey);
1165
+ if (existingMovement) {
1166
+ return {
1167
+ movementId: existingMovement.id,
1168
+ warehouseId: input.warehouseId,
1169
+ locationId: input.locationId,
1170
+ catalogVariantId: input.catalogVariantId,
1171
+ quantity: input.quantity,
1172
+ tenantId: scope.tenantId,
1173
+ organizationId: scope.organizationId,
1174
+ movementEntity: existingMovement,
1175
+ balanceEntity: balance,
1176
+ balanceAction: "updated",
1177
+ idempotentReplay: true
1178
+ };
1179
+ }
1180
+ setNumeric(
1181
+ balance,
1182
+ "quantityOnHand",
1183
+ toNumber(balance.quantityOnHand) + input.quantity
1184
+ );
1185
+ const { movement } = await persistMovementWithIdempotency(trx, scope, movementInput);
1186
+ await trx.flush();
1187
+ return {
1188
+ movementId: movement.id,
1189
+ warehouseId: input.warehouseId,
1190
+ locationId: input.locationId,
1191
+ catalogVariantId: input.catalogVariantId,
1192
+ quantity: input.quantity,
1193
+ tenantId: scope.tenantId,
1194
+ organizationId: scope.organizationId,
1195
+ movementEntity: movement,
1196
+ balanceEntity: balance,
1197
+ balanceAction: balanceWasNew ? "created" : "updated",
1198
+ idempotentReplay: false
1199
+ };
1200
+ });
1201
+ if (!result.idempotentReplay) {
1202
+ await emitInventorySideEffects(ctx, {
1203
+ movements: [{ entity: result.movementEntity, action: "created" }],
1204
+ balances: [{ entity: result.balanceEntity, action: result.balanceAction }]
1205
+ });
1206
+ void emitWmsEvent("wms.inventory.received", {
1207
+ id: result.movementId,
1208
+ movementId: result.movementId,
1209
+ warehouseId: result.warehouseId,
1210
+ locationId: result.locationId,
1211
+ catalogVariantId: result.catalogVariantId,
1212
+ quantity: toNumericString(result.quantity),
1213
+ tenantId: result.tenantId,
1214
+ organizationId: result.organizationId
1215
+ }).catch(() => void 0);
1216
+ void emitLowStockEventIfNeeded(
1217
+ resolveEm(ctx),
1218
+ ctx,
1219
+ { tenantId: result.tenantId, organizationId: result.organizationId },
1220
+ result.catalogVariantId
1221
+ ).catch(() => void 0);
1222
+ }
1223
+ return { movementId: result.movementId };
1224
+ },
1225
+ buildLog: async ({ input, result, ctx }) => buildMutationLog({
1226
+ actionKey: "wms.audit.inventory.receive",
1227
+ fallbackLabel: "Receive inventory",
1228
+ resourceKind: WMS_INVENTORY_MOVEMENT_RESOURCE,
1229
+ resourceId: result?.movementId ?? null,
1230
+ parentResourceId: input?.warehouseId && input?.catalogVariantId ? `${input.warehouseId}:${input.catalogVariantId}` : null,
1231
+ tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,
1232
+ organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
1233
+ cacheAliases: [WMS_INVENTORY_BALANCE_RESOURCE]
1234
+ })
1235
+ };
1236
+ const moveInventoryCommand = {
1237
+ id: "wms.inventory.move",
1238
+ // See "WMS Inventory Mutation Commands — Undo Policy" at top of file.
1239
+ isUndoable: false,
1240
+ async execute(rawInput, ctx) {
1241
+ const input = inventoryMoveSchema.parse(rawInput ?? {});
1242
+ ensureTenantScope(ctx, input.tenantId);
1243
+ ensureOrganizationScope(ctx, input.organizationId);
1244
+ const em = resolveEm(ctx);
1245
+ const result = await runInTransaction(em, async (trx) => {
1246
+ const scope = resolveScope(ctx, input);
1247
+ await requireWarehouse(trx, ctx, input.warehouseId, scope);
1248
+ const sourceLocation = await requireLocation(trx, ctx, input.fromLocationId, scope);
1249
+ const targetLocation = await requireLocation(trx, ctx, input.toLocationId, scope);
1250
+ const sourceWarehouseId = typeof sourceLocation.warehouse === "string" ? sourceLocation.warehouse : sourceLocation.warehouse.id;
1251
+ const targetWarehouseId = typeof targetLocation.warehouse === "string" ? targetLocation.warehouse : targetLocation.warehouse.id;
1252
+ if (sourceWarehouseId !== input.warehouseId || targetWarehouseId !== input.warehouseId) {
1253
+ throw new CrudHttpError(422, { error: "invalid_location" });
1254
+ }
1255
+ const sourceResult = await upsertBalanceBucket(trx, scope, {
1256
+ warehouseId: input.warehouseId,
1257
+ locationId: input.fromLocationId,
1258
+ catalogVariantId: input.catalogVariantId,
1259
+ lotId: input.lotId,
1260
+ serialNumber: input.serialNumber
1261
+ });
1262
+ const sourceBalance = sourceResult.balance;
1263
+ const sourceBalanceAction = sourceResult.created ? "created" : "updated";
1264
+ const targetResult = await upsertBalanceBucket(trx, scope, {
1265
+ warehouseId: input.warehouseId,
1266
+ locationId: input.toLocationId,
1267
+ catalogVariantId: input.catalogVariantId,
1268
+ lotId: input.lotId,
1269
+ serialNumber: input.serialNumber
1270
+ });
1271
+ const targetBalance = targetResult.balance;
1272
+ const targetBalanceAction = targetResult.created ? "created" : "updated";
1273
+ const performedAt = input.performedAt ?? /* @__PURE__ */ new Date();
1274
+ const receivedAt = await resolveReceivedAtForBalance(trx, sourceBalance, scope, performedAt);
1275
+ const movementInput = {
1276
+ warehouseId: input.warehouseId,
1277
+ locationFromId: input.fromLocationId,
1278
+ locationToId: input.toLocationId,
1279
+ catalogVariantId: input.catalogVariantId,
1280
+ lotId: input.lotId ?? null,
1281
+ serialNumber: input.serialNumber ?? null,
1282
+ quantity: input.quantity,
1283
+ type: input.type ?? "transfer",
1284
+ referenceType: input.referenceType,
1285
+ referenceId: input.referenceId,
1286
+ performedBy: input.performedBy,
1287
+ performedAt,
1288
+ receivedAt,
1289
+ reason: input.reason,
1290
+ reasonCode: input.reasonCode ?? null,
1291
+ metadata: input.metadata ?? null
1292
+ };
1293
+ const idempotencyKey = buildMovementIdempotencyKey({
1294
+ referenceType: movementInput.referenceType,
1295
+ referenceId: movementInput.referenceId,
1296
+ type: movementInput.type,
1297
+ warehouseId: movementInput.warehouseId,
1298
+ locationFromId: movementInput.locationFromId,
1299
+ locationToId: movementInput.locationToId,
1300
+ catalogVariantId: movementInput.catalogVariantId,
1301
+ lotId: movementInput.lotId,
1302
+ serialNumber: movementInput.serialNumber,
1303
+ quantity: movementInput.quantity
1304
+ });
1305
+ const existingMovement = await findExistingMovementByIdempotencyKey(trx, scope, idempotencyKey);
1306
+ if (existingMovement) {
1307
+ return {
1308
+ movementId: existingMovement.id,
1309
+ warehouseId: input.warehouseId,
1310
+ catalogVariantId: input.catalogVariantId,
1311
+ quantity: input.quantity,
1312
+ tenantId: scope.tenantId,
1313
+ organizationId: scope.organizationId,
1314
+ movementEntity: existingMovement,
1315
+ balances: [
1316
+ { entity: sourceBalance, action: sourceBalanceAction },
1317
+ { entity: targetBalance, action: targetBalanceAction }
1318
+ ],
1319
+ idempotentReplay: true
1320
+ };
1321
+ }
1322
+ if (getAvailableQuantity(sourceBalance) < input.quantity - 1e-6) {
1323
+ throw new CrudHttpError(409, { error: "insufficient_stock" });
1324
+ }
1325
+ setNumeric(
1326
+ sourceBalance,
1327
+ "quantityOnHand",
1328
+ toNumber(sourceBalance.quantityOnHand) - input.quantity
1329
+ );
1330
+ setNumeric(
1331
+ targetBalance,
1332
+ "quantityOnHand",
1333
+ toNumber(targetBalance.quantityOnHand) + input.quantity
1334
+ );
1335
+ const { movement } = await persistMovementWithIdempotency(trx, scope, movementInput);
1336
+ await trx.flush();
1337
+ return {
1338
+ movementId: movement.id,
1339
+ warehouseId: input.warehouseId,
1340
+ catalogVariantId: input.catalogVariantId,
1341
+ quantity: input.quantity,
1342
+ tenantId: scope.tenantId,
1343
+ organizationId: scope.organizationId,
1344
+ movementEntity: movement,
1345
+ balances: [
1346
+ { entity: sourceBalance, action: sourceBalanceAction },
1347
+ { entity: targetBalance, action: targetBalanceAction }
1348
+ ],
1349
+ idempotentReplay: false
1350
+ };
1351
+ });
1352
+ if (!result.idempotentReplay) {
1353
+ await emitInventorySideEffects(ctx, {
1354
+ movements: [{ entity: result.movementEntity, action: "created" }],
1355
+ balances: result.balances
1356
+ });
1357
+ void emitWmsEvent("wms.inventory.moved", {
1358
+ id: result.movementId,
1359
+ movementId: result.movementId,
1360
+ warehouseId: result.warehouseId,
1361
+ catalogVariantId: result.catalogVariantId,
1362
+ quantity: toNumericString(result.quantity),
1363
+ tenantId: result.tenantId,
1364
+ organizationId: result.organizationId
1365
+ }).catch(() => void 0);
1366
+ void emitLowStockEventIfNeeded(
1367
+ resolveEm(ctx),
1368
+ ctx,
1369
+ { tenantId: result.tenantId, organizationId: result.organizationId },
1370
+ result.catalogVariantId
1371
+ ).catch(() => void 0);
1372
+ }
1373
+ return { movementId: result.movementId };
1374
+ },
1375
+ buildLog: async ({ input, result, ctx }) => buildMutationLog({
1376
+ actionKey: "wms.audit.inventory.move",
1377
+ fallbackLabel: "Move inventory",
1378
+ resourceKind: WMS_INVENTORY_MOVEMENT_RESOURCE,
1379
+ resourceId: result?.movementId ?? null,
1380
+ parentResourceId: input?.warehouseId && input?.catalogVariantId ? `${input.warehouseId}:${input.catalogVariantId}` : null,
1381
+ tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,
1382
+ organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
1383
+ cacheAliases: [WMS_INVENTORY_BALANCE_RESOURCE]
1384
+ })
1385
+ };
1386
+ const cycleCountInventoryCommand = {
1387
+ id: "wms.inventory.cycleCount",
1388
+ // See "WMS Inventory Mutation Commands — Undo Policy" at top of file.
1389
+ isUndoable: false,
1390
+ async execute(rawInput, ctx) {
1391
+ const input = inventoryCycleCountSchema.parse(rawInput ?? {});
1392
+ ensureTenantScope(ctx, input.tenantId);
1393
+ ensureOrganizationScope(ctx, input.organizationId);
1394
+ const em = resolveEm(ctx);
1395
+ const result = await runInTransaction(em, async (trx) => {
1396
+ const scope = resolveScope(ctx, input);
1397
+ await requireWarehouse(trx, ctx, input.warehouseId, scope);
1398
+ const location = await requireLocation(trx, ctx, input.locationId, scope);
1399
+ const locationWarehouseId = typeof location.warehouse === "string" ? location.warehouse : location.warehouse.id;
1400
+ if (locationWarehouseId !== input.warehouseId) {
1401
+ throw new CrudHttpError(422, { error: "invalid_location" });
1402
+ }
1403
+ const { balance, created: balanceWasNew } = await upsertBalanceBucket(trx, scope, {
1404
+ warehouseId: input.warehouseId,
1405
+ locationId: input.locationId,
1406
+ catalogVariantId: input.catalogVariantId,
1407
+ lotId: input.lotId,
1408
+ serialNumber: input.serialNumber
1409
+ });
1410
+ const currentOnHand = toNumber(balance.quantityOnHand);
1411
+ const delta = input.countedQuantity - currentOnHand;
1412
+ if (delta === 0) {
1413
+ return {
1414
+ adjustmentDelta: "0",
1415
+ movementId: null,
1416
+ warehouseId: input.warehouseId,
1417
+ catalogVariantId: input.catalogVariantId,
1418
+ tenantId: scope.tenantId,
1419
+ organizationId: scope.organizationId,
1420
+ movementEntity: null,
1421
+ balanceEntity: balance,
1422
+ balanceAction: "updated"
1423
+ };
1424
+ }
1425
+ if (!input.autoAdjust) {
1426
+ throw new CrudHttpError(422, { error: "auto_adjust_required" });
1427
+ }
1428
+ const performedAt = input.performedAt ?? /* @__PURE__ */ new Date();
1429
+ const receivedAt = await resolveReceivedAtForBalance(trx, balance, scope, performedAt);
1430
+ const movementInput = {
1431
+ warehouseId: input.warehouseId,
1432
+ locationToId: input.locationId,
1433
+ catalogVariantId: input.catalogVariantId,
1434
+ lotId: input.lotId ?? null,
1435
+ serialNumber: input.serialNumber ?? null,
1436
+ quantity: delta,
1437
+ type: "cycle_count",
1438
+ referenceType: "manual",
1439
+ referenceId: input.referenceId,
1440
+ performedBy: input.performedBy,
1441
+ performedAt,
1442
+ receivedAt,
1443
+ reason: input.reason,
1444
+ metadata: input.metadata ?? null
1445
+ };
1446
+ const idempotencyKey = buildMovementIdempotencyKey({
1447
+ referenceType: movementInput.referenceType,
1448
+ referenceId: movementInput.referenceId,
1449
+ type: movementInput.type,
1450
+ warehouseId: movementInput.warehouseId,
1451
+ locationFromId: movementInput.locationFromId,
1452
+ locationToId: movementInput.locationToId,
1453
+ catalogVariantId: movementInput.catalogVariantId,
1454
+ lotId: movementInput.lotId,
1455
+ serialNumber: movementInput.serialNumber,
1456
+ quantity: movementInput.quantity
1457
+ });
1458
+ const existingMovement = await findExistingMovementByIdempotencyKey(trx, scope, idempotencyKey);
1459
+ if (existingMovement) {
1460
+ return {
1461
+ adjustmentDelta: toNumericString(delta),
1462
+ movementId: existingMovement.id,
1463
+ warehouseId: input.warehouseId,
1464
+ catalogVariantId: input.catalogVariantId,
1465
+ tenantId: scope.tenantId,
1466
+ organizationId: scope.organizationId,
1467
+ movementEntity: existingMovement,
1468
+ balanceEntity: balance,
1469
+ balanceAction: "updated",
1470
+ idempotentReplay: true
1471
+ };
1472
+ }
1473
+ setNumeric(
1474
+ balance,
1475
+ "quantityOnHand",
1476
+ input.countedQuantity
1477
+ );
1478
+ const { movement } = await persistMovementWithIdempotency(trx, scope, movementInput);
1479
+ await trx.flush();
1480
+ return {
1481
+ adjustmentDelta: toNumericString(delta),
1482
+ movementId: movement.id,
1483
+ warehouseId: input.warehouseId,
1484
+ catalogVariantId: input.catalogVariantId,
1485
+ tenantId: scope.tenantId,
1486
+ organizationId: scope.organizationId,
1487
+ movementEntity: movement,
1488
+ balanceEntity: balance,
1489
+ balanceAction: balanceWasNew ? "created" : "updated",
1490
+ idempotentReplay: false
1491
+ };
1492
+ });
1493
+ if (!result.idempotentReplay) {
1494
+ await emitInventorySideEffects(ctx, {
1495
+ movements: result.movementEntity ? [{ entity: result.movementEntity, action: "created" }] : [],
1496
+ balances: [{ entity: result.balanceEntity, action: result.balanceAction }]
1497
+ });
1498
+ if (result.movementId) {
1499
+ void emitWmsEvent("wms.inventory.reconciled", {
1500
+ id: result.movementId,
1501
+ movementId: result.movementId,
1502
+ warehouseId: result.warehouseId,
1503
+ catalogVariantId: result.catalogVariantId,
1504
+ adjustmentDelta: result.adjustmentDelta,
1505
+ tenantId: result.tenantId,
1506
+ organizationId: result.organizationId
1507
+ }).catch(() => void 0);
1508
+ void emitLowStockEventIfNeeded(
1509
+ resolveEm(ctx),
1510
+ ctx,
1511
+ { tenantId: result.tenantId, organizationId: result.organizationId },
1512
+ result.catalogVariantId
1513
+ ).catch(() => void 0);
1514
+ }
1515
+ }
1516
+ return {
1517
+ adjustmentDelta: result.adjustmentDelta,
1518
+ movementId: result.movementId ?? null
1519
+ };
1520
+ },
1521
+ buildLog: async ({ input, result, ctx }) => buildMutationLog({
1522
+ actionKey: "wms.audit.inventory.cycleCount",
1523
+ fallbackLabel: "Run cycle count reconciliation",
1524
+ resourceKind: WMS_INVENTORY_MOVEMENT_RESOURCE,
1525
+ resourceId: result?.movementId ?? null,
1526
+ parentResourceId: input?.warehouseId && input?.catalogVariantId ? `${input.warehouseId}:${input.catalogVariantId}` : null,
1527
+ tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,
1528
+ organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
1529
+ cacheAliases: [WMS_INVENTORY_BALANCE_RESOURCE]
1530
+ })
1531
+ };
1532
+ registerCommand(reserveInventoryCommand);
1533
+ registerCommand(releaseInventoryReservationCommand);
1534
+ registerCommand(allocateInventoryReservationCommand);
1535
+ registerCommand(adjustInventoryCommand);
1536
+ registerCommand(receiveInventoryCommand);
1537
+ registerCommand(moveInventoryCommand);
1538
+ registerCommand(cycleCountInventoryCommand);
1539
+ //# sourceMappingURL=inventory-actions.js.map