@open-mercato/core 0.6.7-develop.6661.1.0043ed1d03 → 0.6.7-develop.6670.1.4efa7961c6

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 (494) 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/customer_accounts/migrations/Migration20260724120000_customer_accounts.js +56 -0
  35. package/dist/modules/customer_accounts/migrations/Migration20260724120000_customer_accounts.js.map +7 -0
  36. package/dist/modules/directory/api/organizations/route.js +1 -1
  37. package/dist/modules/directory/api/organizations/route.js.map +1 -1
  38. package/dist/modules/entities/api/definitions.js +2 -0
  39. package/dist/modules/entities/api/definitions.js.map +2 -2
  40. package/dist/modules/entities/lib/helpers.js +1 -0
  41. package/dist/modules/entities/lib/helpers.js.map +2 -2
  42. package/dist/modules/query_index/lib/engine.js +6 -6
  43. package/dist/modules/query_index/lib/engine.js.map +2 -2
  44. package/dist/modules/sales/api/documents/factory.js +1 -0
  45. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  46. package/dist/modules/sales/api/quotes/accept/route.js +7 -2
  47. package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
  48. package/dist/modules/sales/api/quotes/public/[token]/route.js +2 -1
  49. package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
  50. package/dist/modules/sales/commands/documents.js +53 -0
  51. package/dist/modules/sales/commands/documents.js.map +2 -2
  52. package/dist/modules/sales/components/documents/ItemsSection.js +51 -2
  53. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  54. package/dist/modules/sales/events.js +2 -0
  55. package/dist/modules/sales/events.js.map +2 -2
  56. package/dist/modules/sales/lib/publicQuoteTenantScope.js +24 -0
  57. package/dist/modules/sales/lib/publicQuoteTenantScope.js.map +7 -0
  58. package/dist/modules/sales/widgets/injection/order-items-context.js +11 -0
  59. package/dist/modules/sales/widgets/injection/order-items-context.js.map +7 -0
  60. package/dist/modules/wms/acl.js +18 -0
  61. package/dist/modules/wms/acl.js.map +7 -0
  62. package/dist/modules/wms/analytics.js +11 -0
  63. package/dist/modules/wms/analytics.js.map +7 -0
  64. package/dist/modules/wms/api/dashboard/operational/route.js +80 -0
  65. package/dist/modules/wms/api/dashboard/operational/route.js.map +7 -0
  66. package/dist/modules/wms/api/interceptors.js +90 -0
  67. package/dist/modules/wms/api/interceptors.js.map +7 -0
  68. package/dist/modules/wms/api/inventory/adjust/route.js +51 -0
  69. package/dist/modules/wms/api/inventory/adjust/route.js.map +7 -0
  70. package/dist/modules/wms/api/inventory/allocate/route.js +51 -0
  71. package/dist/modules/wms/api/inventory/allocate/route.js.map +7 -0
  72. package/dist/modules/wms/api/inventory/balances/route.js +172 -0
  73. package/dist/modules/wms/api/inventory/balances/route.js.map +7 -0
  74. package/dist/modules/wms/api/inventory/cycle-count/route.js +52 -0
  75. package/dist/modules/wms/api/inventory/cycle-count/route.js.map +7 -0
  76. package/dist/modules/wms/api/inventory/helpers.js +113 -0
  77. package/dist/modules/wms/api/inventory/helpers.js.map +7 -0
  78. package/dist/modules/wms/api/inventory/import/apply/route.js +46 -0
  79. package/dist/modules/wms/api/inventory/import/apply/route.js.map +7 -0
  80. package/dist/modules/wms/api/inventory/import/helpers.js +195 -0
  81. package/dist/modules/wms/api/inventory/import/helpers.js.map +7 -0
  82. package/dist/modules/wms/api/inventory/import/template/route.js +32 -0
  83. package/dist/modules/wms/api/inventory/import/template/route.js.map +7 -0
  84. package/dist/modules/wms/api/inventory/import/validate/route.js +54 -0
  85. package/dist/modules/wms/api/inventory/import/validate/route.js.map +7 -0
  86. package/dist/modules/wms/api/inventory/move/route.js +51 -0
  87. package/dist/modules/wms/api/inventory/move/route.js.map +7 -0
  88. package/dist/modules/wms/api/inventory/movements/route.js +164 -0
  89. package/dist/modules/wms/api/inventory/movements/route.js.map +7 -0
  90. package/dist/modules/wms/api/inventory/receive/route.js +50 -0
  91. package/dist/modules/wms/api/inventory/receive/route.js.map +7 -0
  92. package/dist/modules/wms/api/inventory/release/route.js +44 -0
  93. package/dist/modules/wms/api/inventory/release/route.js.map +7 -0
  94. package/dist/modules/wms/api/inventory/reservations/route.js +128 -0
  95. package/dist/modules/wms/api/inventory/reservations/route.js.map +7 -0
  96. package/dist/modules/wms/api/inventory/reserve/route.js +59 -0
  97. package/dist/modules/wms/api/inventory/reserve/route.js.map +7 -0
  98. package/dist/modules/wms/api/inventory-profiles/route.js +178 -0
  99. package/dist/modules/wms/api/inventory-profiles/route.js.map +7 -0
  100. package/dist/modules/wms/api/listEnrichers.js +181 -0
  101. package/dist/modules/wms/api/listEnrichers.js.map +7 -0
  102. package/dist/modules/wms/api/listSearch.js +77 -0
  103. package/dist/modules/wms/api/listSearch.js.map +7 -0
  104. package/dist/modules/wms/api/locations/route.js +172 -0
  105. package/dist/modules/wms/api/locations/route.js.map +7 -0
  106. package/dist/modules/wms/api/lots/route.js +204 -0
  107. package/dist/modules/wms/api/lots/route.js.map +7 -0
  108. package/dist/modules/wms/api/openapi.js +27 -0
  109. package/dist/modules/wms/api/openapi.js.map +7 -0
  110. package/dist/modules/wms/api/sales-orders/[salesOrderId]/re-run-reservation/route.js +54 -0
  111. package/dist/modules/wms/api/sales-orders/[salesOrderId]/re-run-reservation/route.js.map +7 -0
  112. package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js +279 -0
  113. package/dist/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.js.map +7 -0
  114. package/dist/modules/wms/api/sales-orders/assign-warehouse/route.js +56 -0
  115. package/dist/modules/wms/api/sales-orders/assign-warehouse/route.js.map +7 -0
  116. package/dist/modules/wms/api/sales-orders/unassign-warehouse/route.js +43 -0
  117. package/dist/modules/wms/api/sales-orders/unassign-warehouse/route.js.map +7 -0
  118. package/dist/modules/wms/api/warehouses/route.js +181 -0
  119. package/dist/modules/wms/api/warehouses/route.js.map +7 -0
  120. package/dist/modules/wms/api/zones/route.js +159 -0
  121. package/dist/modules/wms/api/zones/route.js.map +7 -0
  122. package/dist/modules/wms/backend/config/wms/page.js +9 -0
  123. package/dist/modules/wms/backend/config/wms/page.js.map +7 -0
  124. package/dist/modules/wms/backend/config/wms/page.meta.js +18 -0
  125. package/dist/modules/wms/backend/config/wms/page.meta.js.map +7 -0
  126. package/dist/modules/wms/backend/page.js +9 -0
  127. package/dist/modules/wms/backend/page.js.map +7 -0
  128. package/dist/modules/wms/backend/page.meta.js +16 -0
  129. package/dist/modules/wms/backend/page.meta.js.map +7 -0
  130. package/dist/modules/wms/backend/wms/inventory/page.js +9 -0
  131. package/dist/modules/wms/backend/wms/inventory/page.js.map +7 -0
  132. package/dist/modules/wms/backend/wms/inventory/page.meta.js +18 -0
  133. package/dist/modules/wms/backend/wms/inventory/page.meta.js.map +7 -0
  134. package/dist/modules/wms/backend/wms/location/[id]/page.js +9 -0
  135. package/dist/modules/wms/backend/wms/location/[id]/page.js.map +7 -0
  136. package/dist/modules/wms/backend/wms/location/[id]/page.meta.js +18 -0
  137. package/dist/modules/wms/backend/wms/location/[id]/page.meta.js.map +7 -0
  138. package/dist/modules/wms/backend/wms/locations/page.js +10 -0
  139. package/dist/modules/wms/backend/wms/locations/page.js.map +7 -0
  140. package/dist/modules/wms/backend/wms/locations/page.meta.js +18 -0
  141. package/dist/modules/wms/backend/wms/locations/page.meta.js.map +7 -0
  142. package/dist/modules/wms/backend/wms/lot/[id]/page.js +9 -0
  143. package/dist/modules/wms/backend/wms/lot/[id]/page.js.map +7 -0
  144. package/dist/modules/wms/backend/wms/lot/[id]/page.meta.js +18 -0
  145. package/dist/modules/wms/backend/wms/lot/[id]/page.meta.js.map +7 -0
  146. package/dist/modules/wms/backend/wms/lots/page.js +9 -0
  147. package/dist/modules/wms/backend/wms/lots/page.js.map +7 -0
  148. package/dist/modules/wms/backend/wms/lots/page.meta.js +18 -0
  149. package/dist/modules/wms/backend/wms/lots/page.meta.js.map +7 -0
  150. package/dist/modules/wms/backend/wms/movements/page.js +22 -0
  151. package/dist/modules/wms/backend/wms/movements/page.js.map +7 -0
  152. package/dist/modules/wms/backend/wms/movements/page.meta.js +18 -0
  153. package/dist/modules/wms/backend/wms/movements/page.meta.js.map +7 -0
  154. package/dist/modules/wms/backend/wms/reservations/page.js +23 -0
  155. package/dist/modules/wms/backend/wms/reservations/page.js.map +7 -0
  156. package/dist/modules/wms/backend/wms/reservations/page.meta.js +18 -0
  157. package/dist/modules/wms/backend/wms/reservations/page.meta.js.map +7 -0
  158. package/dist/modules/wms/backend/wms/sku/[id]/page.js +9 -0
  159. package/dist/modules/wms/backend/wms/sku/[id]/page.js.map +7 -0
  160. package/dist/modules/wms/backend/wms/sku/[id]/page.meta.js +18 -0
  161. package/dist/modules/wms/backend/wms/sku/[id]/page.meta.js.map +7 -0
  162. package/dist/modules/wms/backend/wms/warehouses/page.js +10 -0
  163. package/dist/modules/wms/backend/wms/warehouses/page.js.map +7 -0
  164. package/dist/modules/wms/backend/wms/warehouses/page.meta.js +18 -0
  165. package/dist/modules/wms/backend/wms/warehouses/page.meta.js.map +7 -0
  166. package/dist/modules/wms/backend/wms/zones/page.js +10 -0
  167. package/dist/modules/wms/backend/wms/zones/page.js.map +7 -0
  168. package/dist/modules/wms/backend/wms/zones/page.meta.js +18 -0
  169. package/dist/modules/wms/backend/wms/zones/page.meta.js.map +7 -0
  170. package/dist/modules/wms/ce.js +80 -0
  171. package/dist/modules/wms/ce.js.map +7 -0
  172. package/dist/modules/wms/cli.js +83 -0
  173. package/dist/modules/wms/cli.js.map +7 -0
  174. package/dist/modules/wms/commands/configuration.js +1519 -0
  175. package/dist/modules/wms/commands/configuration.js.map +7 -0
  176. package/dist/modules/wms/commands/index.js +4 -0
  177. package/dist/modules/wms/commands/index.js.map +7 -0
  178. package/dist/modules/wms/commands/inventory-actions.js +1539 -0
  179. package/dist/modules/wms/commands/inventory-actions.js.map +7 -0
  180. package/dist/modules/wms/commands/sales-order-assignment.js +277 -0
  181. package/dist/modules/wms/commands/sales-order-assignment.js.map +7 -0
  182. package/dist/modules/wms/commands/shared.js +82 -0
  183. package/dist/modules/wms/commands/shared.js.map +7 -0
  184. package/dist/modules/wms/components/backend/AdjustInventoryDialog.js +755 -0
  185. package/dist/modules/wms/components/backend/AdjustInventoryDialog.js.map +7 -0
  186. package/dist/modules/wms/components/backend/ChangeLotStatusDialog.js +269 -0
  187. package/dist/modules/wms/components/backend/ChangeLotStatusDialog.js.map +7 -0
  188. package/dist/modules/wms/components/backend/CycleCountWizardDialog.js +1783 -0
  189. package/dist/modules/wms/components/backend/CycleCountWizardDialog.js.map +7 -0
  190. package/dist/modules/wms/components/backend/ImportInventoryDialog.js +766 -0
  191. package/dist/modules/wms/components/backend/ImportInventoryDialog.js.map +7 -0
  192. package/dist/modules/wms/components/backend/InventoryOperationsSection.js +119 -0
  193. package/dist/modules/wms/components/backend/InventoryOperationsSection.js.map +7 -0
  194. package/dist/modules/wms/components/backend/LocationEditDialog.js +140 -0
  195. package/dist/modules/wms/components/backend/LocationEditDialog.js.map +7 -0
  196. package/dist/modules/wms/components/backend/MoveInventoryDialog.js +791 -0
  197. package/dist/modules/wms/components/backend/MoveInventoryDialog.js.map +7 -0
  198. package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js +656 -0
  199. package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js.map +7 -0
  200. package/dist/modules/wms/components/backend/ReleaseReservationDialog.js +344 -0
  201. package/dist/modules/wms/components/backend/ReleaseReservationDialog.js.map +7 -0
  202. package/dist/modules/wms/components/backend/ReserveInventoryDialog.js +475 -0
  203. package/dist/modules/wms/components/backend/ReserveInventoryDialog.js.map +7 -0
  204. package/dist/modules/wms/components/backend/WmsConfigurationPage.js +1112 -0
  205. package/dist/modules/wms/components/backend/WmsConfigurationPage.js.map +7 -0
  206. package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js +908 -0
  207. package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js.map +7 -0
  208. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +1015 -0
  209. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +7 -0
  210. package/dist/modules/wms/components/backend/WmsLotDetailPage.js +1049 -0
  211. package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +7 -0
  212. package/dist/modules/wms/components/backend/WmsLotsListPage.js +297 -0
  213. package/dist/modules/wms/components/backend/WmsLotsListPage.js.map +7 -0
  214. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js +757 -0
  215. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +7 -0
  216. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +975 -0
  217. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +7 -0
  218. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +710 -0
  219. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +7 -0
  220. package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js +79 -0
  221. package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js.map +7 -0
  222. package/dist/modules/wms/components/backend/useWmsInventoryScopeFromSearchParams.js +24 -0
  223. package/dist/modules/wms/components/backend/useWmsInventoryScopeFromSearchParams.js.map +7 -0
  224. package/dist/modules/wms/components/backend/wmsLookupLoaders.js +55 -0
  225. package/dist/modules/wms/components/backend/wmsLookupLoaders.js.map +7 -0
  226. package/dist/modules/wms/data/enrichers.js +501 -0
  227. package/dist/modules/wms/data/enrichers.js.map +7 -0
  228. package/dist/modules/wms/data/entities.js +467 -0
  229. package/dist/modules/wms/data/entities.js.map +7 -0
  230. package/dist/modules/wms/data/validators.js +399 -0
  231. package/dist/modules/wms/data/validators.js.map +7 -0
  232. package/dist/modules/wms/di.js +27 -0
  233. package/dist/modules/wms/di.js.map +7 -0
  234. package/dist/modules/wms/events.js +42 -0
  235. package/dist/modules/wms/events.js.map +7 -0
  236. package/dist/modules/wms/index.js +17 -0
  237. package/dist/modules/wms/index.js.map +7 -0
  238. package/dist/modules/wms/lib/catalogInventoryProfileIntent.js +54 -0
  239. package/dist/modules/wms/lib/catalogInventoryProfileIntent.js.map +7 -0
  240. package/dist/modules/wms/lib/catalogInventoryProfileSync.js +144 -0
  241. package/dist/modules/wms/lib/catalogInventoryProfileSync.js.map +7 -0
  242. package/dist/modules/wms/lib/expiry.js +29 -0
  243. package/dist/modules/wms/lib/expiry.js.map +7 -0
  244. package/dist/modules/wms/lib/flashMutationError.js +51 -0
  245. package/dist/modules/wms/lib/flashMutationError.js.map +7 -0
  246. package/dist/modules/wms/lib/inventoryDisplayUi.js +188 -0
  247. package/dist/modules/wms/lib/inventoryDisplayUi.js.map +7 -0
  248. package/dist/modules/wms/lib/inventoryIdempotency.js +37 -0
  249. package/dist/modules/wms/lib/inventoryIdempotency.js.map +7 -0
  250. package/dist/modules/wms/lib/inventoryImportCsv.js +173 -0
  251. package/dist/modules/wms/lib/inventoryImportCsv.js.map +7 -0
  252. package/dist/modules/wms/lib/inventoryImportService.js +468 -0
  253. package/dist/modules/wms/lib/inventoryImportService.js.map +7 -0
  254. package/dist/modules/wms/lib/inventoryMutationUi.js +25 -0
  255. package/dist/modules/wms/lib/inventoryMutationUi.js.map +7 -0
  256. package/dist/modules/wms/lib/inventoryPolicy.js +61 -0
  257. package/dist/modules/wms/lib/inventoryPolicy.js.map +7 -0
  258. package/dist/modules/wms/lib/inventoryReconciliation.js +193 -0
  259. package/dist/modules/wms/lib/inventoryReconciliation.js.map +7 -0
  260. package/dist/modules/wms/lib/inventoryTrackingValidation.js +13 -0
  261. package/dist/modules/wms/lib/inventoryTrackingValidation.js.map +7 -0
  262. package/dist/modules/wms/lib/loadOperationalDashboard.js +580 -0
  263. package/dist/modules/wms/lib/loadOperationalDashboard.js.map +7 -0
  264. package/dist/modules/wms/lib/lowStockBalanceFilter.js +49 -0
  265. package/dist/modules/wms/lib/lowStockBalanceFilter.js.map +7 -0
  266. package/dist/modules/wms/lib/primaryWarehousePolicy.js +32 -0
  267. package/dist/modules/wms/lib/primaryWarehousePolicy.js.map +7 -0
  268. package/dist/modules/wms/lib/roleFeatures.js +30 -0
  269. package/dist/modules/wms/lib/roleFeatures.js.map +7 -0
  270. package/dist/modules/wms/lib/salesOrderInventoryAutomation.js +291 -0
  271. package/dist/modules/wms/lib/salesOrderInventoryAutomation.js.map +7 -0
  272. package/dist/modules/wms/lib/salesOrderWarehouseAssignment.js +54 -0
  273. package/dist/modules/wms/lib/salesOrderWarehouseAssignment.js.map +7 -0
  274. package/dist/modules/wms/lib/wmsIntegrationToggles.js +71 -0
  275. package/dist/modules/wms/lib/wmsIntegrationToggles.js.map +7 -0
  276. package/dist/modules/wms/migrations/Migration20260428110546.js +78 -0
  277. package/dist/modules/wms/migrations/Migration20260428110546.js.map +7 -0
  278. package/dist/modules/wms/migrations/Migration20260527120000.js +15 -0
  279. package/dist/modules/wms/migrations/Migration20260527120000.js.map +7 -0
  280. package/dist/modules/wms/migrations/Migration20260527140000.js +37 -0
  281. package/dist/modules/wms/migrations/Migration20260527140000.js.map +7 -0
  282. package/dist/modules/wms/migrations/Migration20260613120000.js +23 -0
  283. package/dist/modules/wms/migrations/Migration20260613120000.js.map +7 -0
  284. package/dist/modules/wms/migrations/Migration20260616090000.js +13 -0
  285. package/dist/modules/wms/migrations/Migration20260616090000.js.map +7 -0
  286. package/dist/modules/wms/migrations/Migration20260707180000.js +15 -0
  287. package/dist/modules/wms/migrations/Migration20260707180000.js.map +7 -0
  288. package/dist/modules/wms/notifications.client.js +18 -0
  289. package/dist/modules/wms/notifications.client.js.map +7 -0
  290. package/dist/modules/wms/notifications.js +53 -0
  291. package/dist/modules/wms/notifications.js.map +7 -0
  292. package/dist/modules/wms/search.js +196 -0
  293. package/dist/modules/wms/search.js.map +7 -0
  294. package/dist/modules/wms/setup.js +30 -0
  295. package/dist/modules/wms/setup.js.map +7 -0
  296. package/dist/modules/wms/subscribers/low-stock-notification.js +40 -0
  297. package/dist/modules/wms/subscribers/low-stock-notification.js.map +7 -0
  298. package/dist/modules/wms/subscribers/reservation-shortfall-notification.js +69 -0
  299. package/dist/modules/wms/subscribers/reservation-shortfall-notification.js.map +7 -0
  300. package/dist/modules/wms/subscribers/sales-order-cancelled-release.js +14 -0
  301. package/dist/modules/wms/subscribers/sales-order-cancelled-release.js.map +7 -0
  302. package/dist/modules/wms/subscribers/sales-order-confirmed-reserve.js +14 -0
  303. package/dist/modules/wms/subscribers/sales-order-confirmed-reserve.js.map +7 -0
  304. package/dist/modules/wms/translations.js +10 -0
  305. package/dist/modules/wms/translations.js.map +7 -0
  306. package/dist/modules/wms/vector.js +9 -0
  307. package/dist/modules/wms/vector.js.map +7 -0
  308. package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js +200 -0
  309. package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js.map +7 -0
  310. package/dist/modules/wms/widgets/injection/order-items-stock-column/widget.js +104 -0
  311. package/dist/modules/wms/widgets/injection/order-items-stock-column/widget.js.map +7 -0
  312. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js +389 -0
  313. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js.map +7 -0
  314. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.js +16 -0
  315. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.js.map +7 -0
  316. package/dist/modules/wms/widgets/injection-table.js +36 -0
  317. package/dist/modules/wms/widgets/injection-table.js.map +7 -0
  318. package/dist/modules/wms/widgets/notifications/WmsLowStockRenderer.js +150 -0
  319. package/dist/modules/wms/widgets/notifications/WmsLowStockRenderer.js.map +7 -0
  320. package/dist/modules/wms/widgets/notifications/WmsReservationShortfallRenderer.js +186 -0
  321. package/dist/modules/wms/widgets/notifications/WmsReservationShortfallRenderer.js.map +7 -0
  322. package/dist/modules/wms/widgets/notifications/index.js +7 -0
  323. package/dist/modules/wms/widgets/notifications/index.js.map +7 -0
  324. package/generated/entities/inventory_balance/index.ts +9 -0
  325. package/generated/entities/inventory_lot/index.ts +8 -0
  326. package/generated/entities/inventory_movement/index.ts +16 -0
  327. package/generated/entities/inventory_reservation/index.ts +10 -0
  328. package/generated/entities/product_inventory_profile/index.ts +9 -0
  329. package/generated/entities/sales_order_warehouse_assignment/index.ts +4 -0
  330. package/generated/entities/warehouse/index.ts +11 -0
  331. package/generated/entities/warehouse_location/index.ts +9 -0
  332. package/generated/entities/warehouse_zone/index.ts +4 -0
  333. package/generated/entities.ids.generated.ts +12 -0
  334. package/generated/entity-fields-registry.ts +98 -0
  335. package/package.json +7 -7
  336. package/src/modules/auth/backend/roles/page.tsx +13 -4
  337. package/src/modules/auth/i18n/de.json +2 -0
  338. package/src/modules/auth/i18n/en.json +2 -0
  339. package/src/modules/auth/i18n/es.json +2 -0
  340. package/src/modules/auth/i18n/pl.json +2 -0
  341. package/src/modules/auth/lib/backendChrome.tsx +1 -0
  342. package/src/modules/auth/services/rbacService.ts +25 -21
  343. package/src/modules/catalog/api/products/route.ts +3 -0
  344. package/src/modules/catalog/api/variants/route.ts +3 -0
  345. package/src/modules/customer_accounts/migrations/Migration20260724120000_customer_accounts.ts +96 -0
  346. package/src/modules/directory/api/organizations/route.ts +1 -1
  347. package/src/modules/entities/api/definitions.ts +2 -0
  348. package/src/modules/entities/lib/helpers.ts +1 -0
  349. package/src/modules/query_index/lib/engine.ts +18 -6
  350. package/src/modules/sales/api/documents/factory.ts +1 -0
  351. package/src/modules/sales/api/quotes/accept/route.ts +9 -2
  352. package/src/modules/sales/api/quotes/public/[token]/route.ts +2 -1
  353. package/src/modules/sales/commands/documents.ts +71 -0
  354. package/src/modules/sales/components/documents/ItemsSection.tsx +58 -0
  355. package/src/modules/sales/events.ts +2 -0
  356. package/src/modules/sales/lib/publicQuoteTenantScope.ts +49 -0
  357. package/src/modules/sales/widgets/injection/order-items-context.ts +15 -0
  358. package/src/modules/wms/acl.ts +14 -0
  359. package/src/modules/wms/analytics.ts +9 -0
  360. package/src/modules/wms/api/dashboard/operational/route.ts +83 -0
  361. package/src/modules/wms/api/interceptors.ts +108 -0
  362. package/src/modules/wms/api/inventory/adjust/route.ts +50 -0
  363. package/src/modules/wms/api/inventory/allocate/route.ts +50 -0
  364. package/src/modules/wms/api/inventory/balances/route.ts +178 -0
  365. package/src/modules/wms/api/inventory/cycle-count/route.ts +51 -0
  366. package/src/modules/wms/api/inventory/helpers.ts +128 -0
  367. package/src/modules/wms/api/inventory/import/apply/route.ts +47 -0
  368. package/src/modules/wms/api/inventory/import/helpers.ts +223 -0
  369. package/src/modules/wms/api/inventory/import/template/route.ts +31 -0
  370. package/src/modules/wms/api/inventory/import/validate/route.ts +56 -0
  371. package/src/modules/wms/api/inventory/move/route.ts +50 -0
  372. package/src/modules/wms/api/inventory/movements/route.ts +164 -0
  373. package/src/modules/wms/api/inventory/receive/route.ts +49 -0
  374. package/src/modules/wms/api/inventory/release/route.ts +43 -0
  375. package/src/modules/wms/api/inventory/reservations/route.ts +128 -0
  376. package/src/modules/wms/api/inventory/reserve/route.ts +59 -0
  377. package/src/modules/wms/api/inventory-profiles/route.ts +177 -0
  378. package/src/modules/wms/api/listEnrichers.ts +312 -0
  379. package/src/modules/wms/api/listSearch.ts +121 -0
  380. package/src/modules/wms/api/locations/route.ts +171 -0
  381. package/src/modules/wms/api/lots/route.ts +205 -0
  382. package/src/modules/wms/api/openapi.ts +28 -0
  383. package/src/modules/wms/api/sales-orders/[salesOrderId]/re-run-reservation/route.ts +58 -0
  384. package/src/modules/wms/api/sales-orders/[salesOrderId]/warehouse-assignment/route.ts +306 -0
  385. package/src/modules/wms/api/sales-orders/assign-warehouse/route.ts +57 -0
  386. package/src/modules/wms/api/sales-orders/unassign-warehouse/route.ts +43 -0
  387. package/src/modules/wms/api/warehouses/route.ts +181 -0
  388. package/src/modules/wms/api/zones/route.ts +158 -0
  389. package/src/modules/wms/backend/config/wms/page.meta.ts +14 -0
  390. package/src/modules/wms/backend/config/wms/page.tsx +5 -0
  391. package/src/modules/wms/backend/page.meta.ts +12 -0
  392. package/src/modules/wms/backend/page.tsx +5 -0
  393. package/src/modules/wms/backend/wms/inventory/page.meta.ts +14 -0
  394. package/src/modules/wms/backend/wms/inventory/page.tsx +5 -0
  395. package/src/modules/wms/backend/wms/location/[id]/page.meta.ts +14 -0
  396. package/src/modules/wms/backend/wms/location/[id]/page.tsx +5 -0
  397. package/src/modules/wms/backend/wms/locations/page.meta.ts +14 -0
  398. package/src/modules/wms/backend/wms/locations/page.tsx +12 -0
  399. package/src/modules/wms/backend/wms/lot/[id]/page.meta.ts +14 -0
  400. package/src/modules/wms/backend/wms/lot/[id]/page.tsx +5 -0
  401. package/src/modules/wms/backend/wms/lots/page.meta.ts +14 -0
  402. package/src/modules/wms/backend/wms/lots/page.tsx +5 -0
  403. package/src/modules/wms/backend/wms/movements/page.meta.ts +14 -0
  404. package/src/modules/wms/backend/wms/movements/page.tsx +23 -0
  405. package/src/modules/wms/backend/wms/reservations/page.meta.ts +14 -0
  406. package/src/modules/wms/backend/wms/reservations/page.tsx +24 -0
  407. package/src/modules/wms/backend/wms/sku/[id]/page.meta.ts +14 -0
  408. package/src/modules/wms/backend/wms/sku/[id]/page.tsx +5 -0
  409. package/src/modules/wms/backend/wms/warehouses/page.meta.ts +14 -0
  410. package/src/modules/wms/backend/wms/warehouses/page.tsx +12 -0
  411. package/src/modules/wms/backend/wms/zones/page.meta.ts +14 -0
  412. package/src/modules/wms/backend/wms/zones/page.tsx +12 -0
  413. package/src/modules/wms/ce.ts +76 -0
  414. package/src/modules/wms/cli.ts +96 -0
  415. package/src/modules/wms/commands/configuration.ts +1844 -0
  416. package/src/modules/wms/commands/index.ts +3 -0
  417. package/src/modules/wms/commands/inventory-actions.ts +1866 -0
  418. package/src/modules/wms/commands/sales-order-assignment.ts +346 -0
  419. package/src/modules/wms/commands/shared.ts +90 -0
  420. package/src/modules/wms/components/backend/AdjustInventoryDialog.tsx +820 -0
  421. package/src/modules/wms/components/backend/ChangeLotStatusDialog.tsx +312 -0
  422. package/src/modules/wms/components/backend/CycleCountWizardDialog.tsx +2007 -0
  423. package/src/modules/wms/components/backend/ImportInventoryDialog.tsx +1018 -0
  424. package/src/modules/wms/components/backend/InventoryOperationsSection.tsx +159 -0
  425. package/src/modules/wms/components/backend/LocationEditDialog.tsx +185 -0
  426. package/src/modules/wms/components/backend/MoveInventoryDialog.tsx +907 -0
  427. package/src/modules/wms/components/backend/ReceiveInventoryDialog.tsx +695 -0
  428. package/src/modules/wms/components/backend/ReleaseReservationDialog.tsx +424 -0
  429. package/src/modules/wms/components/backend/ReserveInventoryDialog.tsx +511 -0
  430. package/src/modules/wms/components/backend/WmsConfigurationPage.tsx +1340 -0
  431. package/src/modules/wms/components/backend/WmsInventoryConsolePage.tsx +1132 -0
  432. package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +1310 -0
  433. package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +1355 -0
  434. package/src/modules/wms/components/backend/WmsLotsListPage.tsx +359 -0
  435. package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +958 -0
  436. package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +1245 -0
  437. package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +960 -0
  438. package/src/modules/wms/components/backend/useWmsInventoryMutationAccess.ts +89 -0
  439. package/src/modules/wms/components/backend/useWmsInventoryScopeFromSearchParams.ts +32 -0
  440. package/src/modules/wms/components/backend/wmsLookupLoaders.ts +78 -0
  441. package/src/modules/wms/data/enrichers.ts +718 -0
  442. package/src/modules/wms/data/entities.ts +436 -0
  443. package/src/modules/wms/data/validators.ts +463 -0
  444. package/src/modules/wms/di.ts +25 -0
  445. package/src/modules/wms/events.ts +41 -0
  446. package/src/modules/wms/i18n/de.json +1051 -0
  447. package/src/modules/wms/i18n/en.json +1051 -0
  448. package/src/modules/wms/i18n/es.json +1051 -0
  449. package/src/modules/wms/i18n/pl.json +1051 -0
  450. package/src/modules/wms/index.ts +15 -0
  451. package/src/modules/wms/lib/catalogInventoryProfileIntent.ts +65 -0
  452. package/src/modules/wms/lib/catalogInventoryProfileSync.ts +199 -0
  453. package/src/modules/wms/lib/expiry.ts +30 -0
  454. package/src/modules/wms/lib/flashMutationError.ts +74 -0
  455. package/src/modules/wms/lib/inventoryDisplayUi.ts +249 -0
  456. package/src/modules/wms/lib/inventoryIdempotency.ts +54 -0
  457. package/src/modules/wms/lib/inventoryImportCsv.ts +212 -0
  458. package/src/modules/wms/lib/inventoryImportService.ts +629 -0
  459. package/src/modules/wms/lib/inventoryMutationUi.ts +21 -0
  460. package/src/modules/wms/lib/inventoryPolicy.ts +111 -0
  461. package/src/modules/wms/lib/inventoryReconciliation.ts +263 -0
  462. package/src/modules/wms/lib/inventoryTrackingValidation.ts +23 -0
  463. package/src/modules/wms/lib/loadOperationalDashboard.ts +776 -0
  464. package/src/modules/wms/lib/lowStockBalanceFilter.ts +74 -0
  465. package/src/modules/wms/lib/primaryWarehousePolicy.ts +46 -0
  466. package/src/modules/wms/lib/roleFeatures.ts +32 -0
  467. package/src/modules/wms/lib/salesOrderInventoryAutomation.ts +390 -0
  468. package/src/modules/wms/lib/salesOrderWarehouseAssignment.ts +78 -0
  469. package/src/modules/wms/lib/wmsIntegrationToggles.ts +81 -0
  470. package/src/modules/wms/migrations/.snapshot-open-mercato.json +2880 -0
  471. package/src/modules/wms/migrations/Migration20260428110546.ts +91 -0
  472. package/src/modules/wms/migrations/Migration20260527120000.ts +15 -0
  473. package/src/modules/wms/migrations/Migration20260527140000.ts +35 -0
  474. package/src/modules/wms/migrations/Migration20260613120000.ts +21 -0
  475. package/src/modules/wms/migrations/Migration20260616090000.ts +11 -0
  476. package/src/modules/wms/migrations/Migration20260707180000.ts +13 -0
  477. package/src/modules/wms/notifications.client.ts +18 -0
  478. package/src/modules/wms/notifications.ts +51 -0
  479. package/src/modules/wms/search.ts +230 -0
  480. package/src/modules/wms/setup.ts +29 -0
  481. package/src/modules/wms/subscribers/low-stock-notification.ts +54 -0
  482. package/src/modules/wms/subscribers/reservation-shortfall-notification.ts +98 -0
  483. package/src/modules/wms/subscribers/sales-order-cancelled-release.ts +21 -0
  484. package/src/modules/wms/subscribers/sales-order-confirmed-reserve.ts +21 -0
  485. package/src/modules/wms/translations.ts +6 -0
  486. package/src/modules/wms/vector.ts +7 -0
  487. package/src/modules/wms/widgets/injection/catalog-inventory-profile/widget.ts +238 -0
  488. package/src/modules/wms/widgets/injection/order-items-stock-column/widget.tsx +153 -0
  489. package/src/modules/wms/widgets/injection/sales-order-stock-context/widget.client.tsx +476 -0
  490. package/src/modules/wms/widgets/injection/sales-order-stock-context/widget.ts +15 -0
  491. package/src/modules/wms/widgets/injection-table.ts +34 -0
  492. package/src/modules/wms/widgets/notifications/WmsLowStockRenderer.tsx +153 -0
  493. package/src/modules/wms/widgets/notifications/WmsReservationShortfallRenderer.tsx +194 -0
  494. package/src/modules/wms/widgets/notifications/index.ts +2 -0
