@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,1340 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import Link from 'next/link'
5
+ import { useRouter } from 'next/navigation'
6
+ import { z } from 'zod'
7
+ import type { ColumnDef, SortingState } from '@tanstack/react-table'
8
+ import { useQuery, useQueryClient, type QueryClient } from '@tanstack/react-query'
9
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
10
+ import { DataTable } from '@open-mercato/ui/backend/DataTable'
11
+ import { EmptyState } from '@open-mercato/ui/backend/EmptyState'
12
+ import { CrudForm, type CrudField, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'
13
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
14
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
15
+ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
16
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
17
+ import { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'
18
+ import { Button } from '@open-mercato/ui/primitives/button'
19
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'
20
+ import { RowActions } from '@open-mercato/ui/backend/RowActions'
21
+ import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
22
+ import { Boxes, Layers, MapPinned, Warehouse } from 'lucide-react'
23
+ import { E } from '#generated/entities.ids.generated'
24
+ import {
25
+ buildQuery,
26
+ loadCatalogVariantOptions,
27
+ loadWarehouseOptions,
28
+ } from './wmsLookupLoaders'
29
+ import {
30
+ createInventoryQuantityFormatter,
31
+ formatCatalogProductLabel,
32
+ formatCatalogVariantLabel,
33
+ formatInventoryQuantity,
34
+ inventoryRotationStrategyLabel,
35
+ } from '../../lib/inventoryDisplayUi'
36
+ import { LocationEditDialog } from './LocationEditDialog'
37
+ import { useWmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'
38
+ import { flashMutationError } from '../../lib/flashMutationError'
39
+
40
+ type PagedResponse<T> = {
41
+ items: T[]
42
+ total: number
43
+ totalPages: number
44
+ page?: number
45
+ pageSize?: number
46
+ }
47
+
48
+ type WarehouseRow = {
49
+ id: string
50
+ name?: string | null
51
+ code?: string | null
52
+ city?: string | null
53
+ country?: string | null
54
+ timezone?: string | null
55
+ is_active?: boolean | null
56
+ is_primary?: boolean | null
57
+ }
58
+
59
+ type LocationRow = {
60
+ id: string
61
+ warehouse_id?: string | null
62
+ warehouse_name?: string | null
63
+ warehouse_code?: string | null
64
+ code?: string | null
65
+ type?: string | null
66
+ capacity_units?: string | number | null
67
+ capacity_weight?: string | number | null
68
+ is_active?: boolean | null
69
+ updated_at?: string | null
70
+ }
71
+
72
+ type ZoneRow = {
73
+ id: string
74
+ warehouse_id?: string | null
75
+ warehouse_name?: string | null
76
+ warehouse_code?: string | null
77
+ code?: string | null
78
+ name?: string | null
79
+ priority?: number | null
80
+ }
81
+
82
+ type InventoryProfileRow = {
83
+ id: string
84
+ catalog_product_id?: string | null
85
+ catalog_variant_id?: string | null
86
+ product_title?: string | null
87
+ product_sku?: string | null
88
+ variant_name?: string | null
89
+ variant_sku?: string | null
90
+ default_uom?: string | null
91
+ default_strategy?: string | null
92
+ track_lot?: boolean | null
93
+ track_serial?: boolean | null
94
+ track_expiration?: boolean | null
95
+ reorder_point?: string | number | null
96
+ safety_stock?: string | number | null
97
+ }
98
+
99
+ type WarehouseFormValues = {
100
+ name: string
101
+ code: string
102
+ city?: string
103
+ country?: string
104
+ timezone?: string
105
+ isActive: boolean
106
+ isPrimary: boolean
107
+ }
108
+
109
+ type ZoneFormValues = {
110
+ warehouseId: string
111
+ code: string
112
+ name: string
113
+ priority?: number
114
+ }
115
+
116
+ type InventoryProfileFormValues = {
117
+ catalogProductId: string
118
+ catalogVariantId?: string
119
+ defaultUom: string
120
+ defaultStrategy: 'fifo' | 'lifo' | 'fefo'
121
+ trackLot: boolean
122
+ trackSerial: boolean
123
+ trackExpiration: boolean
124
+ reorderPoint?: number
125
+ safetyStock?: number
126
+ }
127
+
128
+ type DialogMode<T> =
129
+ | { mode: 'create' }
130
+ | { mode: 'edit'; row: T }
131
+
132
+ const warehouseFormSchema = z.object({
133
+ name: z.string().trim().min(1),
134
+ code: z.string().trim().min(1),
135
+ city: z.string().trim().optional(),
136
+ country: z.string().trim().optional(),
137
+ timezone: z.string().trim().optional(),
138
+ isActive: z.boolean().default(true),
139
+ isPrimary: z.boolean().default(false),
140
+ })
141
+
142
+ const zoneFormSchema = z.object({
143
+ warehouseId: z.string().uuid(),
144
+ code: z.string().trim().min(1).max(80),
145
+ name: z.string().trim().min(1).max(200),
146
+ priority: z.coerce.number().int().min(0).optional(),
147
+ })
148
+
149
+ function buildInventoryProfileFormSchema(fefoRequiredMsg: string) {
150
+ return z.object({
151
+ catalogProductId: z.string().uuid(),
152
+ catalogVariantId: z.string().uuid().optional().or(z.literal('')),
153
+ defaultUom: z.string().trim().min(1),
154
+ defaultStrategy: z.enum(['fifo', 'lifo', 'fefo']),
155
+ trackLot: z.boolean().default(false),
156
+ trackSerial: z.boolean().default(false),
157
+ trackExpiration: z.boolean().default(false),
158
+ reorderPoint: z.coerce.number().min(0).optional(),
159
+ safetyStock: z.coerce.number().min(0).optional(),
160
+ }).superRefine((payload, ctx) => {
161
+ if (payload.trackExpiration && payload.defaultStrategy !== 'fefo') {
162
+ ctx.addIssue({
163
+ code: z.ZodIssueCode.custom,
164
+ path: ['defaultStrategy'],
165
+ message: fefoRequiredMsg,
166
+ })
167
+ }
168
+ })
169
+ }
170
+
171
+ const STRATEGY_OPTIONS: CrudFieldOption[] = [
172
+ { value: 'fifo', label: 'FIFO' },
173
+ { value: 'lifo', label: 'LIFO' },
174
+ { value: 'fefo', label: 'FEFO' },
175
+ ]
176
+
177
+ /** Merge a newly created row into all cached warehouse list queries so the table updates immediately (refetch can lag or match stale HTTP cache). */
178
+ function mergeCreatedWarehouseIntoWarehousesCaches(
179
+ queryClient: QueryClient,
180
+ newId: string,
181
+ values: WarehouseFormValues,
182
+ ) {
183
+ const row: WarehouseRow = {
184
+ id: newId,
185
+ name: values.name,
186
+ code: values.code,
187
+ city: values.city || null,
188
+ country: values.country || null,
189
+ timezone: values.timezone || null,
190
+ is_active: values.isActive,
191
+ is_primary: values.isPrimary,
192
+ }
193
+ const haystack = `${values.name}\n${values.code}`.toLowerCase()
194
+
195
+ const entries = queryClient.getQueriesData<PagedResponse<WarehouseRow>>({
196
+ queryKey: ['wms-config', 'warehouses'],
197
+ exact: false,
198
+ })
199
+ for (const [key, old] of entries) {
200
+ if (!old || !Array.isArray(old.items)) continue
201
+ if (old.items.some((r: WarehouseRow) => r.id === newId)) continue
202
+
203
+ const paramStr = Array.isArray(key) && typeof key[2] === 'string' ? key[2] : ''
204
+ const sp = new URLSearchParams(paramStr)
205
+ const searchTerm = (sp.get('search') || '').trim().toLowerCase()
206
+ if (searchTerm && !haystack.includes(searchTerm)) continue
207
+
208
+ const pageSize = typeof old.pageSize === 'number' && old.pageSize > 0 ? old.pageSize : 10
209
+ const nextTotal = (old.total ?? 0) + 1
210
+ queryClient.setQueryData<PagedResponse<WarehouseRow>>(key, {
211
+ ...old,
212
+ items: [row, ...old.items.filter((r: WarehouseRow) => r.id !== newId)].slice(0, pageSize),
213
+ total: nextTotal,
214
+ totalPages: Math.max(1, Math.ceil(nextTotal / pageSize)),
215
+ })
216
+ }
217
+ }
218
+
219
+ async function loadCatalogProductOptions(query?: string): Promise<CrudFieldOption[]> {
220
+ const params = buildQuery({ page: 1, pageSize: 25, search: query?.trim() || undefined })
221
+ const call = await apiCall<PagedResponse<{ id?: string | null; title?: string | null; sku?: string | null }>>(`/api/catalog/products?${params}`)
222
+ if (!call.ok) return []
223
+ return (call.result?.items ?? [])
224
+ .map((item) => {
225
+ const value = typeof item.id === 'string' ? item.id : null
226
+ if (!value) return null
227
+ const label = item.title || item.sku || value
228
+ return { value, label }
229
+ })
230
+ .filter((option): option is CrudFieldOption => option !== null)
231
+ }
232
+
233
+ function SectionCard({
234
+ title,
235
+ description,
236
+ icon,
237
+ viewAllHref,
238
+ viewAllLabel,
239
+ children,
240
+ }: {
241
+ title: string
242
+ description: string
243
+ icon: React.ReactNode
244
+ viewAllHref?: string
245
+ viewAllLabel?: string
246
+ children: React.ReactNode
247
+ }) {
248
+ return (
249
+ <section className="rounded-lg border bg-card p-5 text-card-foreground shadow-sm">
250
+ <div className="mb-4 flex items-start gap-3">
251
+ <div className="rounded-md border bg-muted/40 p-2 text-muted-foreground">{icon}</div>
252
+ <div className="flex flex-1 items-start justify-between gap-2">
253
+ <div className="space-y-1">
254
+ <h2 className="text-xl font-semibold">{title}</h2>
255
+ <p className="text-sm text-muted-foreground">{description}</p>
256
+ </div>
257
+ {viewAllHref && (
258
+ <Link href={viewAllHref} className="shrink-0 text-sm font-medium text-primary hover:underline">
259
+ {viewAllLabel}
260
+ </Link>
261
+ )}
262
+ </div>
263
+ </div>
264
+ {children}
265
+ </section>
266
+ )
267
+ }
268
+
269
+ type ConfigSectionOptions = {
270
+ /** When set, show a "View all" link to the standalone list page. Omit on that list page itself. */
271
+ viewAllHref?: string
272
+ }
273
+
274
+ export function WarehouseSection({ viewAllHref }: ConfigSectionOptions = {}) {
275
+ const t = useT()
276
+ const queryClient = useQueryClient()
277
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
278
+ const { runMutation } = useGuardedMutation<Record<string, unknown>>({ contextId: 'wms-config-warehouses' })
279
+ const access = useWmsInventoryMutationAccess()
280
+ const [page, setPage] = React.useState(1)
281
+ const [search, setSearch] = React.useState('')
282
+ const [sorting, setSorting] = React.useState<SortingState>([{ id: 'updatedAt', desc: true }])
283
+ const [submitting, setSubmitting] = React.useState(false)
284
+ const [dialog, setDialog] = React.useState<DialogMode<WarehouseRow> | null>(null)
285
+
286
+ const handleSortingChange = React.useCallback((nextSorting: SortingState) => {
287
+ setSorting(nextSorting)
288
+ setPage(1)
289
+ }, [])
290
+
291
+ const params = React.useMemo(() => {
292
+ const sortCol = sorting[0]
293
+ return buildQuery({
294
+ page,
295
+ pageSize: 10,
296
+ search: search.trim() || undefined,
297
+ sortField: sortCol ? sortCol.id : 'updatedAt',
298
+ sortDir: sortCol ? (sortCol.desc ? 'desc' : 'asc') : 'desc',
299
+ })
300
+ }, [page, search, sorting])
301
+
302
+ const query = useQuery({
303
+ queryKey: ['wms-config', 'warehouses', params],
304
+ queryFn: async () => {
305
+ const call = await apiCall<PagedResponse<WarehouseRow>>(`/api/wms/warehouses?${params}`, {
306
+ cache: 'no-store',
307
+ })
308
+ if (!call.ok) {
309
+ await raiseCrudError(call.response, t('wms.backend.config.warehouses.errors.load', 'Failed to load warehouses.'))
310
+ }
311
+ return call.result ?? { items: [], total: 0, totalPages: 1 }
312
+ },
313
+ })
314
+
315
+ const fields = React.useMemo<CrudField[]>(() => [
316
+ { id: 'name', type: 'text', label: t('wms.backend.config.warehouses.form.name', 'Name'), required: true },
317
+ { id: 'code', type: 'text', label: t('wms.backend.config.warehouses.form.code', 'Code'), required: true },
318
+ { id: 'city', type: 'text', label: t('wms.backend.config.warehouses.form.city', 'City') },
319
+ { id: 'country', type: 'text', label: t('wms.backend.config.warehouses.form.country', 'Country') },
320
+ { id: 'timezone', type: 'text', label: t('wms.backend.config.warehouses.form.timezone', 'Timezone') },
321
+ { id: 'isPrimary', type: 'checkbox', label: t('wms.backend.config.warehouses.form.primary', 'Primary warehouse') },
322
+ { id: 'isActive', type: 'checkbox', label: t('wms.backend.config.warehouses.form.active', 'Active') },
323
+ ], [t])
324
+
325
+ const columns = React.useMemo<ColumnDef<WarehouseRow>[]>(() => [
326
+ {
327
+ accessorKey: 'name',
328
+ header: t('wms.backend.config.warehouses.columns.name', 'Warehouse'),
329
+ enableSorting: true,
330
+ cell: ({ row }) => row.original.name || row.original.code || row.original.id,
331
+ },
332
+ { accessorKey: 'code', header: t('wms.backend.config.warehouses.columns.code', 'Code'), enableSorting: true },
333
+ { accessorKey: 'city', header: t('wms.backend.config.warehouses.columns.city', 'City'), cell: ({ row }) => row.original.city || '—' },
334
+ { accessorKey: 'country', header: t('wms.backend.config.warehouses.columns.country', 'Country'), cell: ({ row }) => row.original.country || '—' },
335
+ {
336
+ accessorKey: 'is_primary',
337
+ header: t('wms.backend.config.warehouses.columns.primary', 'Primary'),
338
+ cell: ({ row }) =>
339
+ row.original.is_primary
340
+ ? t('wms.backend.config.warehouses.primary.yes', 'Primary')
341
+ : '—',
342
+ },
343
+ {
344
+ accessorKey: 'is_active',
345
+ header: t('wms.backend.config.warehouses.columns.status', 'Status'),
346
+ cell: ({ row }) =>
347
+ row.original.is_active === false
348
+ ? t('wms.common.inactive', 'Inactive')
349
+ : t('wms.common.active', 'Active'),
350
+ },
351
+ ], [t])
352
+
353
+ const initialValues = React.useMemo<WarehouseFormValues>(() => {
354
+ if (dialog?.mode === 'edit') {
355
+ return {
356
+ name: dialog.row.name || '',
357
+ code: dialog.row.code || '',
358
+ city: dialog.row.city || '',
359
+ country: dialog.row.country || '',
360
+ timezone: dialog.row.timezone || '',
361
+ isActive: dialog.row.is_active !== false,
362
+ isPrimary: dialog.row.is_primary === true,
363
+ }
364
+ }
365
+ return {
366
+ name: '',
367
+ code: '',
368
+ city: '',
369
+ country: '',
370
+ timezone: '',
371
+ isActive: true,
372
+ isPrimary: false,
373
+ }
374
+ }, [dialog])
375
+
376
+ const closeDialog = React.useCallback(() => {
377
+ setDialog(null)
378
+ setSubmitting(false)
379
+ }, [])
380
+
381
+ const refresh = React.useCallback(async () => {
382
+ await queryClient.cancelQueries({ queryKey: ['wms-config', 'warehouses'] })
383
+ await queryClient.invalidateQueries({ queryKey: ['wms-config', 'warehouses'] })
384
+ await queryClient.refetchQueries({ queryKey: ['wms-config', 'warehouses'], type: 'all' })
385
+ }, [queryClient])
386
+
387
+ const handleSubmit = React.useCallback(async (values: WarehouseFormValues) => {
388
+ if (!dialog) return
389
+ const submitMode = dialog.mode
390
+ setSubmitting(true)
391
+ try {
392
+ const call = await runMutation({
393
+ operation: async () => {
394
+ const result = await apiCall<{ id?: string | null }>(
395
+ '/api/wms/warehouses',
396
+ {
397
+ method: submitMode === 'edit' ? 'PUT' : 'POST',
398
+ body: JSON.stringify(
399
+ submitMode === 'edit'
400
+ ? { id: dialog.row.id, ...values }
401
+ : values,
402
+ ),
403
+ },
404
+ )
405
+ if (!result.ok) {
406
+ await raiseCrudError(result.response, t('wms.backend.config.warehouses.errors.save', 'Failed to save warehouse.'))
407
+ }
408
+ return result
409
+ },
410
+ context: {},
411
+ mutationPayload: submitMode === 'edit' ? { id: dialog.row.id, ...values } : values,
412
+ })
413
+ flash(
414
+ submitMode === 'edit'
415
+ ? t('wms.backend.config.warehouses.flash.updated', 'Warehouse updated')
416
+ : t('wms.backend.config.warehouses.flash.created', 'Warehouse created'),
417
+ 'success',
418
+ )
419
+
420
+ const createdId =
421
+ submitMode === 'create' &&
422
+ call?.result &&
423
+ typeof call.result === 'object' &&
424
+ typeof (call.result as { id?: unknown }).id === 'string'
425
+ ? (call.result as { id: string }).id.trim()
426
+ : null
427
+
428
+ closeDialog()
429
+
430
+ if (submitMode === 'create' && createdId) {
431
+ mergeCreatedWarehouseIntoWarehousesCaches(queryClient, createdId, values)
432
+ setSearch('')
433
+ setPage(1)
434
+ }
435
+
436
+ await refresh()
437
+ } catch (error) {
438
+ flashMutationError(error, t('wms.backend.config.warehouses.errors.save', 'Failed to save warehouse.'), t)
439
+ setSubmitting(false)
440
+ }
441
+ }, [closeDialog, dialog, queryClient, refresh, runMutation, t])
442
+
443
+ const handleDelete = React.useCallback(async (row: WarehouseRow) => {
444
+ const confirmed = await confirm({
445
+ title: t('wms.backend.config.warehouses.confirmDelete', 'Archive warehouse "{name}"?', {
446
+ name: row.name || row.code || row.id,
447
+ }),
448
+ variant: 'destructive',
449
+ })
450
+ if (!confirmed) return
451
+
452
+ try {
453
+ await runMutation({
454
+ operation: async () => {
455
+ const call = await apiCall(`/api/wms/warehouses?id=${encodeURIComponent(row.id)}`, { method: 'DELETE' })
456
+ if (!call.ok) {
457
+ await raiseCrudError(call.response, t('wms.backend.config.warehouses.errors.delete', 'Failed to archive warehouse.'))
458
+ }
459
+ return call
460
+ },
461
+ context: {},
462
+ mutationPayload: { id: row.id },
463
+ })
464
+ flash(t('wms.backend.config.warehouses.flash.deleted', 'Warehouse archived'), 'success')
465
+ await refresh()
466
+ } catch (error) {
467
+ flashMutationError(error, t('wms.backend.config.warehouses.errors.delete', 'Failed to archive warehouse.'), t)
468
+ }
469
+ }, [confirm, refresh, runMutation, t])
470
+
471
+ return (
472
+ <>
473
+ <SectionCard
474
+ title={t('wms.backend.config.warehouses.title', 'Warehouses')}
475
+ description={t('wms.backend.config.warehouses.description', 'Manage the high-level warehouse nodes used by WMS reservations and inventory movements.')}
476
+ icon={<Warehouse className="size-5" />}
477
+ viewAllHref={viewAllHref}
478
+ viewAllLabel={viewAllHref ? t('wms.backend.config.viewAll', 'View all →') : undefined}
479
+ >
480
+ <DataTable
481
+ embedded
482
+ title={t('wms.backend.config.warehouses.title', 'Warehouses')}
483
+ columns={columns}
484
+ data={query.data?.items ?? []}
485
+ isLoading={query.isLoading}
486
+ error={query.isError ? t('wms.backend.config.warehouses.errors.load', 'Failed to load warehouses.') : null}
487
+ entityId={E.wms.warehouse}
488
+ searchValue={search}
489
+ onSearchChange={(value) => {
490
+ setSearch(value)
491
+ setPage(1)
492
+ }}
493
+ searchPlaceholder={t('wms.backend.config.warehouses.search', 'Search warehouses')}
494
+ sorting={sorting}
495
+ onSortingChange={handleSortingChange}
496
+ sortable
497
+ manualSorting
498
+ actions={access.canManageWarehouses ? (
499
+ <Button type="button" size="sm" onClick={() => setDialog({ mode: 'create' })}>
500
+ {t('wms.backend.config.actions.addWarehouse', 'Add warehouse')}
501
+ </Button>
502
+ ) : null}
503
+ rowActions={(row) => (
504
+ <RowActions
505
+ items={[
506
+ { id: 'edit', label: t('common.edit', 'Edit'), onSelect: () => setDialog({ mode: 'edit', row }) },
507
+ { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
508
+ ]}
509
+ />
510
+ )}
511
+ pagination={{
512
+ page,
513
+ pageSize: 10,
514
+ total: query.data?.total ?? 0,
515
+ totalPages: query.data?.totalPages ?? 1,
516
+ onPageChange: setPage,
517
+ }}
518
+ perspective={{ tableId: 'wms.config.warehouses' }}
519
+ emptyState={(
520
+ <EmptyState
521
+ title={t('wms.backend.config.warehouses.empty.title', 'No warehouses')}
522
+ description={t('wms.backend.config.warehouses.empty.description', 'Create the first warehouse to expose topology and inventory assignment in WMS.')}
523
+ action={access.canManageWarehouses ? { label: t('wms.backend.config.actions.addWarehouse', 'Add warehouse'), onClick: () => setDialog({ mode: 'create' }) } : undefined}
524
+ />
525
+ )}
526
+ />
527
+ </SectionCard>
528
+
529
+ <Dialog open={dialog !== null} onOpenChange={(next) => !next && closeDialog()}>
530
+ <DialogContent className="max-w-3xl">
531
+ <DialogHeader>
532
+ <DialogTitle>
533
+ {dialog?.mode === 'edit'
534
+ ? t('wms.backend.config.warehouses.dialog.edit', 'Edit warehouse')
535
+ : t('wms.backend.config.warehouses.dialog.create', 'Create warehouse')}
536
+ </DialogTitle>
537
+ </DialogHeader>
538
+ <CrudForm<WarehouseFormValues>
539
+ schema={warehouseFormSchema}
540
+ fields={fields}
541
+ entityId={E.wms.warehouse}
542
+ initialValues={initialValues}
543
+ submitLabel={t('common.save', 'Save')}
544
+ onSubmit={handleSubmit}
545
+ embedded
546
+ isLoading={submitting}
547
+ twoColumn
548
+ />
549
+ </DialogContent>
550
+ </Dialog>
551
+ {ConfirmDialogElement}
552
+ </>
553
+ )
554
+ }
555
+
556
+ export function ZoneSection({ viewAllHref }: ConfigSectionOptions = {}) {
557
+ const t = useT()
558
+ const queryClient = useQueryClient()
559
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
560
+ const { runMutation } = useGuardedMutation<Record<string, unknown>>({ contextId: 'wms-config-zones' })
561
+ const access = useWmsInventoryMutationAccess()
562
+ const [page, setPage] = React.useState(1)
563
+ const [search, setSearch] = React.useState('')
564
+ const [sorting, setSorting] = React.useState<SortingState>([{ id: 'priority', desc: false }])
565
+ const [submitting, setSubmitting] = React.useState(false)
566
+ const [dialog, setDialog] = React.useState<DialogMode<ZoneRow> | null>(null)
567
+
568
+ const handleSortingChange = React.useCallback((nextSorting: SortingState) => {
569
+ setSorting(nextSorting)
570
+ setPage(1)
571
+ }, [])
572
+
573
+ const params = React.useMemo(() => {
574
+ const sortCol = sorting[0]
575
+ return buildQuery({
576
+ page,
577
+ pageSize: 10,
578
+ search: search.trim() || undefined,
579
+ sortField: sortCol ? sortCol.id : 'priority',
580
+ sortDir: sortCol ? (sortCol.desc ? 'desc' : 'asc') : 'asc',
581
+ })
582
+ }, [page, search, sorting])
583
+
584
+ const query = useQuery({
585
+ queryKey: ['wms-config', 'zones', params],
586
+ queryFn: async () => {
587
+ const call = await apiCall<PagedResponse<ZoneRow>>(`/api/wms/zones?${params}`, {
588
+ cache: 'no-store',
589
+ })
590
+ if (!call.ok) {
591
+ await raiseCrudError(call.response, t('wms.backend.config.zones.errors.load', 'Failed to load zones.'))
592
+ }
593
+ return call.result ?? { items: [], total: 0, totalPages: 1 }
594
+ },
595
+ })
596
+
597
+ const fields = React.useMemo<CrudField[]>(() => [
598
+ {
599
+ id: 'warehouseId',
600
+ type: 'combobox',
601
+ label: t('wms.backend.config.zones.form.warehouse', 'Warehouse'),
602
+ required: true,
603
+ loadOptions: loadWarehouseOptions,
604
+ allowCustomValues: false,
605
+ },
606
+ { id: 'code', type: 'text', label: t('wms.backend.config.zones.form.code', 'Code'), required: true },
607
+ { id: 'name', type: 'text', label: t('wms.backend.config.zones.form.name', 'Name'), required: true },
608
+ { id: 'priority', type: 'number', label: t('wms.backend.config.zones.form.priority', 'Priority') },
609
+ ], [t])
610
+
611
+ const columns = React.useMemo<ColumnDef<ZoneRow>[]>(() => [
612
+ {
613
+ accessorKey: 'name',
614
+ header: t('wms.backend.config.zones.columns.name', 'Zone'),
615
+ enableSorting: true,
616
+ cell: ({ row }) => row.original.name || row.original.code || row.original.id,
617
+ },
618
+ { accessorKey: 'code', header: t('wms.backend.config.zones.columns.code', 'Code'), enableSorting: true },
619
+ {
620
+ accessorKey: 'warehouse_name',
621
+ header: t('wms.backend.config.zones.columns.warehouse', 'Warehouse'),
622
+ enableSorting: false,
623
+ cell: ({ row }) =>
624
+ row.original.warehouse_name
625
+ || row.original.warehouse_code
626
+ || row.original.warehouse_id
627
+ || '—',
628
+ },
629
+ {
630
+ accessorKey: 'priority',
631
+ header: t('wms.backend.config.zones.columns.priority', 'Priority'),
632
+ enableSorting: true,
633
+ cell: ({ row }) => (row.original.priority == null ? '—' : String(row.original.priority)),
634
+ },
635
+ ], [t])
636
+
637
+ const initialValues = React.useMemo<ZoneFormValues>(() => {
638
+ if (dialog?.mode === 'edit') {
639
+ return {
640
+ warehouseId: dialog.row.warehouse_id || '',
641
+ code: dialog.row.code || '',
642
+ name: dialog.row.name || '',
643
+ priority: dialog.row.priority == null ? undefined : Number(dialog.row.priority),
644
+ }
645
+ }
646
+ return {
647
+ warehouseId: '',
648
+ code: '',
649
+ name: '',
650
+ priority: undefined,
651
+ }
652
+ }, [dialog])
653
+
654
+ const closeDialog = React.useCallback(() => {
655
+ setDialog(null)
656
+ setSubmitting(false)
657
+ }, [])
658
+
659
+ const refresh = React.useCallback(async () => {
660
+ await queryClient.invalidateQueries({ queryKey: ['wms-config', 'zones'] })
661
+ }, [queryClient])
662
+
663
+ const handleSubmit = React.useCallback(async (values: ZoneFormValues) => {
664
+ if (!dialog) return
665
+ const submitMode = dialog.mode
666
+ setSubmitting(true)
667
+ try {
668
+ const payload = {
669
+ ...values,
670
+ priority: values.priority === undefined || Number.isNaN(values.priority) ? undefined : Number(values.priority),
671
+ }
672
+ await runMutation({
673
+ operation: async () => {
674
+ const call = await apiCall(
675
+ '/api/wms/zones',
676
+ {
677
+ method: submitMode === 'edit' ? 'PUT' : 'POST',
678
+ body: JSON.stringify(submitMode === 'edit' ? { id: dialog.row.id, ...payload } : payload),
679
+ },
680
+ )
681
+ if (!call.ok) {
682
+ await raiseCrudError(call.response, t('wms.backend.config.zones.errors.save', 'Failed to save zone.'))
683
+ }
684
+ return call
685
+ },
686
+ context: {},
687
+ mutationPayload: submitMode === 'edit' ? { id: dialog.row.id, ...payload } : payload,
688
+ })
689
+ flash(
690
+ submitMode === 'edit'
691
+ ? t('wms.backend.config.zones.flash.updated', 'Zone updated')
692
+ : t('wms.backend.config.zones.flash.created', 'Zone created'),
693
+ 'success',
694
+ )
695
+ closeDialog()
696
+ await refresh()
697
+ } catch (error) {
698
+ flashMutationError(error, t('wms.backend.config.zones.errors.save', 'Failed to save zone.'), t)
699
+ setSubmitting(false)
700
+ }
701
+ }, [closeDialog, dialog, refresh, runMutation, t])
702
+
703
+ const handleDelete = React.useCallback(async (row: ZoneRow) => {
704
+ const confirmed = await confirm({
705
+ title: t('wms.backend.config.zones.confirmDelete', 'Archive zone "{name}"?', {
706
+ name: row.name || row.code || row.id,
707
+ }),
708
+ variant: 'destructive',
709
+ })
710
+ if (!confirmed) return
711
+
712
+ try {
713
+ await runMutation({
714
+ operation: async () => {
715
+ const call = await apiCall(`/api/wms/zones?id=${encodeURIComponent(row.id)}`, { method: 'DELETE' })
716
+ if (!call.ok) {
717
+ await raiseCrudError(call.response, t('wms.backend.config.zones.errors.delete', 'Failed to archive zone.'))
718
+ }
719
+ return call
720
+ },
721
+ context: {},
722
+ mutationPayload: { id: row.id },
723
+ })
724
+ flash(t('wms.backend.config.zones.flash.deleted', 'Zone archived'), 'success')
725
+ await refresh()
726
+ } catch (error) {
727
+ flashMutationError(error, t('wms.backend.config.zones.errors.delete', 'Failed to archive zone.'), t)
728
+ }
729
+ }, [confirm, refresh, runMutation, t])
730
+
731
+ return (
732
+ <>
733
+ <SectionCard
734
+ title={t('wms.backend.config.zones.title', 'Zones')}
735
+ description={t('wms.backend.config.zones.description', 'Group locations into functional zones (e.g. receiving, pick face, bulk) to drive routing and priority.')}
736
+ icon={<Layers className="size-5" />}
737
+ viewAllHref={viewAllHref}
738
+ viewAllLabel={viewAllHref ? t('wms.backend.config.viewAll', 'View all →') : undefined}
739
+ >
740
+ <DataTable
741
+ embedded
742
+ title={t('wms.backend.config.zones.title', 'Zones')}
743
+ columns={columns}
744
+ data={query.data?.items ?? []}
745
+ isLoading={query.isLoading}
746
+ error={query.isError ? t('wms.backend.config.zones.errors.load', 'Failed to load zones.') : null}
747
+ entityId={E.wms.warehouse_zone}
748
+ searchValue={search}
749
+ onSearchChange={(value) => {
750
+ setSearch(value)
751
+ setPage(1)
752
+ }}
753
+ searchPlaceholder={t('wms.backend.config.zones.search', 'Search zones')}
754
+ sorting={sorting}
755
+ onSortingChange={handleSortingChange}
756
+ sortable
757
+ manualSorting
758
+ actions={access.canManageZones ? (
759
+ <Button type="button" size="sm" onClick={() => setDialog({ mode: 'create' })}>
760
+ {t('wms.backend.config.actions.addZone', 'Add zone')}
761
+ </Button>
762
+ ) : null}
763
+ rowActions={(row) => (
764
+ <RowActions
765
+ items={[
766
+ { id: 'edit', label: t('common.edit', 'Edit'), onSelect: () => setDialog({ mode: 'edit', row }) },
767
+ { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
768
+ ]}
769
+ />
770
+ )}
771
+ pagination={{
772
+ page,
773
+ pageSize: 10,
774
+ total: query.data?.total ?? 0,
775
+ totalPages: query.data?.totalPages ?? 1,
776
+ onPageChange: setPage,
777
+ }}
778
+ perspective={{ tableId: 'wms.config.zones' }}
779
+ emptyState={(
780
+ <EmptyState
781
+ title={t('wms.backend.config.zones.empty.title', 'No zones')}
782
+ description={t('wms.backend.config.zones.empty.description', 'Zones group locations into functional areas to drive picking priority and routing rules.')}
783
+ action={access.canManageZones ? { label: t('wms.backend.config.actions.addZone', 'Add zone'), onClick: () => setDialog({ mode: 'create' }) } : undefined}
784
+ />
785
+ )}
786
+ />
787
+ </SectionCard>
788
+
789
+ <Dialog open={dialog !== null} onOpenChange={(next) => !next && closeDialog()}>
790
+ <DialogContent className="max-w-3xl">
791
+ <DialogHeader>
792
+ <DialogTitle>
793
+ {dialog?.mode === 'edit'
794
+ ? t('wms.backend.config.zones.dialog.edit', 'Edit zone')
795
+ : t('wms.backend.config.zones.dialog.create', 'Create zone')}
796
+ </DialogTitle>
797
+ </DialogHeader>
798
+ <CrudForm<ZoneFormValues>
799
+ schema={zoneFormSchema}
800
+ fields={fields}
801
+ entityId={E.wms.warehouse_zone}
802
+ initialValues={initialValues}
803
+ submitLabel={t('common.save', 'Save')}
804
+ onSubmit={handleSubmit}
805
+ embedded
806
+ isLoading={submitting}
807
+ twoColumn
808
+ />
809
+ </DialogContent>
810
+ </Dialog>
811
+ {ConfirmDialogElement}
812
+ </>
813
+ )
814
+ }
815
+
816
+ export function LocationSection({ viewAllHref }: ConfigSectionOptions = {}) {
817
+ const t = useT()
818
+ const router = useRouter()
819
+ const queryClient = useQueryClient()
820
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
821
+ const { runMutation } = useGuardedMutation<Record<string, unknown>>({ contextId: 'wms-config-locations' })
822
+ const access = useWmsInventoryMutationAccess()
823
+ const [page, setPage] = React.useState(1)
824
+ const [search, setSearch] = React.useState('')
825
+ const [sorting, setSorting] = React.useState<SortingState>([{ id: 'updatedAt', desc: true }])
826
+ const [dialog, setDialog] = React.useState<DialogMode<LocationRow> | null>(null)
827
+
828
+ const handleSortingChange = React.useCallback((nextSorting: SortingState) => {
829
+ setSorting(nextSorting)
830
+ setPage(1)
831
+ }, [])
832
+
833
+ const params = React.useMemo(() => {
834
+ const sortCol = sorting[0]
835
+ return buildQuery({
836
+ page,
837
+ pageSize: 10,
838
+ search: search.trim() || undefined,
839
+ sortField: sortCol ? sortCol.id : 'updatedAt',
840
+ sortDir: sortCol ? (sortCol.desc ? 'desc' : 'asc') : 'desc',
841
+ })
842
+ }, [page, search, sorting])
843
+
844
+ const query = useQuery({
845
+ queryKey: ['wms-config', 'locations', params],
846
+ queryFn: async () => {
847
+ const call = await apiCall<PagedResponse<LocationRow>>(`/api/wms/locations?${params}`)
848
+ if (!call.ok) {
849
+ await raiseCrudError(call.response, t('wms.backend.config.locations.errors.load', 'Failed to load locations.'))
850
+ }
851
+ return call.result ?? { items: [], total: 0, totalPages: 1 }
852
+ },
853
+ })
854
+
855
+ const columns = React.useMemo<ColumnDef<LocationRow>[]>(() => [
856
+ {
857
+ accessorKey: 'code',
858
+ header: t('wms.backend.config.locations.columns.code', 'Location'),
859
+ enableSorting: true,
860
+ cell: ({ row }) => {
861
+ const locationId = row.original.id?.trim()
862
+ const code = row.original.code || '—'
863
+ if (!locationId) return code
864
+ return (
865
+ <Link
866
+ href={`/backend/wms/location/${encodeURIComponent(locationId)}`}
867
+ className="font-medium text-primary hover:underline"
868
+ >
869
+ {code}
870
+ </Link>
871
+ )
872
+ },
873
+ },
874
+ { accessorKey: 'type', header: t('wms.backend.config.locations.columns.type', 'Type'), enableSorting: true, cell: ({ row }) => row.original.type || '—' },
875
+ {
876
+ accessorKey: 'warehouse_name',
877
+ header: t('wms.backend.config.locations.columns.warehouse', 'Warehouse'),
878
+ enableSorting: false,
879
+ cell: ({ row }) =>
880
+ row.original.warehouse_name
881
+ || row.original.warehouse_code
882
+ || row.original.warehouse_id
883
+ || '—',
884
+ },
885
+ { accessorKey: 'capacity_units', header: t('wms.backend.config.locations.columns.capacityUnits', 'Capacity units'), cell: ({ row }) => String(row.original.capacity_units ?? '—') },
886
+ {
887
+ accessorKey: 'is_active',
888
+ header: t('wms.backend.config.locations.columns.status', 'Status'),
889
+ cell: ({ row }) =>
890
+ row.original.is_active === false
891
+ ? t('wms.common.inactive', 'Inactive')
892
+ : t('wms.common.active', 'Active'),
893
+ },
894
+ ], [t])
895
+
896
+ const closeDialog = React.useCallback(() => {
897
+ setDialog(null)
898
+ }, [])
899
+
900
+ const refresh = React.useCallback(async () => {
901
+ await queryClient.invalidateQueries({ queryKey: ['wms-config', 'locations'] })
902
+ }, [queryClient])
903
+
904
+ const handleDelete = React.useCallback(async (row: LocationRow) => {
905
+ const confirmed = await confirm({
906
+ title: t('wms.backend.config.locations.confirmDelete', 'Archive location "{code}"?', {
907
+ code: row.code || row.id,
908
+ }),
909
+ variant: 'destructive',
910
+ })
911
+ if (!confirmed) return
912
+
913
+ try {
914
+ await runMutation({
915
+ operation: async () => {
916
+ const call = await apiCall(`/api/wms/locations?id=${encodeURIComponent(row.id)}`, { method: 'DELETE' })
917
+ if (!call.ok) {
918
+ await raiseCrudError(call.response, t('wms.backend.config.locations.errors.delete', 'Failed to archive location.'))
919
+ }
920
+ return call
921
+ },
922
+ context: {},
923
+ mutationPayload: { id: row.id },
924
+ })
925
+ flash(t('wms.backend.config.locations.flash.deleted', 'Location archived'), 'success')
926
+ await refresh()
927
+ } catch (error) {
928
+ flashMutationError(error, t('wms.backend.config.locations.errors.delete', 'Failed to archive location.'), t)
929
+ }
930
+ }, [confirm, refresh, runMutation, t])
931
+
932
+ return (
933
+ <>
934
+ <SectionCard
935
+ title={t('wms.backend.config.locations.title', 'Locations')}
936
+ description={t('wms.backend.config.locations.description', 'Maintain aisle/bin/dock level topology buckets that hold operational inventory.')}
937
+ icon={<MapPinned className="size-5" />}
938
+ viewAllHref={viewAllHref}
939
+ viewAllLabel={viewAllHref ? t('wms.backend.config.viewAll', 'View all →') : undefined}
940
+ >
941
+ <DataTable
942
+ embedded
943
+ title={t('wms.backend.config.locations.title', 'Locations')}
944
+ columns={columns}
945
+ data={query.data?.items ?? []}
946
+ isLoading={query.isLoading}
947
+ error={query.isError ? t('wms.backend.config.locations.errors.load', 'Failed to load locations.') : null}
948
+ entityId={E.wms.warehouse_location}
949
+ searchValue={search}
950
+ onSearchChange={(value) => {
951
+ setSearch(value)
952
+ setPage(1)
953
+ }}
954
+ searchPlaceholder={t('wms.backend.config.locations.search', 'Search locations')}
955
+ sorting={sorting}
956
+ onSortingChange={handleSortingChange}
957
+ sortable
958
+ manualSorting
959
+ actions={access.canManageLocations ? (
960
+ <Button type="button" size="sm" onClick={() => setDialog({ mode: 'create' })}>
961
+ {t('wms.backend.config.actions.addLocation', 'Add location')}
962
+ </Button>
963
+ ) : null}
964
+ onRowClick={(row) => {
965
+ const locationId = row.id?.trim()
966
+ if (locationId) {
967
+ router.push(`/backend/wms/location/${encodeURIComponent(locationId)}`)
968
+ }
969
+ }}
970
+ rowActions={(row) => (
971
+ <RowActions
972
+ items={[
973
+ { id: 'edit', label: t('common.edit', 'Edit'), onSelect: () => setDialog({ mode: 'edit', row }) },
974
+ { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
975
+ ]}
976
+ />
977
+ )}
978
+ pagination={{
979
+ page,
980
+ pageSize: 10,
981
+ total: query.data?.total ?? 0,
982
+ totalPages: query.data?.totalPages ?? 1,
983
+ onPageChange: setPage,
984
+ }}
985
+ perspective={{ tableId: 'wms.config.locations' }}
986
+ emptyState={(
987
+ <EmptyState
988
+ title={t('wms.backend.config.locations.empty.title', 'No locations')}
989
+ description={t('wms.backend.config.locations.empty.description', 'Create locations to define the buckets used by balances, reservations, and movement ledger rows.')}
990
+ action={access.canManageLocations ? { label: t('wms.backend.config.actions.addLocation', 'Add location'), onClick: () => setDialog({ mode: 'create' }) } : undefined}
991
+ />
992
+ )}
993
+ />
994
+ </SectionCard>
995
+
996
+ <LocationEditDialog
997
+ open={dialog !== null}
998
+ onOpenChange={(next) => !next && closeDialog()}
999
+ mode={dialog?.mode ?? 'create'}
1000
+ row={dialog?.mode === 'edit' ? dialog.row : null}
1001
+ onSaved={refresh}
1002
+ />
1003
+ {ConfirmDialogElement}
1004
+ </>
1005
+ )
1006
+ }
1007
+
1008
+ export function InventoryProfilesSection() {
1009
+ const t = useT()
1010
+ const locale = useLocale()
1011
+ const queryClient = useQueryClient()
1012
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
1013
+ const { runMutation } = useGuardedMutation<Record<string, unknown>>({ contextId: 'wms-config-profiles' })
1014
+ const access = useWmsInventoryMutationAccess()
1015
+ const [page, setPage] = React.useState(1)
1016
+ const [sorting, setSorting] = React.useState<SortingState>([{ id: 'updatedAt', desc: true }])
1017
+ const [submitting, setSubmitting] = React.useState(false)
1018
+ const [dialog, setDialog] = React.useState<DialogMode<InventoryProfileRow> | null>(null)
1019
+ const inventoryProfileFormSchema = React.useMemo(
1020
+ () => buildInventoryProfileFormSchema(t('wms.backend.config.profiles.validation.fefoRequired', 'FEFO is required when expiration tracking is enabled.')),
1021
+ [t],
1022
+ )
1023
+
1024
+ const handleSortingChange = React.useCallback((nextSorting: SortingState) => {
1025
+ setSorting(nextSorting)
1026
+ setPage(1)
1027
+ }, [])
1028
+
1029
+ const params = React.useMemo(() => {
1030
+ const sortCol = sorting[0]
1031
+ return buildQuery({
1032
+ page,
1033
+ pageSize: 10,
1034
+ sortField: sortCol ? sortCol.id : 'updatedAt',
1035
+ sortDir: sortCol ? (sortCol.desc ? 'desc' : 'asc') : 'desc',
1036
+ })
1037
+ }, [page, sorting])
1038
+
1039
+ const query = useQuery({
1040
+ queryKey: ['wms-config', 'inventory-profiles', params],
1041
+ queryFn: async () => {
1042
+ const call = await apiCall<PagedResponse<InventoryProfileRow>>(`/api/wms/inventory-profiles?${params}`)
1043
+ if (!call.ok) {
1044
+ await raiseCrudError(call.response, t('wms.backend.config.profiles.errors.load', 'Failed to load inventory profiles.'))
1045
+ }
1046
+ return call.result ?? { items: [], total: 0, totalPages: 1 }
1047
+ },
1048
+ })
1049
+
1050
+ const fields = React.useMemo<CrudField[]>(() => [
1051
+ {
1052
+ id: 'catalogProductId',
1053
+ type: 'combobox',
1054
+ label: t('wms.backend.config.profiles.form.product', 'Product'),
1055
+ required: true,
1056
+ loadOptions: loadCatalogProductOptions,
1057
+ allowCustomValues: false,
1058
+ },
1059
+ {
1060
+ id: 'catalogVariantId',
1061
+ type: 'combobox',
1062
+ label: t('wms.backend.config.profiles.form.variant', 'Variant'),
1063
+ loadOptions: loadCatalogVariantOptions,
1064
+ allowCustomValues: false,
1065
+ },
1066
+ { id: 'defaultUom', type: 'text', label: t('wms.backend.config.profiles.form.uom', 'Default UOM'), required: true },
1067
+ {
1068
+ id: 'defaultStrategy',
1069
+ type: 'select',
1070
+ label: t('wms.backend.config.profiles.form.strategy', 'Rotation strategy'),
1071
+ required: true,
1072
+ options: STRATEGY_OPTIONS,
1073
+ },
1074
+ { id: 'reorderPoint', type: 'number', label: t('wms.backend.config.profiles.form.reorderPoint', 'Reorder point') },
1075
+ { id: 'safetyStock', type: 'number', label: t('wms.backend.config.profiles.form.safetyStock', 'Safety stock') },
1076
+ { id: 'trackLot', type: 'checkbox', label: t('wms.backend.config.profiles.form.trackLot', 'Track lots') },
1077
+ { id: 'trackSerial', type: 'checkbox', label: t('wms.backend.config.profiles.form.trackSerial', 'Track serials') },
1078
+ { id: 'trackExpiration', type: 'checkbox', label: t('wms.backend.config.profiles.form.trackExpiration', 'Track expiration') },
1079
+ ], [t])
1080
+
1081
+ const quantityFormatter = React.useMemo(
1082
+ () => createInventoryQuantityFormatter(locale),
1083
+ [locale],
1084
+ )
1085
+
1086
+ const columns = React.useMemo<ColumnDef<InventoryProfileRow>[]>(() => [
1087
+ {
1088
+ accessorKey: 'catalog_product_id',
1089
+ header: t('wms.backend.config.profiles.columns.product', 'Product'),
1090
+ cell: ({ row }) => formatCatalogProductLabel(row.original),
1091
+ },
1092
+ {
1093
+ accessorKey: 'catalog_variant_id',
1094
+ header: t('wms.backend.config.profiles.columns.variant', 'Variant'),
1095
+ cell: ({ row }) => {
1096
+ if (!row.original.catalog_variant_id?.trim()) {
1097
+ return t('wms.backend.config.profiles.allVariants', 'All variants')
1098
+ }
1099
+ return formatCatalogVariantLabel(row.original)
1100
+ },
1101
+ },
1102
+ { accessorKey: 'default_uom', header: t('wms.backend.config.profiles.columns.uom', 'UOM'), cell: ({ row }) => row.original.default_uom || '—' },
1103
+ {
1104
+ accessorKey: 'default_strategy',
1105
+ id: 'defaultStrategy',
1106
+ header: t('wms.backend.config.profiles.columns.strategy', 'Strategy'),
1107
+ enableSorting: true,
1108
+ cell: ({ row }) => {
1109
+ const strategy = row.original.default_strategy?.trim()
1110
+ if (!strategy) return '—'
1111
+ return inventoryRotationStrategyLabel(strategy, t)
1112
+ },
1113
+ },
1114
+ {
1115
+ accessorKey: 'reorder_point',
1116
+ id: 'reorderPoint',
1117
+ header: t('wms.backend.config.profiles.columns.reorderPoint', 'Reorder point'),
1118
+ enableSorting: true,
1119
+ cell: ({ row }) => (
1120
+ <span className="tabular-nums">
1121
+ {formatInventoryQuantity(row.original.reorder_point, quantityFormatter)}
1122
+ </span>
1123
+ ),
1124
+ },
1125
+ {
1126
+ accessorKey: 'safety_stock',
1127
+ id: 'safetyStock',
1128
+ header: t('wms.backend.config.profiles.columns.safetyStock', 'Safety stock'),
1129
+ enableSorting: true,
1130
+ cell: ({ row }) => (
1131
+ <span className="tabular-nums">
1132
+ {formatInventoryQuantity(row.original.safety_stock, quantityFormatter)}
1133
+ </span>
1134
+ ),
1135
+ },
1136
+ ], [quantityFormatter, t])
1137
+
1138
+ const initialValues = React.useMemo<InventoryProfileFormValues>(() => {
1139
+ if (dialog?.mode === 'edit') {
1140
+ return {
1141
+ catalogProductId: dialog.row.catalog_product_id || '',
1142
+ catalogVariantId: dialog.row.catalog_variant_id || '',
1143
+ defaultUom: dialog.row.default_uom || '',
1144
+ defaultStrategy: (dialog.row.default_strategy as InventoryProfileFormValues['defaultStrategy']) || 'fifo',
1145
+ trackLot: dialog.row.track_lot === true,
1146
+ trackSerial: dialog.row.track_serial === true,
1147
+ trackExpiration: dialog.row.track_expiration === true,
1148
+ reorderPoint: dialog.row.reorder_point == null ? undefined : Number(dialog.row.reorder_point),
1149
+ safetyStock: dialog.row.safety_stock == null ? undefined : Number(dialog.row.safety_stock),
1150
+ }
1151
+ }
1152
+ return {
1153
+ catalogProductId: '',
1154
+ catalogVariantId: '',
1155
+ defaultUom: 'pcs',
1156
+ defaultStrategy: 'fifo',
1157
+ trackLot: false,
1158
+ trackSerial: false,
1159
+ trackExpiration: false,
1160
+ reorderPoint: 0,
1161
+ safetyStock: 0,
1162
+ }
1163
+ }, [dialog])
1164
+
1165
+ const closeDialog = React.useCallback(() => {
1166
+ setDialog(null)
1167
+ setSubmitting(false)
1168
+ }, [])
1169
+
1170
+ const refresh = React.useCallback(async () => {
1171
+ await queryClient.invalidateQueries({ queryKey: ['wms-config', 'inventory-profiles'] })
1172
+ }, [queryClient])
1173
+
1174
+ const handleSubmit = React.useCallback(async (values: InventoryProfileFormValues) => {
1175
+ setSubmitting(true)
1176
+ try {
1177
+ const payload = {
1178
+ ...values,
1179
+ catalogVariantId: values.catalogVariantId?.trim() ? values.catalogVariantId : null,
1180
+ }
1181
+ await runMutation({
1182
+ operation: async () => {
1183
+ const call = await apiCall(
1184
+ '/api/wms/inventory-profiles',
1185
+ {
1186
+ method: dialog?.mode === 'edit' ? 'PUT' : 'POST',
1187
+ body: JSON.stringify(dialog?.mode === 'edit' ? { id: dialog.row.id, ...payload } : payload),
1188
+ },
1189
+ )
1190
+ if (!call.ok) {
1191
+ await raiseCrudError(call.response, t('wms.backend.config.profiles.errors.save', 'Failed to save inventory profile.'))
1192
+ }
1193
+ return call
1194
+ },
1195
+ context: {},
1196
+ mutationPayload: dialog?.mode === 'edit' ? { id: dialog.row.id, ...payload } : payload,
1197
+ })
1198
+ flash(
1199
+ dialog?.mode === 'edit'
1200
+ ? t('wms.backend.config.profiles.flash.updated', 'Inventory profile updated')
1201
+ : t('wms.backend.config.profiles.flash.created', 'Inventory profile created'),
1202
+ 'success',
1203
+ )
1204
+ closeDialog()
1205
+ await refresh()
1206
+ } catch (error) {
1207
+ flashMutationError(error, t('wms.backend.config.profiles.errors.save', 'Failed to save inventory profile.'), t)
1208
+ setSubmitting(false)
1209
+ }
1210
+ }, [closeDialog, dialog, refresh, runMutation, t])
1211
+
1212
+ const handleDelete = React.useCallback(async (row: InventoryProfileRow) => {
1213
+ const confirmed = await confirm({
1214
+ title: t('wms.backend.config.profiles.confirmDelete', 'Archive inventory profile "{id}"?', {
1215
+ id: row.id,
1216
+ }),
1217
+ variant: 'destructive',
1218
+ })
1219
+ if (!confirmed) return
1220
+
1221
+ try {
1222
+ await runMutation({
1223
+ operation: async () => {
1224
+ const call = await apiCall(`/api/wms/inventory-profiles?id=${encodeURIComponent(row.id)}`, { method: 'DELETE' })
1225
+ if (!call.ok) {
1226
+ await raiseCrudError(call.response, t('wms.backend.config.profiles.errors.delete', 'Failed to archive inventory profile.'))
1227
+ }
1228
+ return call
1229
+ },
1230
+ context: {},
1231
+ mutationPayload: { id: row.id },
1232
+ })
1233
+ flash(t('wms.backend.config.profiles.flash.deleted', 'Inventory profile archived'), 'success')
1234
+ await refresh()
1235
+ } catch (error) {
1236
+ flashMutationError(error, t('wms.backend.config.profiles.errors.delete', 'Failed to archive inventory profile.'), t)
1237
+ }
1238
+ }, [confirm, refresh, runMutation, t])
1239
+
1240
+ return (
1241
+ <>
1242
+ <SectionCard
1243
+ title={t('wms.backend.config.profiles.title', 'Inventory profiles')}
1244
+ description={t('wms.backend.config.profiles.description', 'Configure tracking strategy, reorder thresholds, and lot/serial behavior per product scope.')}
1245
+ icon={<Boxes className="size-5" />}
1246
+ >
1247
+ <DataTable
1248
+ embedded
1249
+ title={t('wms.backend.config.profiles.title', 'Inventory profiles')}
1250
+ columns={columns}
1251
+ data={query.data?.items ?? []}
1252
+ isLoading={query.isLoading}
1253
+ error={query.isError ? t('wms.backend.config.profiles.errors.load', 'Failed to load inventory profiles.') : null}
1254
+ entityId={E.wms.product_inventory_profile}
1255
+ sorting={sorting}
1256
+ onSortingChange={handleSortingChange}
1257
+ sortable
1258
+ manualSorting
1259
+ actions={access.canManage ? (
1260
+ <Button type="button" size="sm" onClick={() => setDialog({ mode: 'create' })}>
1261
+ {t('wms.backend.config.actions.addProfile', 'Add profile')}
1262
+ </Button>
1263
+ ) : null}
1264
+ rowActions={(row) => (
1265
+ <RowActions
1266
+ items={[
1267
+ { id: 'edit', label: t('common.edit', 'Edit'), onSelect: () => setDialog({ mode: 'edit', row }) },
1268
+ { id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onSelect: () => { void handleDelete(row) } },
1269
+ ]}
1270
+ />
1271
+ )}
1272
+ pagination={{
1273
+ page,
1274
+ pageSize: 10,
1275
+ total: query.data?.total ?? 0,
1276
+ totalPages: query.data?.totalPages ?? 1,
1277
+ onPageChange: setPage,
1278
+ }}
1279
+ perspective={{ tableId: 'wms.config.inventoryProfiles' }}
1280
+ emptyState={(
1281
+ <EmptyState
1282
+ title={t('wms.backend.config.profiles.empty.title', 'No inventory profiles')}
1283
+ description={t('wms.backend.config.profiles.empty.description', 'Create profiles to control reservation strategy, lot/serial tracking, and low-stock thresholds.')}
1284
+ action={access.canManage ? { label: t('wms.backend.config.actions.addProfile', 'Add profile'), onClick: () => setDialog({ mode: 'create' }) } : undefined}
1285
+ />
1286
+ )}
1287
+ />
1288
+ </SectionCard>
1289
+
1290
+ <Dialog open={dialog !== null} onOpenChange={(next) => !next && closeDialog()}>
1291
+ <DialogContent className="max-w-3xl">
1292
+ <DialogHeader>
1293
+ <DialogTitle>
1294
+ {dialog?.mode === 'edit'
1295
+ ? t('wms.backend.config.profiles.dialog.edit', 'Edit inventory profile')
1296
+ : t('wms.backend.config.profiles.dialog.create', 'Create inventory profile')}
1297
+ </DialogTitle>
1298
+ </DialogHeader>
1299
+ <CrudForm<InventoryProfileFormValues>
1300
+ schema={inventoryProfileFormSchema}
1301
+ fields={fields}
1302
+ entityId={E.wms.product_inventory_profile}
1303
+ initialValues={initialValues}
1304
+ submitLabel={t('common.save', 'Save')}
1305
+ onSubmit={handleSubmit}
1306
+ embedded
1307
+ isLoading={submitting}
1308
+ twoColumn
1309
+ />
1310
+ </DialogContent>
1311
+ </Dialog>
1312
+ {ConfirmDialogElement}
1313
+ </>
1314
+ )
1315
+ }
1316
+
1317
+ export default function WmsConfigurationPage() {
1318
+ const t = useT()
1319
+
1320
+ return (
1321
+ <Page>
1322
+ <PageBody>
1323
+ <div className="space-y-6">
1324
+ <section className="rounded-lg border bg-card p-5 text-card-foreground shadow-sm">
1325
+ <div className="space-y-1">
1326
+ <h1 className="text-2xl font-bold tracking-tight">{t('wms.backend.config.title', 'WMS configuration')}</h1>
1327
+ <p className="text-sm text-muted-foreground">
1328
+ {t('wms.backend.config.description', 'Phase-1 operational configuration for warehouses, storage locations, and inventory tracking policies.')}
1329
+ </p>
1330
+ </div>
1331
+ </section>
1332
+ <WarehouseSection viewAllHref="/backend/wms/warehouses" />
1333
+ <ZoneSection viewAllHref="/backend/wms/zones" />
1334
+ <LocationSection viewAllHref="/backend/wms/locations" />
1335
+ <InventoryProfilesSection />
1336
+ </div>
1337
+ </PageBody>
1338
+ </Page>
1339
+ )
1340
+ }