@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,1783 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { z } from "zod";
5
+ import { useQueryClient } from "@tanstack/react-query";
6
+ import { Check, Minus, PackageSearch, Plus, Warehouse } from "lucide-react";
7
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
8
+ import { flashMutationError } from "../../lib/flashMutationError.js";
9
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
10
+ import { ComboboxInput } from "@open-mercato/ui/backend/inputs/ComboboxInput";
11
+ import { DateTimePicker } from "@open-mercato/ui/backend/inputs/DateTimePicker";
12
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
13
+ import { raiseCrudError } from "@open-mercato/ui/backend/utils/serverErrors";
14
+ import { Button } from "@open-mercato/ui/primitives/button";
15
+ import {
16
+ Dialog,
17
+ DialogContent,
18
+ DialogDescription,
19
+ DialogFooter,
20
+ DialogHeader,
21
+ DialogTitle
22
+ } from "@open-mercato/ui/primitives/dialog";
23
+ import { FormField } from "@open-mercato/ui/primitives/form-field";
24
+ import { IconButton } from "@open-mercato/ui/primitives/icon-button";
25
+ import { Input } from "@open-mercato/ui/primitives/input";
26
+ import { KbdShortcut } from "@open-mercato/ui/primitives/kbd";
27
+ import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
28
+ import { Switch } from "@open-mercato/ui/primitives/switch";
29
+ import { Textarea } from "@open-mercato/ui/primitives/textarea";
30
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
31
+ import { createLogger } from "@open-mercato/shared/lib/logger";
32
+ import { cn } from "@open-mercato/shared/lib/utils";
33
+ import {
34
+ buildInventoryMutationReferenceId,
35
+ computeCycleCountVariance,
36
+ formatSignedQuantity
37
+ } from "../../lib/inventoryMutationUi.js";
38
+ import {
39
+ BalanceLookupError,
40
+ ScopeEstimateError,
41
+ ScopeQueueError,
42
+ buildCycleCountScopeQueue,
43
+ fetchBalanceOnHand,
44
+ fetchCycleCountScopeEstimate,
45
+ filterLocationsByCodeRange,
46
+ formatCycleCountZoneLabel,
47
+ loadAllLocations,
48
+ loadAssigneeOptions,
49
+ loadBinLocationOptions,
50
+ loadCatalogVariantOptions,
51
+ loadLocationOptions,
52
+ loadLotOptionsByIds,
53
+ loadLotOptionsForBalanceLocation,
54
+ loadWarehouseOptions,
55
+ loadZoneOptions,
56
+ mapLocationOptions,
57
+ resolveAssigneeLabel,
58
+ resolveCatalogVariantLabel,
59
+ resolveLocationLabel,
60
+ resolveLotLabel,
61
+ resolveWarehouseLabel,
62
+ resolveZoneLabel
63
+ } from "./inventoryMutationLoaders.js";
64
+ const logger = createLogger("wms");
65
+ const EMPTY_FORM = {
66
+ zoneId: "",
67
+ warehouseId: "",
68
+ fromLocationId: "",
69
+ toLocationId: "",
70
+ scheduledAt: "",
71
+ assigneeId: "",
72
+ expectedSkus: 0,
73
+ locationId: "",
74
+ catalogVariantId: "",
75
+ lotId: "",
76
+ countedQuantity: 0,
77
+ setupNotes: "",
78
+ countNotes: "",
79
+ reason: "cycle_count"
80
+ };
81
+ function CycleCountStepIndicator({ step }) {
82
+ const steps = [1, 2, 3];
83
+ return /* @__PURE__ */ jsx(
84
+ "div",
85
+ {
86
+ className: "flex items-center gap-1.5 pt-1",
87
+ "aria-label": `Step ${step} of 3`,
88
+ children: steps.map((stepNumber, index) => {
89
+ const completed = stepNumber < step;
90
+ const current = stepNumber === step;
91
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
92
+ index > 0 ? /* @__PURE__ */ jsx(
93
+ "div",
94
+ {
95
+ className: cn(
96
+ "h-0.5 w-4 shrink-0",
97
+ stepNumber <= step ? "bg-foreground" : "bg-border"
98
+ ),
99
+ "aria-hidden": "true"
100
+ }
101
+ ) : null,
102
+ /* @__PURE__ */ jsx(
103
+ "div",
104
+ {
105
+ className: cn(
106
+ "flex size-5 shrink-0 items-center justify-center rounded-full text-xs font-semibold",
107
+ completed || current ? "bg-foreground text-primary-foreground" : "bg-muted text-muted-foreground"
108
+ ),
109
+ "aria-current": current ? "step" : void 0,
110
+ children: completed ? /* @__PURE__ */ jsx(Check, { className: "size-3", "aria-hidden": "true" }) : stepNumber
111
+ }
112
+ )
113
+ ] }, stepNumber);
114
+ })
115
+ }
116
+ );
117
+ }
118
+ function SummaryPanel({
119
+ title,
120
+ children
121
+ }) {
122
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-muted/40 px-4 py-3.5", children: [
123
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: title }),
124
+ children
125
+ ] });
126
+ }
127
+ function CycleCountWizardDialog({
128
+ open,
129
+ onOpenChange,
130
+ access,
131
+ initialCatalogVariantId,
132
+ initialWarehouseId,
133
+ initialLocationId,
134
+ initialLotId
135
+ }) {
136
+ const t = useT();
137
+ const queryClient = useQueryClient();
138
+ const formRef = React.useRef(null);
139
+ const { runMutation, retryLastMutation } = useGuardedMutation({
140
+ contextId: "wms-inventory-cycle-count"
141
+ });
142
+ const mutationContext = React.useMemo(
143
+ () => ({ retryLastMutation }),
144
+ [retryLastMutation]
145
+ );
146
+ const [step, setStep] = React.useState(1);
147
+ const [form, setForm] = React.useState(EMPTY_FORM);
148
+ const [fieldErrors, setFieldErrors] = React.useState({});
149
+ const [systemOnHand, setSystemOnHand] = React.useState(0);
150
+ const [loadingBalance, setLoadingBalance] = React.useState(false);
151
+ const [submitting, setSubmitting] = React.useState(false);
152
+ const [linesPosted, setLinesPosted] = React.useState(0);
153
+ const [autoAdjust, setAutoAdjust] = React.useState(true);
154
+ const [referenceId] = React.useState(() => buildInventoryMutationReferenceId());
155
+ const [optionLabelByValue, setOptionLabelByValue] = React.useState({});
156
+ const [loadingScopeEstimate, setLoadingScopeEstimate] = React.useState(false);
157
+ const [scopeStats, setScopeStats] = React.useState(
158
+ null
159
+ );
160
+ const [expectedSkusTouched, setExpectedSkusTouched] = React.useState(false);
161
+ const [zoneSuggestions, setZoneSuggestions] = React.useState([]);
162
+ const [lotSuggestions, setLotSuggestions] = React.useState([]);
163
+ const [assigneeCanListUsers, setAssigneeCanListUsers] = React.useState(true);
164
+ const [scopeEstimateError, setScopeEstimateError] = React.useState(null);
165
+ const [balanceError, setBalanceError] = React.useState(null);
166
+ const [scopeQueue, setScopeQueue] = React.useState([]);
167
+ const [scopeQueueIndex, setScopeQueueIndex] = React.useState(0);
168
+ const [scopeQueueLoading, setScopeQueueLoading] = React.useState(false);
169
+ const [scopeQueueReady, setScopeQueueReady] = React.useState(false);
170
+ const [scopeQueueError, setScopeQueueError] = React.useState(null);
171
+ const [finishConfirmPending, setFinishConfirmPending] = React.useState(false);
172
+ const scopeQueueRef = React.useRef([]);
173
+ const scopeQueueIndexRef = React.useRef(0);
174
+ scopeQueueRef.current = scopeQueue;
175
+ scopeQueueIndexRef.current = scopeQueueIndex;
176
+ const registerOptionLabels = React.useCallback(
177
+ (options) => {
178
+ setOptionLabelByValue((current) => {
179
+ let changed = false;
180
+ const next = { ...current };
181
+ for (const option of options) {
182
+ const value = option.value.trim();
183
+ const label = option.label.trim();
184
+ if (!value || !label || next[value] === label) continue;
185
+ next[value] = label;
186
+ changed = true;
187
+ }
188
+ return changed ? next : current;
189
+ });
190
+ },
191
+ []
192
+ );
193
+ const seedLotSuggestionsFromCandidates = React.useCallback(
194
+ async (candidateLotIds) => {
195
+ if (candidateLotIds.length === 0) {
196
+ setLotSuggestions([]);
197
+ return;
198
+ }
199
+ const options = await loadLotOptionsByIds(candidateLotIds);
200
+ registerOptionLabels(options);
201
+ setLotSuggestions(options.map((option) => ({ value: option.value, label: option.label })));
202
+ },
203
+ [registerOptionLabels]
204
+ );
205
+ const resolveOptionLabel = React.useCallback(
206
+ (value) => optionLabelByValue[value] ?? value,
207
+ [optionLabelByValue]
208
+ );
209
+ const optionLabelByValueRef = React.useRef(optionLabelByValue);
210
+ optionLabelByValueRef.current = optionLabelByValue;
211
+ const zoneWarehouseByZoneIdRef = React.useRef({});
212
+ const rememberZoneWarehouseOptions = React.useCallback(
213
+ (options) => {
214
+ for (const option of options) {
215
+ const zoneId = option.value.trim();
216
+ const warehouseId = option.warehouseId?.trim();
217
+ if (!zoneId || !warehouseId) continue;
218
+ zoneWarehouseByZoneIdRef.current[zoneId] = warehouseId;
219
+ }
220
+ },
221
+ []
222
+ );
223
+ const variance = computeCycleCountVariance(systemOnHand, form.countedQuantity);
224
+ const setupSchema = React.useMemo(
225
+ () => z.object({
226
+ warehouseId: z.string().uuid(),
227
+ zoneId: z.string().uuid(),
228
+ fromLocationId: z.string().uuid().optional().or(z.literal("")),
229
+ toLocationId: z.string().uuid().optional().or(z.literal("")),
230
+ assigneeId: z.string().uuid(),
231
+ expectedSkus: z.coerce.number().int().min(1),
232
+ setupNotes: z.string().trim().max(500).optional()
233
+ }),
234
+ []
235
+ );
236
+ const countSchema = React.useMemo(
237
+ () => z.object({
238
+ warehouseId: z.string().uuid(),
239
+ locationId: z.string().uuid(),
240
+ catalogVariantId: z.string().uuid(),
241
+ lotId: z.string().uuid().optional().or(z.literal("")),
242
+ countedQuantity: z.coerce.number().min(0),
243
+ countNotes: z.string().trim().max(500).optional()
244
+ }),
245
+ []
246
+ );
247
+ const commitSchema = React.useMemo(
248
+ () => z.object({
249
+ reason: z.string().trim().min(1).max(500)
250
+ }),
251
+ []
252
+ );
253
+ const resetWizard = React.useCallback(() => {
254
+ setStep(1);
255
+ setForm(EMPTY_FORM);
256
+ setFieldErrors({});
257
+ setSystemOnHand(0);
258
+ setLoadingBalance(false);
259
+ setSubmitting(false);
260
+ setAutoAdjust(true);
261
+ setOptionLabelByValue({});
262
+ zoneWarehouseByZoneIdRef.current = {};
263
+ setLoadingScopeEstimate(false);
264
+ setScopeStats(null);
265
+ setExpectedSkusTouched(false);
266
+ setZoneSuggestions([]);
267
+ setLotSuggestions([]);
268
+ setAssigneeCanListUsers(true);
269
+ setScopeEstimateError(null);
270
+ setBalanceError(null);
271
+ setLinesPosted(0);
272
+ setScopeQueue([]);
273
+ setScopeQueueIndex(0);
274
+ setScopeQueueLoading(false);
275
+ setScopeQueueReady(false);
276
+ setScopeQueueError(null);
277
+ setFinishConfirmPending(false);
278
+ }, []);
279
+ const resetToStep2 = React.useCallback(() => {
280
+ const currentQueue = scopeQueueRef.current;
281
+ const currentIndex = scopeQueueIndexRef.current;
282
+ const nextIndex = currentIndex + 1;
283
+ const nextItem = currentQueue[nextIndex];
284
+ setStep(2);
285
+ setFieldErrors({});
286
+ setSystemOnHand(0);
287
+ setLoadingBalance(false);
288
+ setBalanceError(null);
289
+ setAutoAdjust(true);
290
+ setFinishConfirmPending(false);
291
+ if (nextItem) {
292
+ setScopeQueueIndex(nextIndex);
293
+ setForm((current) => ({
294
+ ...current,
295
+ locationId: nextItem.locationId,
296
+ catalogVariantId: nextItem.catalogVariantId,
297
+ lotId: nextItem.lotId ?? "",
298
+ countedQuantity: 0,
299
+ countNotes: "",
300
+ reason: "cycle_count"
301
+ }));
302
+ } else {
303
+ setForm((current) => ({
304
+ ...current,
305
+ locationId: "",
306
+ catalogVariantId: "",
307
+ lotId: "",
308
+ countedQuantity: 0,
309
+ countNotes: "",
310
+ reason: "cycle_count"
311
+ }));
312
+ }
313
+ }, []);
314
+ const closeDialog = React.useCallback(() => {
315
+ onOpenChange(false);
316
+ resetWizard();
317
+ }, [onOpenChange, resetWizard]);
318
+ const patchForm = React.useCallback((patch) => {
319
+ setForm((current) => ({ ...current, ...patch }));
320
+ setFieldErrors({});
321
+ }, []);
322
+ React.useEffect(() => {
323
+ if (!open) return;
324
+ const catalogVariantId = initialCatalogVariantId?.trim();
325
+ const warehouseId = initialWarehouseId?.trim();
326
+ const locationId = initialLocationId?.trim();
327
+ const lotId = initialLotId?.trim();
328
+ setForm((current) => ({
329
+ ...current,
330
+ scheduledAt: current.scheduledAt.trim() || (/* @__PURE__ */ new Date()).toISOString(),
331
+ assigneeId: current.assigneeId.trim() || access.userId?.trim() || "",
332
+ ...catalogVariantId ? { catalogVariantId } : {},
333
+ ...warehouseId ? { warehouseId } : {},
334
+ ...lotId ? { lotId } : {},
335
+ ...locationId ? {
336
+ locationId,
337
+ fromLocationId: locationId,
338
+ toLocationId: locationId
339
+ } : {}
340
+ }));
341
+ }, [access.userId, initialCatalogVariantId, initialLocationId, initialLotId, initialWarehouseId, open]);
342
+ const assigneeFallback = React.useMemo(() => {
343
+ const userId = access.userId?.trim();
344
+ if (!userId) return null;
345
+ const label = resolveOptionLabel(userId);
346
+ return { userId, label: label === userId ? userId : label };
347
+ }, [access.userId, resolveOptionLabel]);
348
+ React.useEffect(() => {
349
+ if (!open || !assigneeFallback) return;
350
+ let cancelled = false;
351
+ void loadAssigneeOptions(void 0, assigneeFallback).then((result) => {
352
+ if (cancelled) return;
353
+ setAssigneeCanListUsers(result.canListUsers);
354
+ registerOptionLabels(result.options);
355
+ });
356
+ return () => {
357
+ cancelled = true;
358
+ };
359
+ }, [assigneeFallback, open, registerOptionLabels]);
360
+ React.useEffect(() => {
361
+ if (!open) return;
362
+ const warehouseId = form.warehouseId.trim();
363
+ if (!warehouseId) {
364
+ setZoneSuggestions([]);
365
+ return;
366
+ }
367
+ let cancelled = false;
368
+ void loadZoneOptions(warehouseId).then((options) => {
369
+ if (cancelled) return;
370
+ rememberZoneWarehouseOptions(options);
371
+ registerOptionLabels(options);
372
+ setZoneSuggestions(
373
+ options.map((option) => ({
374
+ value: option.value,
375
+ label: option.label
376
+ }))
377
+ );
378
+ }).catch((error) => {
379
+ if (cancelled) return;
380
+ setZoneSuggestions([]);
381
+ logger.error("loadZoneOptions failed", { component: "CycleCountWizardDialog", err: error });
382
+ });
383
+ return () => {
384
+ cancelled = true;
385
+ };
386
+ }, [form.warehouseId, open, registerOptionLabels, rememberZoneWarehouseOptions]);
387
+ const loadZoneSuggestions = React.useCallback(
388
+ async (query) => {
389
+ const warehouseId = form.warehouseId.trim();
390
+ if (!warehouseId) return [];
391
+ const options = await loadZoneOptions(warehouseId, query);
392
+ rememberZoneWarehouseOptions(options);
393
+ registerOptionLabels(options);
394
+ if (!query?.trim()) {
395
+ setZoneSuggestions(
396
+ options.map((option) => ({
397
+ value: option.value,
398
+ label: option.label
399
+ }))
400
+ );
401
+ }
402
+ return options.map((option) => ({
403
+ value: option.value,
404
+ label: option.label
405
+ }));
406
+ },
407
+ [form.warehouseId, registerOptionLabels, rememberZoneWarehouseOptions]
408
+ );
409
+ const scheduledAtValue = React.useMemo(() => {
410
+ const raw = form.scheduledAt.trim();
411
+ if (!raw) return /* @__PURE__ */ new Date();
412
+ const parsed = new Date(raw);
413
+ return Number.isNaN(parsed.getTime()) ? /* @__PURE__ */ new Date() : parsed;
414
+ }, [form.scheduledAt]);
415
+ const scheduleImmediate = React.useMemo(() => {
416
+ const deltaMs = Math.abs(scheduledAtValue.getTime() - Date.now());
417
+ return deltaMs <= 5 * 60 * 1e3;
418
+ }, [scheduledAtValue]);
419
+ React.useEffect(() => {
420
+ if (!open) return;
421
+ let cancelled = false;
422
+ const ensureLabel = async (value, resolve) => {
423
+ const id = value.trim();
424
+ if (!id || optionLabelByValueRef.current[id]) return;
425
+ const label = await resolve(id);
426
+ if (cancelled || !label) return;
427
+ registerOptionLabels([{ value: id, label }]);
428
+ };
429
+ void Promise.all([
430
+ ensureLabel(form.catalogVariantId, resolveCatalogVariantLabel),
431
+ ensureLabel(form.zoneId, resolveZoneLabel),
432
+ ensureLabel(form.warehouseId, resolveWarehouseLabel),
433
+ ensureLabel(form.fromLocationId, resolveLocationLabel),
434
+ ensureLabel(form.toLocationId, resolveLocationLabel),
435
+ ensureLabel(form.locationId, resolveLocationLabel),
436
+ ensureLabel(form.lotId, resolveLotLabel),
437
+ ensureLabel(form.assigneeId, resolveAssigneeLabel)
438
+ ]);
439
+ return () => {
440
+ cancelled = true;
441
+ };
442
+ }, [
443
+ form.assigneeId,
444
+ form.catalogVariantId,
445
+ form.fromLocationId,
446
+ form.locationId,
447
+ form.lotId,
448
+ form.toLocationId,
449
+ form.warehouseId,
450
+ form.zoneId,
451
+ open,
452
+ registerOptionLabels
453
+ ]);
454
+ React.useEffect(() => {
455
+ if (!open || step !== 1) return;
456
+ const warehouseId = form.warehouseId.trim();
457
+ if (!warehouseId) {
458
+ setScopeStats(null);
459
+ setScopeEstimateError(null);
460
+ if (!expectedSkusTouched) patchForm({ expectedSkus: 0 });
461
+ return;
462
+ }
463
+ let cancelled = false;
464
+ setLoadingScopeEstimate(true);
465
+ setScopeEstimateError(null);
466
+ void fetchCycleCountScopeEstimate({
467
+ warehouseId,
468
+ fromLocationId: form.fromLocationId.trim() || null,
469
+ toLocationId: form.toLocationId.trim() || null
470
+ }).then((estimate) => {
471
+ if (cancelled) return;
472
+ setScopeStats(estimate);
473
+ setScopeEstimateError(null);
474
+ if (!expectedSkusTouched) {
475
+ patchForm({ expectedSkus: Math.max(1, estimate.expectedSkus) });
476
+ }
477
+ }).catch((error) => {
478
+ if (cancelled) return;
479
+ setScopeStats(null);
480
+ if (error instanceof ScopeEstimateError) {
481
+ setScopeEstimateError(
482
+ t(
483
+ "wms.backend.inventory.cycleCount.errors.scopeEstimate",
484
+ "Failed to estimate count scope."
485
+ )
486
+ );
487
+ return;
488
+ }
489
+ logger.error("fetchCycleCountScopeEstimate failed", { component: "CycleCountWizardDialog", err: error });
490
+ setScopeEstimateError(
491
+ t(
492
+ "wms.backend.inventory.cycleCount.errors.scopeEstimate",
493
+ "Failed to estimate count scope."
494
+ )
495
+ );
496
+ }).finally(() => {
497
+ if (!cancelled) setLoadingScopeEstimate(false);
498
+ });
499
+ return () => {
500
+ cancelled = true;
501
+ };
502
+ }, [
503
+ expectedSkusTouched,
504
+ form.fromLocationId,
505
+ form.toLocationId,
506
+ form.warehouseId,
507
+ open,
508
+ patchForm,
509
+ step,
510
+ t
511
+ ]);
512
+ React.useEffect(() => {
513
+ if (!open || !form.zoneId.trim() || !scopeStats) return;
514
+ const baseLabel = optionLabelByValueRef.current[form.zoneId.trim()];
515
+ if (!baseLabel || baseLabel.includes("SKUs")) return;
516
+ registerOptionLabels([
517
+ {
518
+ value: form.zoneId.trim(),
519
+ label: formatCycleCountZoneLabel(baseLabel, scopeStats)
520
+ }
521
+ ]);
522
+ }, [form.zoneId, open, registerOptionLabels, scopeStats]);
523
+ React.useEffect(() => {
524
+ if (!open || step < 2) return;
525
+ const warehouseId = form.warehouseId.trim();
526
+ const locationId = form.locationId.trim();
527
+ const catalogVariantId = form.catalogVariantId.trim();
528
+ if (!warehouseId || !locationId || !catalogVariantId) {
529
+ setSystemOnHand(0);
530
+ setBalanceError(null);
531
+ return;
532
+ }
533
+ let cancelled = false;
534
+ setLoadingBalance(true);
535
+ setBalanceError(null);
536
+ void fetchBalanceOnHand({
537
+ warehouseId,
538
+ locationId,
539
+ catalogVariantId,
540
+ lotId: form.lotId.trim() || null
541
+ }).then((value) => {
542
+ if (!cancelled) {
543
+ setSystemOnHand(value);
544
+ setBalanceError(null);
545
+ }
546
+ }).catch((error) => {
547
+ if (cancelled) return;
548
+ setSystemOnHand(0);
549
+ if (error instanceof BalanceLookupError) {
550
+ if (error.code === "LOT_REQUIRED") {
551
+ setBalanceError(
552
+ t(
553
+ "wms.backend.inventory.cycleCount.errors.ambiguousLot",
554
+ "Multiple lots found at this location \u2014 select a lot to continue."
555
+ )
556
+ );
557
+ void seedLotSuggestionsFromCandidates(error.candidateLotIds);
558
+ return;
559
+ }
560
+ setBalanceError(
561
+ t("wms.backend.inventory.cycleCount.errors.balance", "Failed to load system on-hand.")
562
+ );
563
+ return;
564
+ }
565
+ logger.error("fetchBalanceOnHand failed", { component: "CycleCountWizardDialog", err: error });
566
+ setBalanceError(
567
+ t("wms.backend.inventory.cycleCount.errors.balance", "Failed to load system on-hand.")
568
+ );
569
+ }).finally(() => {
570
+ if (!cancelled) setLoadingBalance(false);
571
+ });
572
+ return () => {
573
+ cancelled = true;
574
+ };
575
+ }, [
576
+ form.catalogVariantId,
577
+ form.locationId,
578
+ form.lotId,
579
+ form.warehouseId,
580
+ open,
581
+ seedLotSuggestionsFromCandidates,
582
+ step,
583
+ t
584
+ ]);
585
+ const applyValidationErrors = React.useCallback((issues) => {
586
+ const nextErrors = {};
587
+ for (const issue of issues) {
588
+ const key = String(issue.path[0] ?? "form");
589
+ if (!nextErrors[key]) nextErrors[key] = issue.message;
590
+ }
591
+ setFieldErrors(nextErrors);
592
+ }, []);
593
+ const handleSetupContinue = React.useCallback(() => {
594
+ const parsed = setupSchema.safeParse({
595
+ warehouseId: form.warehouseId,
596
+ zoneId: form.zoneId,
597
+ fromLocationId: form.fromLocationId.trim() || void 0,
598
+ toLocationId: form.toLocationId.trim() || void 0,
599
+ assigneeId: form.assigneeId,
600
+ expectedSkus: form.expectedSkus,
601
+ setupNotes: form.setupNotes.trim() || void 0
602
+ });
603
+ if (!parsed.success) {
604
+ applyValidationErrors(parsed.error.issues);
605
+ return;
606
+ }
607
+ const zoneWarehouseId = zoneWarehouseByZoneIdRef.current[parsed.data.zoneId.trim()];
608
+ if (zoneWarehouseId && zoneWarehouseId !== parsed.data.warehouseId) {
609
+ setFieldErrors({
610
+ zoneId: t(
611
+ "wms.backend.inventory.cycleCount.errors.zoneWarehouse",
612
+ "Selected zone does not belong to this warehouse."
613
+ )
614
+ });
615
+ return;
616
+ }
617
+ const fromLocId = form.fromLocationId.trim() || null;
618
+ const toLocId = form.toLocationId.trim() || null;
619
+ const hasRange = !!(fromLocId || toLocId);
620
+ if (!hasRange) {
621
+ const nextLocationId = form.fromLocationId.trim() || form.locationId.trim();
622
+ if (nextLocationId && nextLocationId !== form.locationId.trim()) {
623
+ patchForm({ locationId: nextLocationId });
624
+ }
625
+ }
626
+ setStep(2);
627
+ if (hasRange) {
628
+ setScopeQueue([]);
629
+ setScopeQueueIndex(0);
630
+ setScopeQueueLoading(true);
631
+ setScopeQueueReady(false);
632
+ setScopeQueueError(null);
633
+ void buildCycleCountScopeQueue({
634
+ warehouseId: form.warehouseId,
635
+ fromLocationId: fromLocId,
636
+ toLocationId: toLocId
637
+ }).then((queue) => {
638
+ setScopeQueue(queue);
639
+ setScopeQueueIndex(0);
640
+ setScopeQueueReady(true);
641
+ for (const item of queue) {
642
+ registerOptionLabels([{ value: item.locationId, label: item.locationCode }]);
643
+ }
644
+ if (queue.length > 0) {
645
+ const first = queue[0];
646
+ setForm((current) => ({
647
+ ...current,
648
+ locationId: first.locationId,
649
+ catalogVariantId: first.catalogVariantId,
650
+ lotId: first.lotId ?? ""
651
+ }));
652
+ }
653
+ }).catch((error) => {
654
+ setScopeQueueReady(true);
655
+ if (error instanceof ScopeQueueError) {
656
+ setScopeQueueError(
657
+ t(
658
+ "wms.backend.inventory.cycleCount.steps.counting.queueError",
659
+ "Failed to load scope \u2014 enter location manually."
660
+ )
661
+ );
662
+ } else {
663
+ logger.error("buildCycleCountScopeQueue failed", { component: "CycleCountWizardDialog", err: error });
664
+ setScopeQueueError(
665
+ t(
666
+ "wms.backend.inventory.cycleCount.steps.counting.queueError",
667
+ "Failed to load scope \u2014 enter location manually."
668
+ )
669
+ );
670
+ }
671
+ }).finally(() => {
672
+ setScopeQueueLoading(false);
673
+ });
674
+ } else {
675
+ setScopeQueueReady(true);
676
+ setScopeQueueLoading(false);
677
+ setScopeQueueError(null);
678
+ }
679
+ }, [
680
+ applyValidationErrors,
681
+ form.assigneeId,
682
+ form.expectedSkus,
683
+ form.fromLocationId,
684
+ form.locationId,
685
+ form.setupNotes,
686
+ form.toLocationId,
687
+ form.warehouseId,
688
+ form.zoneId,
689
+ patchForm,
690
+ registerOptionLabels,
691
+ setupSchema,
692
+ t
693
+ ]);
694
+ const handleCountContinue = React.useCallback(async () => {
695
+ const parsed = countSchema.safeParse({
696
+ ...form,
697
+ lotId: form.lotId.trim() || void 0,
698
+ countNotes: form.countNotes.trim() || void 0
699
+ });
700
+ if (!parsed.success) {
701
+ applyValidationErrors(parsed.error.issues);
702
+ return;
703
+ }
704
+ setLoadingBalance(true);
705
+ try {
706
+ const onHand = await fetchBalanceOnHand({
707
+ warehouseId: parsed.data.warehouseId,
708
+ locationId: parsed.data.locationId,
709
+ catalogVariantId: parsed.data.catalogVariantId,
710
+ lotId: parsed.data.lotId ?? null
711
+ });
712
+ setSystemOnHand(onHand);
713
+ setStep(3);
714
+ } catch (error) {
715
+ if (error instanceof BalanceLookupError && error.code === "LOT_REQUIRED") {
716
+ const message = t(
717
+ "wms.backend.inventory.cycleCount.errors.ambiguousLot",
718
+ "Multiple lots found at this location \u2014 select a lot to continue."
719
+ );
720
+ setFieldErrors({ lotId: message });
721
+ flash(message, "error");
722
+ void seedLotSuggestionsFromCandidates(error.candidateLotIds);
723
+ return;
724
+ }
725
+ logger.error("fetchBalanceOnHand failed", { component: "CycleCountWizardDialog", err: error });
726
+ flash(
727
+ t("wms.backend.inventory.cycleCount.errors.balance", "Failed to load system on-hand."),
728
+ "error"
729
+ );
730
+ } finally {
731
+ setLoadingBalance(false);
732
+ }
733
+ }, [applyValidationErrors, countSchema, form, seedLotSuggestionsFromCandidates, t]);
734
+ const handlePost = React.useCallback(async () => {
735
+ const parsed = commitSchema.safeParse({ reason: form.reason });
736
+ if (!parsed.success) {
737
+ applyValidationErrors(parsed.error.issues);
738
+ return;
739
+ }
740
+ if (!form.warehouseId || !form.locationId || !form.catalogVariantId) {
741
+ flash(
742
+ t("wms.backend.inventory.cycleCount.errors.scope", "Complete setup and select a count location."),
743
+ "error"
744
+ );
745
+ return;
746
+ }
747
+ if (!access.scopeReady || !access.organizationId || !access.tenantId || !access.userId) {
748
+ flash(
749
+ t(
750
+ "wms.backend.inventory.mutations.errors.scope",
751
+ "Select an organization and sign in before posting inventory changes."
752
+ ),
753
+ "error"
754
+ );
755
+ return;
756
+ }
757
+ if (!autoAdjust && variance !== 0) {
758
+ flash(
759
+ t(
760
+ "wms.backend.inventory.cycleCount.errors.autoAdjustRequired",
761
+ "Enable auto-adjust to commit a variance."
762
+ ),
763
+ "error"
764
+ );
765
+ return;
766
+ }
767
+ setSubmitting(true);
768
+ try {
769
+ const notes = [form.setupNotes.trim(), form.countNotes.trim()].filter(Boolean).join(" \xB7 ");
770
+ const setupMetadata = {
771
+ zoneId: form.zoneId.trim() || null,
772
+ fromLocationId: form.fromLocationId.trim() || null,
773
+ toLocationId: form.toLocationId.trim() || null,
774
+ scheduledAt: form.scheduledAt.trim() || null,
775
+ assigneeId: form.assigneeId.trim() || null,
776
+ expectedSkus: form.expectedSkus
777
+ };
778
+ const payload = {
779
+ organizationId: access.organizationId,
780
+ tenantId: access.tenantId,
781
+ warehouseId: form.warehouseId,
782
+ locationId: form.locationId,
783
+ catalogVariantId: form.catalogVariantId,
784
+ countedQuantity: form.countedQuantity,
785
+ autoAdjust,
786
+ reason: parsed.data.reason,
787
+ referenceId,
788
+ performedBy: form.assigneeId.trim() || access.userId
789
+ };
790
+ if (form.lotId.trim()) payload.lotId = form.lotId.trim();
791
+ if (notes || Object.values(setupMetadata).some((value) => value !== null && value !== 0)) {
792
+ payload.metadata = { notes: notes || void 0, setup: setupMetadata };
793
+ }
794
+ await runMutation({
795
+ operation: async () => {
796
+ const call = await apiCall("/api/wms/inventory/cycle-count", {
797
+ method: "POST",
798
+ headers: { "content-type": "application/json" },
799
+ body: JSON.stringify(payload)
800
+ });
801
+ if (!call.ok) {
802
+ await raiseCrudError(
803
+ call.response,
804
+ t("wms.backend.inventory.cycleCount.errors.submit", "Failed to post cycle count.")
805
+ );
806
+ }
807
+ return call.result ?? {};
808
+ },
809
+ context: mutationContext,
810
+ mutationPayload: payload
811
+ });
812
+ const deltaLabel = formatSignedQuantity(variance);
813
+ flash(
814
+ t("wms.backend.inventory.cycleCount.flash.success", "Cycle count posted ({delta})", {
815
+ delta: deltaLabel
816
+ }),
817
+ "success"
818
+ );
819
+ await queryClient.invalidateQueries({ queryKey: ["wms-inventory-console"] });
820
+ await queryClient.invalidateQueries({ queryKey: ["wms-sku-detail"] });
821
+ setLinesPosted((n) => n + 1);
822
+ resetToStep2();
823
+ } catch (error) {
824
+ flashMutationError(error, t("wms.backend.inventory.cycleCount.errors.submit", "Failed to post cycle count."), t);
825
+ } finally {
826
+ setSubmitting(false);
827
+ }
828
+ }, [
829
+ access,
830
+ applyValidationErrors,
831
+ autoAdjust,
832
+ commitSchema,
833
+ form,
834
+ mutationContext,
835
+ queryClient,
836
+ referenceId,
837
+ resetToStep2,
838
+ runMutation,
839
+ t,
840
+ variance
841
+ ]);
842
+ const handlePrimaryAction = React.useCallback(() => {
843
+ if (step === 1) {
844
+ handleSetupContinue();
845
+ return;
846
+ }
847
+ if (step === 2) {
848
+ void handleCountContinue();
849
+ return;
850
+ }
851
+ void handlePost();
852
+ }, [handleCountContinue, handlePost, handleSetupContinue, step]);
853
+ const handleDialogKeyDown = React.useCallback(
854
+ (event) => {
855
+ if (event.key === "Escape") {
856
+ event.preventDefault();
857
+ closeDialog();
858
+ return;
859
+ }
860
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
861
+ event.preventDefault();
862
+ if (submitting || loadingBalance) return;
863
+ handlePrimaryAction();
864
+ }
865
+ },
866
+ [closeDialog, handlePrimaryAction, loadingBalance, submitting]
867
+ );
868
+ const adjustCounted = React.useCallback((delta) => {
869
+ setForm((current) => ({
870
+ ...current,
871
+ countedQuantity: Math.max(0, current.countedQuantity + delta)
872
+ }));
873
+ setFieldErrors({});
874
+ }, []);
875
+ const adjustExpectedSkus = React.useCallback((delta) => {
876
+ setExpectedSkusTouched(true);
877
+ setForm((current) => ({
878
+ ...current,
879
+ expectedSkus: Math.max(1, current.expectedSkus + delta)
880
+ }));
881
+ setFieldErrors({});
882
+ }, []);
883
+ const isQueueActive = scopeQueueReady && scopeQueue.length > 0;
884
+ const isQueueComplete = isQueueActive && scopeQueueIndex >= scopeQueue.length;
885
+ const queueCurrent = scopeQueueIndex + 1;
886
+ const queueTotal = scopeQueue.length;
887
+ const stepSubtitle = React.useMemo(() => {
888
+ if (step === 1) {
889
+ return t(
890
+ "wms.backend.inventory.cycleCount.steps.setup.subtitle",
891
+ "Step 1 of 3 \xB7 Set scope and expectations"
892
+ );
893
+ }
894
+ if (step === 2) {
895
+ if (linesPosted > 0) {
896
+ return t(
897
+ "wms.backend.inventory.cycleCount.steps.counting.subtitleWithPosted",
898
+ "Step 2 of 3 \xB7 {count} line(s) posted \u2014 count next SKU",
899
+ { count: linesPosted }
900
+ );
901
+ }
902
+ return t(
903
+ "wms.backend.inventory.cycleCount.steps.counting.subtitle",
904
+ "Step 2 of 3 \xB7 Scan and tally items"
905
+ );
906
+ }
907
+ return t(
908
+ "wms.backend.inventory.cycleCount.steps.review.subtitle",
909
+ "Step 3 of 3 \xB7 Review variances and commit"
910
+ );
911
+ }, [linesPosted, step, t]);
912
+ const shortcutHint = React.useMemo(() => {
913
+ if (step === 1) {
914
+ return t("wms.backend.inventory.cycleCount.steps.setup.shortcut", "to start");
915
+ }
916
+ if (step === 2) {
917
+ return t("wms.backend.inventory.cycleCount.steps.counting.shortcut", "to continue");
918
+ }
919
+ return t("wms.backend.inventory.cycleCount.steps.review.shortcut", "to commit");
920
+ }, [step, t]);
921
+ const variantScanLabel = form.catalogVariantId ? resolveOptionLabel(form.catalogVariantId) : t(
922
+ "wms.backend.inventory.cycleCount.form.variantScanPlaceholder",
923
+ "Select a variant to begin scanning"
924
+ );
925
+ const locationLabel = form.locationId ? resolveOptionLabel(form.locationId) : "\u2014";
926
+ const variantLabel = form.catalogVariantId ? resolveOptionLabel(form.catalogVariantId) : "\u2014";
927
+ const lotLabel = form.lotId ? resolveOptionLabel(form.lotId) : "\u2014";
928
+ const primaryDisabled = submitting || step === 2 && (loadingBalance || scopeQueueLoading || isQueueComplete);
929
+ const loadRangeFilteredLocationOptions = React.useCallback(
930
+ async (query) => {
931
+ const warehouseId = form.warehouseId.trim();
932
+ if (!warehouseId) return [];
933
+ const fromLocId = form.fromLocationId.trim();
934
+ const toLocId = form.toLocationId.trim();
935
+ if (!fromLocId && !toLocId) {
936
+ const options = await loadLocationOptions(warehouseId, query);
937
+ registerOptionLabels(options);
938
+ return options.map((option) => ({ value: option.value, label: option.label }));
939
+ }
940
+ try {
941
+ const allLocations = await loadAllLocations(warehouseId, {
942
+ search: query?.trim() || void 0
943
+ });
944
+ const fromCode = fromLocId ? optionLabelByValueRef.current[fromLocId] !== fromLocId ? optionLabelByValueRef.current[fromLocId] : void 0 : void 0;
945
+ const toCode = toLocId ? optionLabelByValueRef.current[toLocId] !== toLocId ? optionLabelByValueRef.current[toLocId] : void 0 : void 0;
946
+ const filtered = filterLocationsByCodeRange(allLocations, fromCode, toCode);
947
+ const options = mapLocationOptions(filtered);
948
+ registerOptionLabels(options);
949
+ return options.map((option) => ({ value: option.value, label: option.label }));
950
+ } catch {
951
+ const options = await loadLocationOptions(warehouseId, query);
952
+ registerOptionLabels(options);
953
+ return options.map((option) => ({ value: option.value, label: option.label }));
954
+ }
955
+ },
956
+ [
957
+ form.fromLocationId,
958
+ form.toLocationId,
959
+ form.warehouseId,
960
+ registerOptionLabels
961
+ ]
962
+ );
963
+ const loadLotSuggestionsForBalanceLocation = React.useCallback(
964
+ async (query) => {
965
+ const options = await loadLotOptionsForBalanceLocation({
966
+ warehouseId: form.warehouseId,
967
+ locationId: form.locationId,
968
+ catalogVariantId: form.catalogVariantId,
969
+ query
970
+ });
971
+ registerOptionLabels(options);
972
+ return options.map((option) => ({ value: option.value, label: option.label }));
973
+ },
974
+ [form.catalogVariantId, form.locationId, form.warehouseId, registerOptionLabels]
975
+ );
976
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => next ? onOpenChange(true) : closeDialog(), children: /* @__PURE__ */ jsxs(
977
+ DialogContent,
978
+ {
979
+ className: "max-w-lg gap-0 overflow-hidden p-0",
980
+ onKeyDown: handleDialogKeyDown,
981
+ children: [
982
+ /* @__PURE__ */ jsx("div", { className: "border-b px-6 py-4 pr-12", children: /* @__PURE__ */ jsxs(DialogHeader, { className: "space-y-1 text-left", children: [
983
+ /* @__PURE__ */ jsx(DialogTitle, { children: t("wms.backend.inventory.cycleCount.dialog.title", "Cycle count") }),
984
+ /* @__PURE__ */ jsx(DialogDescription, { children: stepSubtitle }),
985
+ /* @__PURE__ */ jsx(CycleCountStepIndicator, { step })
986
+ ] }) }),
987
+ /* @__PURE__ */ jsxs(
988
+ "form",
989
+ {
990
+ ref: formRef,
991
+ onSubmit: (event) => {
992
+ event.preventDefault();
993
+ handlePrimaryAction();
994
+ },
995
+ className: "flex min-h-0 flex-1 flex-col",
996
+ children: [
997
+ /* @__PURE__ */ jsxs("div", { className: "flex max-h-[min(70vh,640px)] flex-col gap-5 overflow-y-auto px-6 py-6", children: [
998
+ step === 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
999
+ /* @__PURE__ */ jsx(
1000
+ FormField,
1001
+ {
1002
+ label: t("wms.backend.inventory.cycleCount.form.warehouse", "Warehouse"),
1003
+ required: true,
1004
+ error: fieldErrors.warehouseId,
1005
+ children: /* @__PURE__ */ jsx(
1006
+ ComboboxInput,
1007
+ {
1008
+ value: form.warehouseId,
1009
+ onChange: (next) => {
1010
+ patchForm({
1011
+ warehouseId: next.trim(),
1012
+ zoneId: "",
1013
+ fromLocationId: "",
1014
+ toLocationId: "",
1015
+ locationId: ""
1016
+ });
1017
+ setExpectedSkusTouched(false);
1018
+ setScopeStats(null);
1019
+ setZoneSuggestions([]);
1020
+ },
1021
+ loadSuggestions: async (query) => {
1022
+ const options = await loadWarehouseOptions(query);
1023
+ registerOptionLabels(options);
1024
+ return options.map((option) => ({
1025
+ value: option.value,
1026
+ label: option.label
1027
+ }));
1028
+ },
1029
+ resolveLabel: resolveOptionLabel,
1030
+ placeholder: t(
1031
+ "wms.backend.inventory.cycleCount.form.warehousePlaceholder",
1032
+ "Select warehouse"
1033
+ ),
1034
+ allowCustomValues: false,
1035
+ disabled: loadingBalance || submitting
1036
+ }
1037
+ )
1038
+ }
1039
+ ),
1040
+ /* @__PURE__ */ jsxs(
1041
+ FormField,
1042
+ {
1043
+ label: t("wms.backend.inventory.cycleCount.form.zone", "Zone"),
1044
+ required: true,
1045
+ error: fieldErrors.zoneId,
1046
+ children: [
1047
+ /* @__PURE__ */ jsxs("div", { className: "relative [&_input]:pl-9", children: [
1048
+ /* @__PURE__ */ jsx(
1049
+ Warehouse,
1050
+ {
1051
+ className: "pointer-events-none absolute left-3 top-1/2 z-10 size-4 -translate-y-1/2 text-muted-foreground",
1052
+ "aria-hidden": "true"
1053
+ }
1054
+ ),
1055
+ /* @__PURE__ */ jsx(
1056
+ ComboboxInput,
1057
+ {
1058
+ value: form.zoneId,
1059
+ onChange: (next) => {
1060
+ const zoneId = next.trim();
1061
+ patchForm({
1062
+ zoneId,
1063
+ fromLocationId: "",
1064
+ toLocationId: "",
1065
+ locationId: ""
1066
+ });
1067
+ setExpectedSkusTouched(false);
1068
+ setScopeStats(null);
1069
+ },
1070
+ suggestions: zoneSuggestions,
1071
+ loadSuggestions: loadZoneSuggestions,
1072
+ resolveLabel: (value) => {
1073
+ const label = resolveOptionLabel(value);
1074
+ if (value === form.zoneId.trim() && scopeStats) {
1075
+ const base = label.includes(" \xB7 ") ? label.split(" \xB7 ")[0] ?? label : label;
1076
+ return formatCycleCountZoneLabel(base, scopeStats);
1077
+ }
1078
+ return label;
1079
+ },
1080
+ placeholder: form.warehouseId.trim() ? t(
1081
+ "wms.backend.inventory.cycleCount.form.zonePlaceholder",
1082
+ "Select zone"
1083
+ ) : t(
1084
+ "wms.backend.inventory.cycleCount.form.zonePlaceholderDisabled",
1085
+ "Select warehouse first"
1086
+ ),
1087
+ allowCustomValues: false,
1088
+ disabled: loadingBalance || submitting || !form.warehouseId.trim()
1089
+ },
1090
+ form.warehouseId || "cycle-count-zone"
1091
+ )
1092
+ ] }),
1093
+ form.warehouseId.trim() ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: form.zoneId.trim() && scopeStats ? t(
1094
+ "wms.backend.inventory.cycleCount.form.zoneScopeHint",
1095
+ "{warehouse} \xB7 {expectedSkus} SKUs \xB7 {binCount} bins in scope",
1096
+ {
1097
+ warehouse: resolveOptionLabel(form.warehouseId),
1098
+ expectedSkus: scopeStats.expectedSkus,
1099
+ binCount: scopeStats.binCount
1100
+ }
1101
+ ) : t(
1102
+ "wms.backend.inventory.cycleCount.form.zoneWarehouseHint",
1103
+ "Functional zone within {warehouse}",
1104
+ { warehouse: resolveOptionLabel(form.warehouseId) }
1105
+ ) }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t(
1106
+ "wms.backend.inventory.cycleCount.form.zoneSelectWarehouse",
1107
+ "Select a warehouse to choose a zone."
1108
+ ) })
1109
+ ]
1110
+ }
1111
+ ),
1112
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-5 sm:grid-cols-2", children: [
1113
+ /* @__PURE__ */ jsx(
1114
+ FormField,
1115
+ {
1116
+ label: t("wms.backend.inventory.cycleCount.form.fromBin", "From bin"),
1117
+ error: fieldErrors.fromLocationId,
1118
+ children: /* @__PURE__ */ jsx(
1119
+ ComboboxInput,
1120
+ {
1121
+ value: form.fromLocationId,
1122
+ onChange: (next) => {
1123
+ patchForm({ fromLocationId: next.trim() });
1124
+ setExpectedSkusTouched(false);
1125
+ },
1126
+ loadSuggestions: async (query) => {
1127
+ const options = await loadBinLocationOptions(form.warehouseId, query);
1128
+ registerOptionLabels(options);
1129
+ return options.map((option) => ({
1130
+ value: option.value,
1131
+ label: option.label
1132
+ }));
1133
+ },
1134
+ resolveLabel: resolveOptionLabel,
1135
+ placeholder: t(
1136
+ "wms.backend.inventory.cycleCount.form.fromBinPlaceholder",
1137
+ "Start bin"
1138
+ ),
1139
+ allowCustomValues: false,
1140
+ disabled: loadingBalance || submitting || !form.warehouseId.trim()
1141
+ }
1142
+ )
1143
+ }
1144
+ ),
1145
+ /* @__PURE__ */ jsx(
1146
+ FormField,
1147
+ {
1148
+ label: t("wms.backend.inventory.cycleCount.form.toBin", "To bin"),
1149
+ error: fieldErrors.toLocationId,
1150
+ children: /* @__PURE__ */ jsx(
1151
+ ComboboxInput,
1152
+ {
1153
+ value: form.toLocationId,
1154
+ onChange: (next) => {
1155
+ patchForm({ toLocationId: next.trim() });
1156
+ setExpectedSkusTouched(false);
1157
+ },
1158
+ loadSuggestions: async (query) => {
1159
+ const options = await loadBinLocationOptions(form.warehouseId, query);
1160
+ registerOptionLabels(options);
1161
+ return options.map((option) => ({
1162
+ value: option.value,
1163
+ label: option.label
1164
+ }));
1165
+ },
1166
+ resolveLabel: resolveOptionLabel,
1167
+ placeholder: t(
1168
+ "wms.backend.inventory.cycleCount.form.toBinPlaceholder",
1169
+ "End bin"
1170
+ ),
1171
+ allowCustomValues: false,
1172
+ disabled: loadingBalance || submitting || !form.warehouseId.trim()
1173
+ }
1174
+ )
1175
+ }
1176
+ )
1177
+ ] }),
1178
+ /* @__PURE__ */ jsxs(
1179
+ FormField,
1180
+ {
1181
+ label: t("wms.backend.inventory.cycleCount.form.schedule", "Schedule"),
1182
+ error: fieldErrors.scheduledAt,
1183
+ children: [
1184
+ /* @__PURE__ */ jsx(
1185
+ DateTimePicker,
1186
+ {
1187
+ value: scheduledAtValue,
1188
+ onChange: (next) => {
1189
+ patchForm({ scheduledAt: next ? next.toISOString() : "" });
1190
+ },
1191
+ placeholder: t(
1192
+ "wms.backend.inventory.cycleCount.form.schedulePlaceholder",
1193
+ "Pick date and time"
1194
+ ),
1195
+ disabled: loadingBalance || submitting
1196
+ }
1197
+ ),
1198
+ scheduleImmediate ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t(
1199
+ "wms.backend.inventory.cycleCount.form.scheduleImmediate",
1200
+ "Immediate start"
1201
+ ) }) : null
1202
+ ]
1203
+ }
1204
+ ),
1205
+ /* @__PURE__ */ jsxs(
1206
+ FormField,
1207
+ {
1208
+ label: t("wms.backend.inventory.cycleCount.form.expectedSkus", "Expected SKUs"),
1209
+ required: true,
1210
+ error: fieldErrors.expectedSkus,
1211
+ children: [
1212
+ /* @__PURE__ */ jsxs("div", { className: "flex w-32 items-center gap-2 rounded-md border bg-background p-2 shadow-xs", children: [
1213
+ /* @__PURE__ */ jsx(
1214
+ IconButton,
1215
+ {
1216
+ type: "button",
1217
+ variant: "ghost",
1218
+ size: "sm",
1219
+ "aria-label": t(
1220
+ "wms.backend.inventory.cycleCount.form.decreaseExpectedSkus",
1221
+ "Decrease expected SKUs"
1222
+ ),
1223
+ onClick: () => adjustExpectedSkus(-1),
1224
+ disabled: loadingBalance || submitting || loadingScopeEstimate,
1225
+ children: /* @__PURE__ */ jsx(Minus, { className: "size-4" })
1226
+ }
1227
+ ),
1228
+ /* @__PURE__ */ jsx(
1229
+ Input,
1230
+ {
1231
+ type: "number",
1232
+ inputMode: "numeric",
1233
+ min: 1,
1234
+ value: String(form.expectedSkus),
1235
+ onChange: (event) => {
1236
+ const parsed = Number(event.target.value);
1237
+ if (!Number.isFinite(parsed) || parsed < 1) return;
1238
+ setExpectedSkusTouched(true);
1239
+ patchForm({ expectedSkus: Math.floor(parsed) });
1240
+ },
1241
+ className: "h-8 w-auto min-w-0 flex-1 border-0 bg-transparent p-0 shadow-none",
1242
+ inputClassName: "text-center [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
1243
+ disabled: loadingBalance || submitting || loadingScopeEstimate
1244
+ }
1245
+ ),
1246
+ /* @__PURE__ */ jsx(
1247
+ IconButton,
1248
+ {
1249
+ type: "button",
1250
+ variant: "ghost",
1251
+ size: "sm",
1252
+ "aria-label": t(
1253
+ "wms.backend.inventory.cycleCount.form.increaseExpectedSkus",
1254
+ "Increase expected SKUs"
1255
+ ),
1256
+ onClick: () => adjustExpectedSkus(1),
1257
+ disabled: loadingBalance || submitting || loadingScopeEstimate,
1258
+ children: /* @__PURE__ */ jsx(Plus, { className: "size-4" })
1259
+ }
1260
+ )
1261
+ ] }),
1262
+ loadingScopeEstimate ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t(
1263
+ "wms.backend.inventory.cycleCount.form.expectedSkusLoading",
1264
+ "Estimating scope\u2026"
1265
+ ) }) : null,
1266
+ scopeEstimateError ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-status-warning-fg", children: scopeEstimateError }) : null
1267
+ ]
1268
+ }
1269
+ ),
1270
+ /* @__PURE__ */ jsxs(
1271
+ FormField,
1272
+ {
1273
+ label: t("wms.backend.inventory.cycleCount.form.assignee", "Assignee"),
1274
+ required: true,
1275
+ error: fieldErrors.assigneeId,
1276
+ children: [
1277
+ assigneeCanListUsers ? /* @__PURE__ */ jsx(
1278
+ ComboboxInput,
1279
+ {
1280
+ value: form.assigneeId,
1281
+ onChange: (next) => patchForm({ assigneeId: next.trim() }),
1282
+ loadSuggestions: async (query) => {
1283
+ const result = await loadAssigneeOptions(query, assigneeFallback ?? void 0);
1284
+ setAssigneeCanListUsers(result.canListUsers);
1285
+ registerOptionLabels(result.options);
1286
+ return result.options.map((option) => ({
1287
+ value: option.value,
1288
+ label: option.label
1289
+ }));
1290
+ },
1291
+ resolveLabel: resolveOptionLabel,
1292
+ placeholder: t(
1293
+ "wms.backend.inventory.cycleCount.form.assigneePlaceholder",
1294
+ "Select assignee"
1295
+ ),
1296
+ allowCustomValues: false,
1297
+ disabled: loadingBalance || submitting
1298
+ }
1299
+ ) : /* @__PURE__ */ jsx("div", { className: "rounded-md border bg-muted/40 px-3 py-2.5 text-sm text-foreground", children: resolveOptionLabel(form.assigneeId) || form.assigneeId }),
1300
+ !assigneeCanListUsers ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t(
1301
+ "wms.backend.inventory.cycleCount.form.assigneeLocked",
1302
+ "Only the current user can be assigned without user-directory access."
1303
+ ) }) : null
1304
+ ]
1305
+ }
1306
+ ),
1307
+ /* @__PURE__ */ jsx(FormField, { label: t("wms.backend.inventory.cycleCount.form.setupNotes", "Notes"), children: /* @__PURE__ */ jsx(
1308
+ Textarea,
1309
+ {
1310
+ value: form.setupNotes,
1311
+ onChange: (event) => patchForm({ setupNotes: event.target.value }),
1312
+ placeholder: t(
1313
+ "wms.backend.inventory.cycleCount.form.setupNotesPlaceholder",
1314
+ "Optional \u2014 pre-count instructions"
1315
+ ),
1316
+ rows: 3,
1317
+ disabled: loadingBalance || submitting
1318
+ }
1319
+ ) })
1320
+ ] }) : null,
1321
+ step === 2 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1322
+ scopeQueueLoading ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
1323
+ "wms.backend.inventory.cycleCount.steps.counting.queueLoading",
1324
+ "Loading count scope\u2026"
1325
+ ) }) : null,
1326
+ !scopeQueueLoading && scopeQueueError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-status-warning-fg", children: scopeQueueError }) : null,
1327
+ !scopeQueueLoading && scopeQueueReady && isQueueActive && !isQueueComplete ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-muted/40 px-4 py-3", children: [
1328
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1329
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-foreground", children: t(
1330
+ "wms.backend.inventory.cycleCount.steps.counting.queueItem",
1331
+ "Item {current} of {total}",
1332
+ { current: queueCurrent, total: queueTotal }
1333
+ ) }),
1334
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: resolveOptionLabel(form.locationId || "") })
1335
+ ] }),
1336
+ form.expectedSkus > 0 ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t(
1337
+ "wms.backend.inventory.cycleCount.steps.counting.progressSkus",
1338
+ "{posted} / {expected} SKUs counted",
1339
+ { posted: linesPosted, expected: form.expectedSkus }
1340
+ ) }) : null
1341
+ ] }) : null,
1342
+ !scopeQueueLoading && scopeQueueReady && isQueueActive && !isQueueComplete && scopeQueueError === null && scopeQueue.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
1343
+ "wms.backend.inventory.cycleCount.steps.counting.queueEmpty",
1344
+ "No items found in range \u2014 enter location manually."
1345
+ ) }) : null,
1346
+ isQueueComplete ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-status-success-border bg-status-success-bg px-4 py-3", children: /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-status-success-fg", children: t(
1347
+ "wms.backend.inventory.cycleCount.steps.counting.queueComplete",
1348
+ "All {total} items counted \u2014 session complete",
1349
+ { total: queueTotal }
1350
+ ) }) }) : null,
1351
+ !isQueueComplete ? /* @__PURE__ */ jsxs(Fragment, { children: [
1352
+ !isQueueActive && form.expectedSkus > 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
1353
+ "wms.backend.inventory.cycleCount.steps.counting.progressSkus",
1354
+ "{posted} / {expected} SKUs counted",
1355
+ { posted: linesPosted, expected: form.expectedSkus }
1356
+ ) }) : null,
1357
+ /* @__PURE__ */ jsx(
1358
+ FormField,
1359
+ {
1360
+ label: t(
1361
+ "wms.backend.inventory.cycleCount.form.currentlyScanning",
1362
+ "Currently scanning"
1363
+ ),
1364
+ children: /* @__PURE__ */ jsxs("div", { className: "relative rounded-md bg-muted/50 px-3 py-2.5 pl-9 text-sm text-muted-foreground", children: [
1365
+ /* @__PURE__ */ jsx(
1366
+ PackageSearch,
1367
+ {
1368
+ className: "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground",
1369
+ "aria-hidden": "true"
1370
+ }
1371
+ ),
1372
+ variantScanLabel
1373
+ ] })
1374
+ }
1375
+ ),
1376
+ /* @__PURE__ */ jsx(
1377
+ FormField,
1378
+ {
1379
+ label: t("wms.backend.inventory.cycleCount.form.variant", "Variant / SKU"),
1380
+ required: true,
1381
+ error: fieldErrors.catalogVariantId,
1382
+ children: /* @__PURE__ */ jsx(
1383
+ ComboboxInput,
1384
+ {
1385
+ value: form.catalogVariantId,
1386
+ onChange: (next) => {
1387
+ patchForm({
1388
+ catalogVariantId: next.trim(),
1389
+ lotId: ""
1390
+ });
1391
+ setLotSuggestions([]);
1392
+ },
1393
+ loadSuggestions: async (query) => {
1394
+ const options = await loadCatalogVariantOptions(query);
1395
+ registerOptionLabels(options);
1396
+ return options.map((option) => ({
1397
+ value: option.value,
1398
+ label: option.label,
1399
+ description: option.description
1400
+ }));
1401
+ },
1402
+ resolveLabel: resolveOptionLabel,
1403
+ placeholder: t(
1404
+ "wms.backend.inventory.cycleCount.form.variantPlaceholder",
1405
+ "Search variant or SKU"
1406
+ ),
1407
+ allowCustomValues: false,
1408
+ disabled: loadingBalance || submitting || scopeQueueLoading
1409
+ }
1410
+ )
1411
+ }
1412
+ ),
1413
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-5 sm:grid-cols-2", children: [
1414
+ /* @__PURE__ */ jsx(
1415
+ FormField,
1416
+ {
1417
+ label: t("wms.backend.inventory.cycleCount.form.location", "Location"),
1418
+ required: true,
1419
+ error: fieldErrors.locationId,
1420
+ children: /* @__PURE__ */ jsx(
1421
+ ComboboxInput,
1422
+ {
1423
+ value: form.locationId,
1424
+ onChange: (next) => {
1425
+ patchForm({ locationId: next.trim(), lotId: "" });
1426
+ setLotSuggestions([]);
1427
+ },
1428
+ loadSuggestions: loadRangeFilteredLocationOptions,
1429
+ resolveLabel: resolveOptionLabel,
1430
+ placeholder: t(
1431
+ "wms.backend.inventory.cycleCount.form.locationPlaceholder",
1432
+ "Select location"
1433
+ ),
1434
+ allowCustomValues: false,
1435
+ disabled: loadingBalance || submitting || !form.warehouseId || scopeQueueLoading
1436
+ }
1437
+ )
1438
+ }
1439
+ ),
1440
+ /* @__PURE__ */ jsx(
1441
+ FormField,
1442
+ {
1443
+ label: t("wms.backend.inventory.cycleCount.form.lot", "Lot"),
1444
+ error: fieldErrors.lotId,
1445
+ children: /* @__PURE__ */ jsx(
1446
+ ComboboxInput,
1447
+ {
1448
+ value: form.lotId,
1449
+ onChange: (next) => patchForm({ lotId: next.trim() }),
1450
+ suggestions: lotSuggestions,
1451
+ loadSuggestions: loadLotSuggestionsForBalanceLocation,
1452
+ resolveLabel: resolveOptionLabel,
1453
+ placeholder: t(
1454
+ "wms.backend.inventory.cycleCount.form.lotPlaceholder",
1455
+ "Select lot (optional)"
1456
+ ),
1457
+ allowCustomValues: false,
1458
+ disabled: loadingBalance || submitting || !form.catalogVariantId || !form.locationId
1459
+ }
1460
+ )
1461
+ }
1462
+ )
1463
+ ] }),
1464
+ /* @__PURE__ */ jsx(
1465
+ FormField,
1466
+ {
1467
+ label: t("wms.backend.inventory.cycleCount.form.counted", "Counted"),
1468
+ required: true,
1469
+ error: fieldErrors.countedQuantity,
1470
+ children: /* @__PURE__ */ jsxs("div", { className: "flex w-32 items-center gap-2 rounded-md border bg-background p-2 shadow-xs", children: [
1471
+ /* @__PURE__ */ jsx(
1472
+ IconButton,
1473
+ {
1474
+ type: "button",
1475
+ variant: "ghost",
1476
+ size: "sm",
1477
+ "aria-label": t(
1478
+ "wms.backend.inventory.cycleCount.form.decrease",
1479
+ "Decrease quantity"
1480
+ ),
1481
+ onClick: () => adjustCounted(-1),
1482
+ disabled: loadingBalance || submitting,
1483
+ children: /* @__PURE__ */ jsx(Minus, { className: "size-4" })
1484
+ }
1485
+ ),
1486
+ /* @__PURE__ */ jsx(
1487
+ Input,
1488
+ {
1489
+ type: "number",
1490
+ inputMode: "numeric",
1491
+ min: 0,
1492
+ value: String(form.countedQuantity),
1493
+ onChange: (event) => {
1494
+ const parsed = Number(event.target.value);
1495
+ if (!Number.isFinite(parsed) || parsed < 0) return;
1496
+ patchForm({ countedQuantity: parsed });
1497
+ },
1498
+ className: "h-8 w-auto min-w-0 flex-1 border-0 bg-transparent p-0 shadow-none",
1499
+ inputClassName: "text-center [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
1500
+ disabled: loadingBalance || submitting
1501
+ }
1502
+ ),
1503
+ /* @__PURE__ */ jsx(
1504
+ IconButton,
1505
+ {
1506
+ type: "button",
1507
+ variant: "ghost",
1508
+ size: "sm",
1509
+ "aria-label": t(
1510
+ "wms.backend.inventory.cycleCount.form.increase",
1511
+ "Increase quantity"
1512
+ ),
1513
+ onClick: () => adjustCounted(1),
1514
+ disabled: loadingBalance || submitting,
1515
+ children: /* @__PURE__ */ jsx(Plus, { className: "size-4" })
1516
+ }
1517
+ )
1518
+ ] })
1519
+ }
1520
+ ),
1521
+ /* @__PURE__ */ jsx(FormField, { label: t("wms.backend.inventory.cycleCount.form.countNotes", "Notes"), children: /* @__PURE__ */ jsx(
1522
+ Textarea,
1523
+ {
1524
+ value: form.countNotes,
1525
+ onChange: (event) => patchForm({ countNotes: event.target.value }),
1526
+ placeholder: t(
1527
+ "wms.backend.inventory.cycleCount.form.countNotesPlaceholder",
1528
+ "Optional \u2014 defects, packaging notes"
1529
+ ),
1530
+ rows: 3,
1531
+ disabled: loadingBalance || submitting
1532
+ }
1533
+ ) }),
1534
+ form.catalogVariantId && form.locationId ? /* @__PURE__ */ jsxs(
1535
+ SummaryPanel,
1536
+ {
1537
+ title: t("wms.backend.inventory.cycleCount.review.progress.title", "Progress"),
1538
+ children: [
1539
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between gap-3", children: [
1540
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold tabular-nums text-foreground", children: t(
1541
+ "wms.backend.inventory.cycleCount.review.progress.summary",
1542
+ "System {system} \xB7 Counted {counted}",
1543
+ {
1544
+ system: systemOnHand,
1545
+ counted: form.countedQuantity
1546
+ }
1547
+ ) }),
1548
+ variance === 0 ? /* @__PURE__ */ jsx(StatusBadge, { variant: "success", children: t("wms.backend.inventory.cycleCount.review.progress.match", "Match") }) : /* @__PURE__ */ jsx(StatusBadge, { variant: "info", children: formatSignedQuantity(variance) })
1549
+ ] }),
1550
+ loadingBalance ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t(
1551
+ "wms.backend.inventory.cycleCount.review.progress.loading",
1552
+ "Refreshing balance\u2026"
1553
+ ) }) : null,
1554
+ balanceError ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-status-warning-fg", children: balanceError }) : null
1555
+ ]
1556
+ }
1557
+ ) : null
1558
+ ] }) : null
1559
+ ] }) : null,
1560
+ step === 3 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1561
+ /* @__PURE__ */ jsx(
1562
+ FormField,
1563
+ {
1564
+ label: t(
1565
+ "wms.backend.inventory.cycleCount.review.totalCounted",
1566
+ "Total counted"
1567
+ ),
1568
+ children: /* @__PURE__ */ jsx("div", { className: "rounded-md bg-muted/50 px-3 py-2.5 text-sm text-muted-foreground", children: t(
1569
+ "wms.backend.inventory.cycleCount.review.totalCountedSummary",
1570
+ "{counted} units \xB7 {location} \xB7 {variant}",
1571
+ {
1572
+ counted: form.countedQuantity,
1573
+ location: locationLabel,
1574
+ variant: variantLabel
1575
+ }
1576
+ ) })
1577
+ }
1578
+ ),
1579
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-5 sm:grid-cols-2", children: [
1580
+ /* @__PURE__ */ jsx(
1581
+ FormField,
1582
+ {
1583
+ label: t("wms.backend.inventory.cycleCount.review.matches", "Matches"),
1584
+ children: /* @__PURE__ */ jsx("div", { className: "rounded-md border bg-background px-3 py-2.5 text-sm text-foreground", children: variance === 0 ? t(
1585
+ "wms.backend.inventory.cycleCount.review.matchesWithinTolerance",
1586
+ "Within tolerance"
1587
+ ) : t("wms.backend.inventory.cycleCount.review.matchesNone", "No match") })
1588
+ }
1589
+ ),
1590
+ /* @__PURE__ */ jsx(
1591
+ FormField,
1592
+ {
1593
+ label: t("wms.backend.inventory.cycleCount.review.variances", "Variances"),
1594
+ children: /* @__PURE__ */ jsx("div", { className: "rounded-md border bg-background px-3 py-2.5 text-sm text-foreground", children: variance === 0 ? t("wms.backend.inventory.cycleCount.review.variancesNone", "None") : formatSignedQuantity(variance) })
1595
+ }
1596
+ )
1597
+ ] }),
1598
+ variance !== 0 ? /* @__PURE__ */ jsx(
1599
+ FormField,
1600
+ {
1601
+ label: t("wms.backend.inventory.cycleCount.review.varianceDetail", "Variance"),
1602
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1603
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1604
+ locationLabel,
1605
+ " \xB7 ",
1606
+ variantLabel,
1607
+ form.lotId ? ` \xB7 ${lotLabel}` : ""
1608
+ ] }),
1609
+ /* @__PURE__ */ jsx("div", { className: "rounded-md border bg-background px-3 py-2.5 text-sm text-foreground", children: t(
1610
+ "wms.backend.inventory.cycleCount.review.varianceLine",
1611
+ "Counted {counted} vs expected {expected} \xB7 {delta}",
1612
+ {
1613
+ counted: form.countedQuantity,
1614
+ expected: systemOnHand,
1615
+ delta: formatSignedQuantity(variance)
1616
+ }
1617
+ ) })
1618
+ ] })
1619
+ }
1620
+ ) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t(
1621
+ "wms.backend.inventory.cycleCount.review.noVariance",
1622
+ "Counts match the ledger. Posting will not create a movement."
1623
+ ) }),
1624
+ /* @__PURE__ */ jsx(
1625
+ FormField,
1626
+ {
1627
+ label: t(
1628
+ "wms.backend.inventory.cycleCount.form.autoAdjust",
1629
+ "Auto-adjust on commit"
1630
+ ),
1631
+ required: variance !== 0,
1632
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1633
+ /* @__PURE__ */ jsx(
1634
+ Switch,
1635
+ {
1636
+ checked: autoAdjust,
1637
+ onCheckedChange: setAutoAdjust,
1638
+ disabled: submitting || variance === 0
1639
+ }
1640
+ ),
1641
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: variance === 0 ? t(
1642
+ "wms.backend.inventory.cycleCount.form.autoAdjustNoVariance",
1643
+ "No ledger write required \u2014 counts match."
1644
+ ) : autoAdjust ? t(
1645
+ "wms.backend.inventory.cycleCount.form.autoAdjustHint",
1646
+ "Yes \u2014 writes a cycle-count movement on commit"
1647
+ ) : t(
1648
+ "wms.backend.inventory.cycleCount.form.autoAdjustHintOff",
1649
+ "No \u2014 ledger unchanged on commit"
1650
+ ) })
1651
+ ] })
1652
+ }
1653
+ ),
1654
+ /* @__PURE__ */ jsx(
1655
+ FormField,
1656
+ {
1657
+ label: t("wms.backend.inventory.cycleCount.form.reason", "Reason"),
1658
+ required: true,
1659
+ error: fieldErrors.reason,
1660
+ children: /* @__PURE__ */ jsx(
1661
+ Textarea,
1662
+ {
1663
+ value: form.reason,
1664
+ onChange: (event) => patchForm({ reason: event.target.value }),
1665
+ placeholder: t(
1666
+ "wms.backend.inventory.cycleCount.form.reasonPlaceholder",
1667
+ "Required for variances \u2014 auditor context"
1668
+ ),
1669
+ rows: 3,
1670
+ disabled: submitting
1671
+ }
1672
+ )
1673
+ }
1674
+ ),
1675
+ /* @__PURE__ */ jsx(
1676
+ SummaryPanel,
1677
+ {
1678
+ title: t(
1679
+ "wms.backend.inventory.cycleCount.review.commitSummary.title",
1680
+ "Commit summary"
1681
+ ),
1682
+ children: /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between gap-3", children: [
1683
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold tabular-nums text-foreground", children: variance === 0 || !autoAdjust ? t(
1684
+ "wms.backend.inventory.cycleCount.review.commitSummary.noMovement",
1685
+ "No movement \xB7 ledger unchanged"
1686
+ ) : t(
1687
+ "wms.backend.inventory.cycleCount.review.commitSummary.movement",
1688
+ "{delta} net \xB7 1 ledger write",
1689
+ { delta: formatSignedQuantity(variance) }
1690
+ ) }),
1691
+ /* @__PURE__ */ jsx(StatusBadge, { variant: "success", children: t("wms.backend.inventory.cycleCount.review.commitSummary.ready", "Ready") })
1692
+ ] })
1693
+ }
1694
+ )
1695
+ ] }) : null
1696
+ ] }),
1697
+ /* @__PURE__ */ jsxs(DialogFooter, { bordered: false, className: "border-t px-6 py-4 sm:justify-between", children: [
1698
+ /* @__PURE__ */ jsxs("p", { className: "hidden text-xs text-muted-foreground md:inline-flex md:items-center md:gap-1.5", children: [
1699
+ /* @__PURE__ */ jsx(KbdShortcut, { keys: ["\u2318", "Enter"] }),
1700
+ /* @__PURE__ */ jsx("span", { children: "/" }),
1701
+ /* @__PURE__ */ jsx(KbdShortcut, { keys: ["Ctrl", "Enter"] }),
1702
+ /* @__PURE__ */ jsx("span", { children: shortcutHint })
1703
+ ] }),
1704
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col-reverse gap-2 md:w-auto md:flex-row md:flex-wrap md:justify-end", children: [
1705
+ step === 1 ? /* @__PURE__ */ jsx(
1706
+ Button,
1707
+ {
1708
+ type: "button",
1709
+ variant: "outline",
1710
+ onClick: closeDialog,
1711
+ disabled: loadingBalance || submitting,
1712
+ children: t("common.cancel", "Cancel")
1713
+ }
1714
+ ) : /* @__PURE__ */ jsx(
1715
+ Button,
1716
+ {
1717
+ type: "button",
1718
+ variant: "outline",
1719
+ onClick: () => setStep((current) => current === 3 ? 2 : 1),
1720
+ disabled: loadingBalance || submitting,
1721
+ children: t("wms.backend.inventory.cycleCount.steps.back", "Back")
1722
+ }
1723
+ ),
1724
+ step === 2 && (linesPosted > 0 || isQueueComplete) ? /* @__PURE__ */ jsx(
1725
+ Button,
1726
+ {
1727
+ type: "button",
1728
+ variant: finishConfirmPending ? "destructive" : "outline",
1729
+ onClick: () => {
1730
+ const remaining = form.expectedSkus - linesPosted;
1731
+ if (!finishConfirmPending && remaining > 0 && !isQueueComplete) {
1732
+ setFinishConfirmPending(true);
1733
+ return;
1734
+ }
1735
+ closeDialog();
1736
+ },
1737
+ disabled: loadingBalance || submitting,
1738
+ title: isQueueComplete ? void 0 : t(
1739
+ "wms.backend.inventory.cycleCount.steps.counting.finishTooltip",
1740
+ "{count} line(s) already committed \u2014 click to close without losing them",
1741
+ { count: linesPosted }
1742
+ ),
1743
+ children: finishConfirmPending ? t(
1744
+ "wms.backend.inventory.cycleCount.steps.counting.finishConfirm",
1745
+ "Confirm finish"
1746
+ ) : isQueueComplete ? t(
1747
+ "wms.backend.inventory.cycleCount.steps.counting.finishComplete",
1748
+ "Close session"
1749
+ ) : t(
1750
+ "wms.backend.inventory.cycleCount.steps.counting.finish",
1751
+ "Finish session ({count})",
1752
+ { count: linesPosted }
1753
+ )
1754
+ }
1755
+ ) : null,
1756
+ finishConfirmPending ? /* @__PURE__ */ jsx("p", { className: "order-first text-xs text-status-warning-fg sm:order-none sm:self-center", children: t(
1757
+ "wms.backend.inventory.cycleCount.steps.counting.finishWarning",
1758
+ "{remaining} item(s) still in scope \u2014 confirm to finish early.",
1759
+ { remaining: form.expectedSkus - linesPosted }
1760
+ ) }) : null,
1761
+ !isQueueComplete ? /* @__PURE__ */ jsx(Button, { type: "submit", disabled: primaryDisabled, children: step === 1 ? t(
1762
+ "wms.backend.inventory.cycleCount.steps.setup.submit",
1763
+ "Start counting"
1764
+ ) : step === 2 ? t(
1765
+ "wms.backend.inventory.cycleCount.steps.counting.submit",
1766
+ "Review variances"
1767
+ ) : t(
1768
+ "wms.backend.inventory.cycleCount.steps.review.submit",
1769
+ "Commit & count next"
1770
+ ) }) : null
1771
+ ] })
1772
+ ] })
1773
+ ]
1774
+ }
1775
+ )
1776
+ ]
1777
+ }
1778
+ ) });
1779
+ }
1780
+ export {
1781
+ CycleCountWizardDialog
1782
+ };
1783
+ //# sourceMappingURL=CycleCountWizardDialog.js.map