@@ -0,0 +1,960 @@
1
+ import type { CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'
2
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
3
+ import {
4
+ buildQuery,
5
+ loadCatalogVariantOptions,
6
+ loadWarehouseOptions,
7
+ } from './wmsLookupLoaders'
8
+
9
+ export { buildQuery, loadCatalogVariantOptions, loadWarehouseOptions }
10
+
11
+ type PagedResponse<T> = {
12
+ items: T[]
13
+ total: number
14
+ totalPages: number
15
+ }
16
+
17
+ type InventoryLotListRow = {
18
+ id?: string | null
19
+ lot_number?: string | null
20
+ expires_at?: string | null
21
+ }
22
+
23
+ async function loadCrudOptionsByIds<T>(
24
+ endpoint: string,
25
+ ids: string[],
26
+ mapItem: (item: T) => CrudFieldOption | null,
27
+ ): Promise<CrudFieldOption[]> {
28
+ const normalizedIds = [...new Set(ids.map((id) => id.trim()).filter(Boolean))]
29
+ if (normalizedIds.length === 0) return []
30
+ const params = buildQuery({
31
+ page: 1,
32
+ pageSize: normalizedIds.length,
33
+ ids: normalizedIds.join(','),
34
+ })
35
+ const call = await apiCall<PagedResponse<T>>(`${endpoint}?${params}`)
36
+ if (!call.ok) return []
37
+ return (call.result?.items ?? [])
38
+ .map(mapItem)
39
+ .filter((option): option is CrudFieldOption => option !== null)
40
+ }
41
+
42
+ export async function resolveCatalogVariantLabel(catalogVariantId: string): Promise<string | null> {
43
+ const id = catalogVariantId.trim()
44
+ if (!id) return null
45
+ const params = buildQuery({ page: 1, pageSize: 1, id })
46
+ const call = await apiCall<PagedResponse<{ id?: string | null; name?: string | null; sku?: string | null }>>(
47
+ `/api/catalog/variants?${params}`,
48
+ )
49
+ if (!call.ok) return null
50
+ const item = call.result?.items?.[0]
51
+ if (!item) return null
52
+ return item.sku?.trim() || item.name?.trim() || id
53
+ }
54
+
55
+ export async function resolveWarehouseLabel(warehouseId: string): Promise<string | null> {
56
+ const [option] = await loadCrudOptionsByIds<{ id?: string | null; name?: string | null; code?: string | null }>(
57
+ '/api/wms/warehouses',
58
+ [warehouseId],
59
+ (item) => {
60
+ const value = typeof item.id === 'string' ? item.id : null
61
+ if (!value) return null
62
+ return { value, label: item.name || item.code || value }
63
+ },
64
+ )
65
+ return option?.label ?? null
66
+ }
67
+
68
+ export async function resolveLocationLabel(locationId: string): Promise<string | null> {
69
+ const [option] = await loadCrudOptionsByIds<{ id?: string | null; code?: string | null }>(
70
+ '/api/wms/locations',
71
+ [locationId],
72
+ (item) => {
73
+ const value = typeof item.id === 'string' ? item.id : null
74
+ if (!value) return null
75
+ return { value, label: item.code || value }
76
+ },
77
+ )
78
+ return option?.label ?? null
79
+ }
80
+
81
+ export async function resolveLotLabel(lotId: string): Promise<string | null> {
82
+ const [option] = await loadCrudOptionsByIds<InventoryLotListRow>(
83
+ '/api/wms/lots',
84
+ [lotId],
85
+ (item) => {
86
+ const value = typeof item.id === 'string' ? item.id : null
87
+ if (!value) return null
88
+ const lotNumber = item.lot_number?.trim() || value
89
+ const expiresAt = item.expires_at?.trim()
90
+ const label = expiresAt ? `${lotNumber} · exp ${expiresAt.slice(0, 10)}` : lotNumber
91
+ return { value, label }
92
+ },
93
+ )
94
+ return option?.label ?? null
95
+ }
96
+
97
+ export async function resolveLotNumberFromId(lotId: string): Promise<string | null> {
98
+ const [option] = await loadCrudOptionsByIds<InventoryLotListRow>(
99
+ '/api/wms/lots',
100
+ [lotId],
101
+ (item) => {
102
+ const value = typeof item.id === 'string' ? item.id : null
103
+ const lotNumber = item.lot_number?.trim()
104
+ if (!value || !lotNumber) return null
105
+ return { value, label: lotNumber }
106
+ },
107
+ )
108
+ return option?.label ?? null
109
+ }
110
+
111
+ export async function resolveCatalogVariantSku(catalogVariantId: string): Promise<string | null> {
112
+ const id = catalogVariantId.trim()
113
+ if (!id) return null
114
+ const params = buildQuery({ page: 1, pageSize: 1, id })
115
+ const call = await apiCall<PagedResponse<{ id?: string | null; sku?: string | null }>>(
116
+ `/api/catalog/variants?${params}`,
117
+ )
118
+ if (!call.ok) return null
119
+ const sku = call.result?.items?.[0]?.sku?.trim()
120
+ return sku || null
121
+ }
122
+
123
+ type InventoryProfileListRow = {
124
+ track_lot?: boolean | null
125
+ track_serial?: boolean | null
126
+ }
127
+
128
+ export async function loadInventoryProfileForVariant(
129
+ catalogVariantId: string,
130
+ ): Promise<InventoryProfileListRow | null> {
131
+ const variantId = catalogVariantId.trim()
132
+ if (!variantId) return null
133
+ const params = buildQuery({ page: 1, pageSize: 1, catalogVariantId: variantId })
134
+ const call = await apiCall<PagedResponse<InventoryProfileListRow>>(
135
+ `/api/wms/inventory-profiles?${params}`,
136
+ )
137
+ if (!call.ok) return null
138
+ return call.result?.items?.[0] ?? null
139
+ }
140
+
141
+ export async function findLotIdByNumber(
142
+ catalogVariantId: string,
143
+ lotNumber: string,
144
+ ): Promise<string | null> {
145
+ const variantId = catalogVariantId.trim()
146
+ const normalizedLotNumber = lotNumber.trim()
147
+ if (!variantId || !normalizedLotNumber) return null
148
+
149
+ const params = buildQuery({
150
+ page: 1,
151
+ pageSize: 20,
152
+ catalogVariantId: variantId,
153
+ search: normalizedLotNumber,
154
+ })
155
+ const call = await apiCall<PagedResponse<InventoryLotListRow>>(`/api/wms/lots?${params}`)
156
+ if (!call.ok) return null
157
+
158
+ const match = (call.result?.items ?? []).find(
159
+ (item) => item.lot_number?.trim().toLowerCase() === normalizedLotNumber.toLowerCase(),
160
+ )
161
+ return typeof match?.id === 'string' ? match.id : null
162
+ }
163
+
164
+ export class InventoryLotMutationError extends Error {
165
+ constructor(message = 'Failed to resolve inventory lot.') {
166
+ super(message)
167
+ this.name = 'InventoryLotMutationError'
168
+ }
169
+ }
170
+
171
+ export async function ensureLotIdForInventoryMutation(input: {
172
+ catalogVariantId: string
173
+ lotNumber: string
174
+ organizationId: string
175
+ tenantId: string
176
+ }): Promise<string> {
177
+ const lotNumber = input.lotNumber.trim()
178
+ if (!lotNumber) {
179
+ throw new InventoryLotMutationError('Lot number is required.')
180
+ }
181
+
182
+ const existingId = await findLotIdByNumber(input.catalogVariantId, lotNumber)
183
+ if (existingId) return existingId
184
+
185
+ const sku = await resolveCatalogVariantSku(input.catalogVariantId)
186
+ if (!sku) {
187
+ throw new InventoryLotMutationError('Could not resolve SKU for the selected variant.')
188
+ }
189
+
190
+ const call = await apiCall<{ id?: string | null }>('/api/wms/lots', {
191
+ method: 'POST',
192
+ headers: { 'content-type': 'application/json' },
193
+ body: JSON.stringify({
194
+ organizationId: input.organizationId,
195
+ tenantId: input.tenantId,
196
+ catalogVariantId: input.catalogVariantId,
197
+ sku,
198
+ lotNumber,
199
+ status: 'available',
200
+ }),
201
+ })
202
+
203
+ if (!call.ok) {
204
+ const racedId = await findLotIdByNumber(input.catalogVariantId, lotNumber)
205
+ if (racedId) return racedId
206
+ throw new InventoryLotMutationError('Failed to create inventory lot.')
207
+ }
208
+
209
+ const createdId = call.result?.id?.trim()
210
+ if (createdId) return createdId
211
+
212
+ const resolvedId = await findLotIdByNumber(input.catalogVariantId, lotNumber)
213
+ if (resolvedId) return resolvedId
214
+
215
+ throw new InventoryLotMutationError('Failed to resolve created inventory lot.')
216
+ }
217
+
218
+ type LocationListRow = {
219
+ id?: string | null
220
+ code?: string | null
221
+ type?: string | null
222
+ }
223
+
224
+ type ZoneListRow = {
225
+ id?: string | null
226
+ name?: string | null
227
+ code?: string | null
228
+ warehouse_id?: string | null
229
+ warehouseId?: string | null
230
+ }
231
+
232
+ export type ZoneCrudFieldOption = CrudFieldOption & {
233
+ warehouseId?: string
234
+ }
235
+
236
+ function readZoneWarehouseId(item: ZoneListRow): string | null {
237
+ if (typeof item.warehouse_id === 'string' && item.warehouse_id.trim()) {
238
+ return item.warehouse_id.trim()
239
+ }
240
+ if (typeof item.warehouseId === 'string' && item.warehouseId.trim()) {
241
+ return item.warehouseId.trim()
242
+ }
243
+ return null
244
+ }
245
+
246
+ type AuthUserListRow = {
247
+ id?: string | null
248
+ email?: string | null
249
+ roles?: string[] | null
250
+ }
251
+
252
+ export type AssigneeOptionsResult = {
253
+ options: CrudFieldOption[]
254
+ canListUsers: boolean
255
+ }
256
+
257
+ export type BalanceLookupErrorCode = 'LOOKUP_FAILED' | 'LOT_REQUIRED' | 'LOT_NOT_FOUND'
258
+
259
+ export class BalanceLookupError extends Error {
260
+ code: BalanceLookupErrorCode
261
+ /** Lot IDs present at the ambiguous location (set when `code === 'LOT_REQUIRED'`). */
262
+ candidateLotIds: string[]
263
+
264
+ constructor(
265
+ message = 'Failed to load inventory balance.',
266
+ code: BalanceLookupErrorCode = 'LOOKUP_FAILED',
267
+ candidateLotIds: string[] = [],
268
+ ) {
269
+ super(message)
270
+ this.name = 'BalanceLookupError'
271
+ this.code = code
272
+ this.candidateLotIds = candidateLotIds
273
+ }
274
+ }
275
+
276
+ export class ScopeEstimateError extends Error {
277
+ constructor(message = 'Failed to estimate cycle count scope.') {
278
+ super(message)
279
+ this.name = 'ScopeEstimateError'
280
+ }
281
+ }
282
+
283
+ function compareLocationCodes(left: string, right: string): number {
284
+ return left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' })
285
+ }
286
+
287
+ function isLocationCodeWithinRange(
288
+ code: string,
289
+ fromCode?: string | null,
290
+ toCode?: string | null,
291
+ ): boolean {
292
+ const from = fromCode?.trim()
293
+ const to = toCode?.trim()
294
+ if (from && compareLocationCodes(code, from) < 0) return false
295
+ if (to && compareLocationCodes(code, to) > 0) return false
296
+ return true
297
+ }
298
+
299
+ export function formatCycleCountZoneLabel(
300
+ baseLabel: string,
301
+ stats?: { expectedSkus: number; binCount: number },
302
+ ): string {
303
+ const trimmed = baseLabel.trim()
304
+ if (!trimmed || !stats) return trimmed
305
+ return `${trimmed} · ${stats.expectedSkus} SKUs · ${stats.binCount} bins`
306
+ }
307
+
308
+ export function mapLocationOptions(items: LocationListRow[]): CrudFieldOption[] {
309
+ return items
310
+ .map((item) => {
311
+ const value = typeof item.id === 'string' ? item.id : null
312
+ if (!value) return null
313
+ const label = item.code || value
314
+ return { value, label }
315
+ })
316
+ .filter((option): option is CrudFieldOption => option !== null)
317
+ }
318
+
319
+ export async function loadAllLocations(
320
+ warehouseId: string,
321
+ filters: { type?: string; search?: string },
322
+ ): Promise<LocationListRow[]> {
323
+ const items: LocationListRow[] = []
324
+ let page = 1
325
+ let totalPages = 1
326
+
327
+ while (page <= totalPages && page <= 10) {
328
+ const params = buildQuery({
329
+ page,
330
+ pageSize: 100,
331
+ warehouseId,
332
+ type: filters.type,
333
+ search: filters.search?.trim() || undefined,
334
+ })
335
+ const call = await apiCall<PagedResponse<LocationListRow>>(`/api/wms/locations?${params}`)
336
+ if (!call.ok) break
337
+ items.push(...(call.result?.items ?? []))
338
+ totalPages = call.result?.totalPages ?? 1
339
+ page += 1
340
+ }
341
+
342
+ return items
343
+ }
344
+
345
+ export function filterLocationsByCodeRange(
346
+ locations: LocationListRow[],
347
+ fromCode?: string | null,
348
+ toCode?: string | null,
349
+ ): LocationListRow[] {
350
+ const from = fromCode?.trim()
351
+ const to = toCode?.trim()
352
+ if (!from && !to) return locations
353
+ return locations.filter((location) => {
354
+ const code = location.code?.trim()
355
+ if (!code) return false
356
+ return isLocationCodeWithinRange(code, from, to)
357
+ })
358
+ }
359
+
360
+ async function loadLocationPage(
361
+ warehouseId: string,
362
+ filters: { type?: string; search?: string; page?: number; pageSize?: number },
363
+ ): Promise<PagedResponse<LocationListRow>> {
364
+ const params = buildQuery({
365
+ page: filters.page ?? 1,
366
+ pageSize: filters.pageSize ?? 50,
367
+ warehouseId,
368
+ type: filters.type,
369
+ search: filters.search?.trim() || undefined,
370
+ })
371
+ const call = await apiCall<PagedResponse<LocationListRow>>(`/api/wms/locations?${params}`)
372
+ if (!call.ok) {
373
+ throw new BalanceLookupError('Failed to load warehouse locations.')
374
+ }
375
+ return call.result ?? { items: [], total: 0, totalPages: 0 }
376
+ }
377
+
378
+ export async function loadLocationOptions(
379
+ warehouseId: string,
380
+ query?: string,
381
+ ): Promise<CrudFieldOption[]> {
382
+ if (!warehouseId) return []
383
+ try {
384
+ const page = await loadLocationPage(warehouseId, { search: query })
385
+ return mapLocationOptions(page.items ?? [])
386
+ } catch {
387
+ return []
388
+ }
389
+ }
390
+
391
+ export async function loadBinLocationOptions(
392
+ warehouseId: string,
393
+ query?: string,
394
+ ): Promise<CrudFieldOption[]> {
395
+ if (!warehouseId) return []
396
+ try {
397
+ const page = await loadLocationPage(warehouseId, { type: 'bin', search: query })
398
+ return mapLocationOptions(page.items ?? [])
399
+ } catch {
400
+ return []
401
+ }
402
+ }
403
+
404
+ export async function loadZoneOptions(
405
+ warehouseId: string,
406
+ query?: string,
407
+ ): Promise<ZoneCrudFieldOption[]> {
408
+ const scopedWarehouseId = warehouseId.trim()
409
+ if (!scopedWarehouseId) return []
410
+ const params = buildQuery({
411
+ page: 1,
412
+ pageSize: 50,
413
+ warehouseId: scopedWarehouseId,
414
+ search: query?.trim() || undefined,
415
+ })
416
+ const call = await apiCall<PagedResponse<ZoneListRow>>(`/api/wms/zones?${params}`)
417
+ if (!call.ok) return []
418
+ return (call.result?.items ?? [])
419
+ .map((item) => {
420
+ const value = typeof item.id === 'string' ? item.id : null
421
+ if (!value) return null
422
+ const label = item.name?.trim() || item.code?.trim() || value
423
+ const zoneWarehouseId = readZoneWarehouseId(item)
424
+ return {
425
+ value,
426
+ label,
427
+ ...(zoneWarehouseId ? { warehouseId: zoneWarehouseId } : {}),
428
+ }
429
+ })
430
+ .filter((option): option is ZoneCrudFieldOption => option !== null)
431
+ }
432
+
433
+ export async function resolveZoneLabel(zoneId: string): Promise<string | null> {
434
+ const [option] = await loadCrudOptionsByIds<ZoneListRow>(
435
+ '/api/wms/zones',
436
+ [zoneId],
437
+ (item) => {
438
+ const value = typeof item.id === 'string' ? item.id : null
439
+ if (!value) return null
440
+ return { value, label: item.name?.trim() || item.code?.trim() || value }
441
+ },
442
+ )
443
+ return option?.label ?? null
444
+ }
445
+
446
+ export async function resolveZoneWarehouseId(zoneId: string): Promise<string | null> {
447
+ const id = zoneId.trim()
448
+ if (!id) return null
449
+ const [option] = await loadCrudOptionsByIds<ZoneListRow>(
450
+ '/api/wms/zones',
451
+ [id],
452
+ (item) => {
453
+ const value = typeof item.id === 'string' ? item.id : null
454
+ const warehouseId = readZoneWarehouseId(item)
455
+ if (!value || !warehouseId) return null
456
+ return { value, label: warehouseId }
457
+ },
458
+ )
459
+ return option?.label ?? null
460
+ }
461
+
462
+ export async function loadAssigneeOptions(
463
+ query?: string,
464
+ fallback?: { userId: string; label: string },
465
+ ): Promise<AssigneeOptionsResult> {
466
+ const fallbackUserId = fallback?.userId.trim()
467
+ const fallbackLabel = fallback?.label.trim()
468
+ const fallbackOption =
469
+ fallbackUserId && fallbackLabel
470
+ ? [{ value: fallbackUserId, label: fallbackLabel }]
471
+ : []
472
+
473
+ const params = buildQuery({
474
+ page: 1,
475
+ pageSize: 50,
476
+ search: query?.trim() || undefined,
477
+ })
478
+ const call = await apiCall<PagedResponse<AuthUserListRow>>(`/api/auth/users?${params}`)
479
+ if (!call.ok) {
480
+ return {
481
+ options: fallbackOption,
482
+ canListUsers: false,
483
+ }
484
+ }
485
+
486
+ const options = (call.result?.items ?? [])
487
+ .map((item) => {
488
+ const value = typeof item.id === 'string' ? item.id : null
489
+ if (!value) return null
490
+ const email = item.email?.trim() || value
491
+ const role = item.roles?.find((entry) => entry.trim().length > 0)?.trim()
492
+ const label = role ? `${email} (${role})` : email
493
+ return { value, label }
494
+ })
495
+ .filter((option): option is CrudFieldOption => option !== null)
496
+
497
+ return {
498
+ options,
499
+ canListUsers: true,
500
+ }
501
+ }
502
+
503
+ export async function resolveAssigneeLabel(userId: string): Promise<string | null> {
504
+ const params = buildQuery({ page: 1, pageSize: 1, id: userId.trim() })
505
+ const call = await apiCall<PagedResponse<AuthUserListRow>>(`/api/auth/users?${params}`)
506
+ if (!call.ok) return null
507
+ const item = call.result?.items?.[0]
508
+ if (!item?.id) return null
509
+ const email = item.email?.trim() || item.id
510
+ const role = item.roles?.find((entry) => entry.trim().length > 0)?.trim()
511
+ return role ? `${email} (${role})` : email
512
+ }
513
+
514
+ export async function fetchCycleCountScopeEstimate(input: {
515
+ warehouseId: string
516
+ fromLocationId?: string | null
517
+ toLocationId?: string | null
518
+ }): Promise<{ expectedSkus: number; binCount: number }> {
519
+ const warehouseId = input.warehouseId.trim()
520
+ if (!warehouseId) return { expectedSkus: 0, binCount: 0 }
521
+
522
+ const bins = await loadAllLocations(warehouseId, { type: 'bin' })
523
+ const locationById = new Map(
524
+ bins
525
+ .map((location) => {
526
+ const id = location.id?.trim()
527
+ return id ? [id, location] as const : null
528
+ })
529
+ .filter((entry): entry is readonly [string, LocationListRow] => entry !== null),
530
+ )
531
+
532
+ const fromCode = input.fromLocationId ? locationById.get(input.fromLocationId.trim())?.code : undefined
533
+ const toCode = input.toLocationId ? locationById.get(input.toLocationId.trim())?.code : undefined
534
+ const scopedBins = filterLocationsByCodeRange(bins, fromCode, toCode)
535
+ const scopedBinIds = new Set(
536
+ scopedBins.map((location) => location.id?.trim()).filter((id): id is string => Boolean(id)),
537
+ )
538
+ const binCount = scopedBinIds.size
539
+
540
+ const variantIds = new Set<string>()
541
+ let page = 1
542
+ let totalPages = 1
543
+
544
+ while (page <= totalPages && page <= 10) {
545
+ const params = buildQuery({
546
+ page,
547
+ pageSize: 100,
548
+ warehouseId,
549
+ })
550
+ const call = await apiCall<PagedResponse<{ catalog_variant_id?: string | null; location_id?: string | null; quantity_on_hand?: string | number | null }>>(
551
+ `/api/wms/inventory/balances?${params}`,
552
+ )
553
+ if (!call.ok) {
554
+ throw new ScopeEstimateError()
555
+ }
556
+
557
+ for (const row of call.result?.items ?? []) {
558
+ const locationId = row.location_id?.trim()
559
+ const variantId = row.catalog_variant_id?.trim()
560
+ const onHand = Number(row.quantity_on_hand ?? 0)
561
+ if (!locationId || !variantId || !Number.isFinite(onHand) || onHand <= 0) continue
562
+ if (scopedBinIds.size > 0 && !scopedBinIds.has(locationId)) continue
563
+ variantIds.add(variantId)
564
+ }
565
+
566
+ totalPages = call.result?.totalPages ?? 1
567
+ page += 1
568
+ }
569
+
570
+ return { expectedSkus: variantIds.size, binCount }
571
+ }
572
+
573
+ export type InventoryBalanceLookupRow = {
574
+ lot_id?: string | null
575
+ quantity_on_hand?: string | number | null
576
+ quantity_available?: number | null
577
+ }
578
+
579
+ function normalizeBalanceLotId(value: string | null | undefined): string | null {
580
+ const trimmed = value?.trim()
581
+ return trimmed || null
582
+ }
583
+
584
+ function readBalanceAvailable(row: InventoryBalanceLookupRow): number {
585
+ const available = Number(row.quantity_available ?? row.quantity_on_hand ?? 0)
586
+ return Number.isFinite(available) ? available : 0
587
+ }
588
+
589
+ function selectBalanceLookupRow(
590
+ items: InventoryBalanceLookupRow[],
591
+ lotId?: string | null,
592
+ ): InventoryBalanceLookupRow | null {
593
+ if (items.length === 0) return null
594
+ if (items.length === 1) return items[0]
595
+
596
+ const normalizedLotId = normalizeBalanceLotId(lotId)
597
+ if (normalizedLotId) {
598
+ const exactMatch = items.find(
599
+ (row) => normalizeBalanceLotId(row.lot_id) === normalizedLotId,
600
+ )
601
+ if (exactMatch) return exactMatch
602
+ throw new BalanceLookupError('No balance bucket matches the selected lot.', 'LOT_NOT_FOUND')
603
+ }
604
+
605
+ const withoutLot = items.filter((row) => !normalizeBalanceLotId(row.lot_id))
606
+ if (withoutLot.length === 1) return withoutLot[0]
607
+
608
+ const candidateLotIds = [
609
+ ...new Set(
610
+ items
611
+ .map((row) => normalizeBalanceLotId(row.lot_id))
612
+ .filter((id): id is string => Boolean(id)),
613
+ ),
614
+ ]
615
+ throw new BalanceLookupError(
616
+ 'Multiple balance buckets match this location; specify a lot.',
617
+ 'LOT_REQUIRED',
618
+ candidateLotIds,
619
+ )
620
+ }
621
+
622
+ export async function fetchBalanceAvailable(input: {
623
+ warehouseId: string
624
+ locationId: string
625
+ catalogVariantId: string
626
+ lotId?: string | null
627
+ }): Promise<number> {
628
+ const params = buildQuery({
629
+ page: 1,
630
+ pageSize: 20,
631
+ warehouseId: input.warehouseId,
632
+ locationId: input.locationId,
633
+ catalogVariantId: input.catalogVariantId,
634
+ lotId: input.lotId?.trim() || undefined,
635
+ })
636
+ const call = await apiCall<PagedResponse<InventoryBalanceLookupRow>>(
637
+ `/api/wms/inventory/balances?${params}`,
638
+ )
639
+ if (!call.ok) {
640
+ throw new BalanceLookupError()
641
+ }
642
+ const row = selectBalanceLookupRow(call.result?.items ?? [], input.lotId)
643
+ if (!row) return 0
644
+ return readBalanceAvailable(row)
645
+ }
646
+
647
+ function mapInventoryLotListRowToOption(item: InventoryLotListRow): CrudFieldOption | null {
648
+ const value = typeof item.id === 'string' ? item.id : null
649
+ if (!value) return null
650
+ const lotNumber = item.lot_number?.trim() || value
651
+ const expiresAt = item.expires_at?.trim()
652
+ const label = expiresAt ? `${lotNumber} · exp ${expiresAt.slice(0, 10)}` : lotNumber
653
+ return { value, label }
654
+ }
655
+
656
+ function mapInventoryLotListRowToLotNumberOption(item: InventoryLotListRow): CrudFieldOption | null {
657
+ const lotNumber = item.lot_number?.trim()
658
+ if (!lotNumber) return null
659
+ const expiresAt = item.expires_at?.trim()
660
+ const label = expiresAt ? `${lotNumber} · exp ${expiresAt.slice(0, 10)}` : lotNumber
661
+ return { value: lotNumber, label }
662
+ }
663
+
664
+ async function loadInventoryLotListOptions(
665
+ catalogVariantId: string,
666
+ query: string | undefined,
667
+ mapItem: (item: InventoryLotListRow) => CrudFieldOption | null,
668
+ ): Promise<CrudFieldOption[]> {
669
+ if (!catalogVariantId) return []
670
+ const params = buildQuery({
671
+ page: 1,
672
+ pageSize: 50,
673
+ catalogVariantId,
674
+ status: 'available',
675
+ search: query?.trim() || undefined,
676
+ })
677
+ const call = await apiCall<PagedResponse<InventoryLotListRow>>(`/api/wms/lots?${params}`)
678
+ if (!call.ok) return []
679
+ return (call.result?.items ?? [])
680
+ .map(mapItem)
681
+ .filter((option): option is CrudFieldOption => option !== null)
682
+ }
683
+
684
+ export async function loadLotOptions(
685
+ catalogVariantId: string,
686
+ query?: string,
687
+ ): Promise<CrudFieldOption[]> {
688
+ return loadInventoryLotListOptions(catalogVariantId, query, mapInventoryLotListRowToOption)
689
+ }
690
+
691
+ /** Resolve display options for known lot IDs (e.g. candidates from a LOT_REQUIRED balance lookup). */
692
+ export async function loadLotOptionsByIds(lotIds: string[]): Promise<CrudFieldOption[]> {
693
+ return loadCrudOptionsByIds<InventoryLotListRow>(
694
+ '/api/wms/lots',
695
+ lotIds,
696
+ mapInventoryLotListRowToOption,
697
+ )
698
+ }
699
+
700
+ // Scopes lot suggestions to the lots actually present in the given warehouse
701
+ // location's balance buckets, instead of every lot ever created for the
702
+ // variant. This is what backs the LOT_REQUIRED disambiguation flow: when a
703
+ // location holds multiple lot-bearing balances for the same variant, the
704
+ // candidates offered here are exactly the ones `selectBalanceLookupRow` would
705
+ // otherwise reject as ambiguous.
706
+ export async function loadLotOptionsForBalanceLocation(input: {
707
+ warehouseId: string
708
+ locationId: string
709
+ catalogVariantId: string
710
+ query?: string
711
+ }): Promise<CrudFieldOption[]> {
712
+ const warehouseId = input.warehouseId.trim()
713
+ const locationId = input.locationId.trim()
714
+ const catalogVariantId = input.catalogVariantId.trim()
715
+ if (!warehouseId || !locationId || !catalogVariantId) return []
716
+
717
+ const params = buildQuery({
718
+ page: 1,
719
+ pageSize: 50,
720
+ warehouseId,
721
+ locationId,
722
+ catalogVariantId,
723
+ })
724
+ const call = await apiCall<PagedResponse<InventoryBalanceLookupRow>>(
725
+ `/api/wms/inventory/balances?${params}`,
726
+ )
727
+ if (!call.ok) return []
728
+
729
+ const lotIds = [
730
+ ...new Set(
731
+ (call.result?.items ?? [])
732
+ .map((row) => normalizeBalanceLotId(row.lot_id))
733
+ .filter((id): id is string => Boolean(id)),
734
+ ),
735
+ ]
736
+ if (lotIds.length === 0) return []
737
+
738
+ const options = await loadCrudOptionsByIds<InventoryLotListRow>(
739
+ '/api/wms/lots',
740
+ lotIds,
741
+ mapInventoryLotListRowToOption,
742
+ )
743
+
744
+ const query = input.query?.trim().toLowerCase()
745
+ if (!query) return options
746
+ return options.filter((option) => option.label.toLowerCase().includes(query))
747
+ }
748
+
749
+ export async function loadLotNumberOptions(
750
+ catalogVariantId: string,
751
+ query?: string,
752
+ ): Promise<CrudFieldOption[]> {
753
+ return loadInventoryLotListOptions(catalogVariantId, query, mapInventoryLotListRowToLotNumberOption)
754
+ }
755
+
756
+ export class ScopeQueueError extends Error {
757
+ constructor(message = 'Failed to build cycle count scope queue.') {
758
+ super(message)
759
+ this.name = 'ScopeQueueError'
760
+ }
761
+ }
762
+
763
+ export type ScopeQueueItem = {
764
+ locationId: string
765
+ locationCode: string
766
+ catalogVariantId: string
767
+ lotId: string | null
768
+ expectedOnHand: number
769
+ }
770
+
771
+ export async function buildCycleCountScopeQueue(input: {
772
+ warehouseId: string
773
+ fromLocationId?: string | null
774
+ toLocationId?: string | null
775
+ }): Promise<ScopeQueueItem[]> {
776
+ const warehouseId = input.warehouseId.trim()
777
+ if (!warehouseId) return []
778
+
779
+ const bins = await loadAllLocations(warehouseId, { type: 'bin' })
780
+ const binById = new Map(
781
+ bins
782
+ .map((b) => {
783
+ const id = b.id?.trim()
784
+ return id ? ([id, b] as const) : null
785
+ })
786
+ .filter((entry): entry is readonly [string, LocationListRow] => entry !== null),
787
+ )
788
+
789
+ const fromCode = input.fromLocationId?.trim()
790
+ ? binById.get(input.fromLocationId.trim())?.code
791
+ : undefined
792
+ const toCode = input.toLocationId?.trim()
793
+ ? binById.get(input.toLocationId.trim())?.code
794
+ : undefined
795
+
796
+ const scopedBins = filterLocationsByCodeRange(bins, fromCode, toCode)
797
+ const scopedBinIds = new Set(
798
+ scopedBins.map((b) => b.id?.trim()).filter((id): id is string => Boolean(id)),
799
+ )
800
+
801
+ if (scopedBinIds.size === 0) return []
802
+
803
+ type BalanceRow = {
804
+ catalog_variant_id?: string | null
805
+ location_id?: string | null
806
+ lot_id?: string | null
807
+ quantity_on_hand?: string | number | null
808
+ }
809
+
810
+ const items: ScopeQueueItem[] = []
811
+ let page = 1
812
+ let totalPages = 1
813
+
814
+ while (page <= totalPages && page <= 20) {
815
+ const params = buildQuery({ page, pageSize: 100, warehouseId })
816
+ const call = await apiCall<PagedResponse<BalanceRow>>(
817
+ `/api/wms/inventory/balances?${params}`,
818
+ )
819
+ if (!call.ok) throw new ScopeQueueError()
820
+
821
+ for (const row of call.result?.items ?? []) {
822
+ const locationId = row.location_id?.trim()
823
+ const variantId = row.catalog_variant_id?.trim()
824
+ const lotId = row.lot_id?.trim() || null
825
+ const onHand = Number(row.quantity_on_hand ?? 0)
826
+
827
+ if (!locationId || !variantId) continue
828
+ if (!scopedBinIds.has(locationId)) continue
829
+ if (!Number.isFinite(onHand) || onHand <= 0) continue
830
+
831
+ const bin = binById.get(locationId)
832
+ items.push({
833
+ locationId,
834
+ locationCode: bin?.code?.trim() ?? locationId,
835
+ catalogVariantId: variantId,
836
+ lotId,
837
+ expectedOnHand: onHand,
838
+ })
839
+ }
840
+
841
+ totalPages = call.result?.totalPages ?? 1
842
+ page += 1
843
+ }
844
+
845
+ items.sort((a, b) => {
846
+ const cmp = a.locationCode.localeCompare(b.locationCode, undefined, {
847
+ numeric: true,
848
+ sensitivity: 'base',
849
+ })
850
+ return cmp !== 0 ? cmp : a.catalogVariantId.localeCompare(b.catalogVariantId)
851
+ })
852
+
853
+ return items
854
+ }
855
+
856
+ export async function fetchVariantReorderPoint(catalogVariantId: string): Promise<number> {
857
+ if (!catalogVariantId) return 0
858
+ const params = buildQuery({
859
+ page: 1,
860
+ pageSize: 1,
861
+ catalogVariantId,
862
+ })
863
+ const call = await apiCall<PagedResponse<{ reorder_point?: string | number | null }>>(
864
+ `/api/wms/inventory-profiles?${params}`,
865
+ )
866
+ if (!call.ok) return 0
867
+ const row = call.result?.items?.[0]
868
+ if (!row) return 0
869
+ const reorderPoint = Number(row.reorder_point ?? 0)
870
+ return Number.isFinite(reorderPoint) ? reorderPoint : 0
871
+ }
872
+
873
+ export type LocationCapacitySnapshot = {
874
+ capacityUnits: number | null
875
+ totalOnHand: number
876
+ }
877
+
878
+ async function fetchLocationCapacityUnits(locationId: string): Promise<number | null> {
879
+ const id = locationId.trim()
880
+ if (!id) return null
881
+ const params = buildQuery({ page: 1, pageSize: 1, ids: id })
882
+ const call = await apiCall<PagedResponse<{ id?: string | null; capacity_units?: string | number | null }>>(
883
+ `/api/wms/locations?${params}`,
884
+ )
885
+ if (!call.ok) return null
886
+ const raw = call.result?.items?.[0]?.capacity_units
887
+ if (raw == null) return null
888
+ const value = Number(raw)
889
+ return Number.isFinite(value) ? value : null
890
+ }
891
+
892
+ const LOCATION_TOTAL_ON_HAND_PAGE_SIZE = 100
893
+ const LOCATION_TOTAL_ON_HAND_MAX_PAGES = 50
894
+
895
+ async function fetchLocationTotalOnHand(input: {
896
+ warehouseId: string
897
+ locationId: string
898
+ }): Promise<number> {
899
+ const locationId = input.locationId.trim()
900
+ if (!locationId) return 0
901
+ let sum = 0
902
+ let page = 1
903
+ let totalPages = 1
904
+ do {
905
+ const params = buildQuery({
906
+ page,
907
+ pageSize: LOCATION_TOTAL_ON_HAND_PAGE_SIZE,
908
+ warehouseId: input.warehouseId,
909
+ locationId,
910
+ })
911
+ const call = await apiCall<PagedResponse<{ quantity_on_hand?: string | number | null }>>(
912
+ `/api/wms/inventory/balances?${params}`,
913
+ )
914
+ if (!call.ok) break
915
+ for (const row of call.result?.items ?? []) {
916
+ const value = Number(row.quantity_on_hand ?? 0)
917
+ if (Number.isFinite(value)) sum += value
918
+ }
919
+ totalPages = call.result?.totalPages ?? 1
920
+ page += 1
921
+ } while (page <= totalPages && page <= LOCATION_TOTAL_ON_HAND_MAX_PAGES)
922
+ return sum
923
+ }
924
+
925
+ export async function fetchLocationCapacitySnapshot(input: {
926
+ warehouseId: string
927
+ locationId: string
928
+ }): Promise<LocationCapacitySnapshot> {
929
+ const [capacityUnits, totalOnHand] = await Promise.all([
930
+ fetchLocationCapacityUnits(input.locationId),
931
+ fetchLocationTotalOnHand(input),
932
+ ])
933
+ return { capacityUnits, totalOnHand }
934
+ }
935
+
936
+ export async function fetchBalanceOnHand(input: {
937
+ warehouseId: string
938
+ locationId: string
939
+ catalogVariantId: string
940
+ lotId?: string | null
941
+ }): Promise<number> {
942
+ const params = buildQuery({
943
+ page: 1,
944
+ pageSize: 20,
945
+ warehouseId: input.warehouseId,
946
+ locationId: input.locationId,
947
+ catalogVariantId: input.catalogVariantId,
948
+ lotId: input.lotId?.trim() || undefined,
949
+ })
950
+ const call = await apiCall<PagedResponse<InventoryBalanceLookupRow>>(
951
+ `/api/wms/inventory/balances?${params}`,
952
+ )
953
+ if (!call.ok) {
954
+ throw new BalanceLookupError()
955
+ }
956
+ const row = selectBalanceLookupRow(call.result?.items ?? [], input.lotId)
957
+ if (!row) return 0
958
+ const onHand = Number(row.quantity_on_hand ?? 0)
959
+ return Number.isFinite(onHand) ? onHand : 0
960
+ }