@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,958 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import Link from 'next/link'
5
+ import { useQuery } from '@tanstack/react-query'
6
+ import type { ColumnDef } from '@tanstack/react-table'
7
+ import {
8
+ ArrowDown,
9
+ ClipboardList,
10
+ ExternalLink,
11
+ MapPin,
12
+ Package,
13
+ RefreshCw,
14
+ SlidersHorizontal,
15
+ Warehouse as WarehouseIcon2,
16
+ } from 'lucide-react'
17
+ import { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'
18
+ import { BarChart } from '@open-mercato/ui/backend/charts'
19
+ import { DataTable } from '@open-mercato/ui/backend/DataTable'
20
+ import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
21
+ import { RowActions } from '@open-mercato/ui/backend/RowActions'
22
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
23
+ import { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'
24
+ import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
25
+ import { Button } from '@open-mercato/ui/primitives/button'
26
+ import { LinkButton } from '@open-mercato/ui/primitives/link-button'
27
+ import {
28
+ Select,
29
+ SelectContent,
30
+ SelectItem,
31
+ SelectTrigger,
32
+ SelectValue,
33
+ } from '@open-mercato/ui/primitives/select'
34
+ import { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'
35
+ import type {
36
+ OperationalDashboardActivityRow,
37
+ OperationalDashboardExpiryLotRow,
38
+ OperationalDashboardKpi,
39
+ OperationalDashboardPayload,
40
+ } from '../../lib/loadOperationalDashboard'
41
+ import {
42
+ inventoryMovementReasonLabel,
43
+ type InventoryDisplayTranslator,
44
+ } from '../../lib/inventoryDisplayUi'
45
+ import { AdjustInventoryDialog } from './AdjustInventoryDialog'
46
+ import { ChangeLotStatusDialog } from './ChangeLotStatusDialog'
47
+ import { CycleCountWizardDialog } from './CycleCountWizardDialog'
48
+ import { MoveInventoryDialog } from './MoveInventoryDialog'
49
+ import { ReceiveInventoryDialog } from './ReceiveInventoryDialog'
50
+ import { useWmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'
51
+
52
+ type WarehouseOption = {
53
+ id: string
54
+ name?: string | null
55
+ code?: string | null
56
+ }
57
+
58
+ type PagedWarehouses = {
59
+ items: WarehouseOption[]
60
+ }
61
+
62
+ const AUTO_REFRESH_MS = 60_000
63
+
64
+ function Sparkline({ values, className }: { values: number[]; className?: string }) {
65
+ const width = 160
66
+ const height = 36
67
+ const max = Math.max(...values, 1)
68
+ const points = values.map((value, index) => {
69
+ const x = values.length <= 1 ? width / 2 : (index / (values.length - 1)) * width
70
+ const y = height - (value / max) * (height - 4) - 2
71
+ return `${x},${y}`
72
+ })
73
+
74
+ return (
75
+ <svg
76
+ viewBox={`0 0 ${width} ${height}`}
77
+ className={className}
78
+ aria-hidden="true"
79
+ preserveAspectRatio="none"
80
+ >
81
+ <polyline
82
+ fill="none"
83
+ stroke="currentColor"
84
+ strokeWidth="1.5"
85
+ strokeLinecap="round"
86
+ strokeLinejoin="round"
87
+ points={points.join(' ')}
88
+ className="text-status-info-icon"
89
+ />
90
+ </svg>
91
+ )
92
+ }
93
+
94
+ function formatActivityTitle(
95
+ row: OperationalDashboardActivityRow,
96
+ t: ReturnType<typeof useT>,
97
+ ): string {
98
+ const sku = row.variantSku?.trim() || row.variantId
99
+ const quantity = Math.abs(row.quantity)
100
+ const signedQuantity = row.quantity
101
+ switch (row.movementType) {
102
+ case 'receipt':
103
+ case 'return_receive':
104
+ return t('wms.backend.dashboard.activity.titles.received', 'Received {quantity}× {sku}', {
105
+ quantity,
106
+ sku,
107
+ })
108
+ case 'adjust':
109
+ return t('wms.backend.dashboard.activity.titles.adjusted', 'Adjusted {quantity}× {sku}', {
110
+ quantity: `${signedQuantity >= 0 ? '+' : ''}${signedQuantity}`,
111
+ sku,
112
+ })
113
+ case 'transfer':
114
+ return t('wms.backend.dashboard.activity.titles.moved', 'Moved {quantity}× {sku}', {
115
+ quantity,
116
+ sku,
117
+ })
118
+ case 'pick':
119
+ case 'pack':
120
+ return t('wms.backend.dashboard.activity.titles.allocated', 'Allocated {quantity}× {sku}', {
121
+ quantity,
122
+ sku,
123
+ })
124
+ case 'cycle_count':
125
+ return t('wms.backend.dashboard.activity.titles.reconciled', 'Inventory reconciled — {sku}', {
126
+ sku,
127
+ })
128
+ default:
129
+ return t('wms.backend.dashboard.activity.titles.generic', '{type} {quantity}× {sku}', {
130
+ type: row.movementType,
131
+ quantity,
132
+ sku,
133
+ })
134
+ }
135
+ }
136
+
137
+ function formatActivitySubtitle(
138
+ row: OperationalDashboardActivityRow,
139
+ t: InventoryDisplayTranslator,
140
+ ): string | null {
141
+ const reasonLabel = inventoryMovementReasonLabel(
142
+ {
143
+ reasonCode: row.reasonCode,
144
+ reason: row.reason,
145
+ movementType: row.movementType,
146
+ },
147
+ t,
148
+ )
149
+ if (reasonLabel) return reasonLabel
150
+ if (row.referenceType && row.referenceId) return `${row.referenceType} · ${row.referenceId}`
151
+ return null
152
+ }
153
+
154
+ function createDateTimeFormatter(locale: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {
155
+ return new Intl.DateTimeFormat(locale, options)
156
+ }
157
+
158
+ export function resolveDashboardFirstRunMode(input: {
159
+ warehousesLoading: boolean
160
+ warehousesError: boolean
161
+ hasWarehouses: boolean
162
+ locationsLoading: boolean
163
+ locationsError: boolean
164
+ locationsCount: number
165
+ }): 'no-warehouses' | 'no-locations' | null {
166
+ if (input.warehousesLoading || input.warehousesError) return null
167
+ if (!input.hasWarehouses) return 'no-warehouses'
168
+ if (input.locationsLoading || input.locationsError) return null
169
+ if (input.locationsCount === 0) return 'no-locations'
170
+ return null
171
+ }
172
+
173
+ type DashboardKpiCardProps = {
174
+ kpi: OperationalDashboardKpi
175
+ title: string
176
+ caption: string
177
+ badgeLabel: string | null
178
+ badgeVariant: StatusBadgeVariant
179
+ ctaLabel: string
180
+ href: string
181
+ }
182
+
183
+ function DashboardKpiCard({
184
+ kpi,
185
+ title,
186
+ caption,
187
+ badgeLabel,
188
+ badgeVariant,
189
+ ctaLabel,
190
+ href,
191
+ }: DashboardKpiCardProps) {
192
+ return (
193
+ <section className="flex min-h-52 flex-col rounded-lg border bg-card p-5 text-card-foreground shadow-sm">
194
+ <p className="text-sm font-medium">{title}</p>
195
+ <p className="mt-3 text-xs text-muted-foreground">{caption}</p>
196
+ <div className="mt-2 flex items-end gap-3">
197
+ <p className="text-3xl font-semibold tracking-tight">{kpi.count}</p>
198
+ {badgeLabel ? (
199
+ <StatusBadge variant={badgeVariant} dot>
200
+ {badgeLabel}
201
+ </StatusBadge>
202
+ ) : null}
203
+ </div>
204
+ <Sparkline values={kpi.sparkline} className="mt-3 h-9 w-full max-w-40" />
205
+ <LinkButton asChild variant="primary" size="sm" className="mt-4 w-fit">
206
+ <Link href={href}>{ctaLabel}</Link>
207
+ </LinkButton>
208
+ </section>
209
+ )
210
+ }
211
+
212
+ type ExpiryLotListProps = {
213
+ title: string
214
+ emptyLabel: string
215
+ viewAllLabel: string
216
+ viewAllHref: string
217
+ rows: OperationalDashboardExpiryLotRow[]
218
+ expiryFormatter: Intl.DateTimeFormat
219
+ quantityFormatter: Intl.NumberFormat
220
+ canAdjust: boolean
221
+ onChangeStatus: (row: OperationalDashboardExpiryLotRow) => void
222
+ onMove: (row: OperationalDashboardExpiryLotRow) => void
223
+ t: ReturnType<typeof useT>
224
+ }
225
+
226
+ function ExpiryLotList({
227
+ title,
228
+ emptyLabel,
229
+ viewAllLabel,
230
+ viewAllHref,
231
+ rows,
232
+ expiryFormatter,
233
+ quantityFormatter,
234
+ canAdjust,
235
+ onChangeStatus,
236
+ onMove,
237
+ t,
238
+ }: ExpiryLotListProps) {
239
+ return (
240
+ <div className="min-w-0">
241
+ <div className="mb-3 flex items-center justify-between gap-3">
242
+ <h3 className="text-sm font-semibold">{title}</h3>
243
+ <LinkButton asChild variant="gray" size="sm" className="h-8 px-2">
244
+ <Link href={viewAllHref}>{viewAllLabel}</Link>
245
+ </LinkButton>
246
+ </div>
247
+ {rows.length === 0 ? (
248
+ <p className="text-sm text-muted-foreground">{emptyLabel}</p>
249
+ ) : (
250
+ <ul className="divide-y divide-border">
251
+ {rows.map((row) => {
252
+ const badgeVariant: StatusBadgeVariant = row.category === 'pastDue' ? 'error' : 'warning'
253
+ return (
254
+ <li key={row.id} className="flex items-start justify-between gap-3 py-3 first:pt-0 last:pb-0">
255
+ <div className="min-w-0">
256
+ <p className="truncate text-sm font-medium">{row.lotNumber}</p>
257
+ <p className="truncate text-xs text-muted-foreground">{row.sku}</p>
258
+ <div className="mt-1 flex flex-wrap items-center gap-2">
259
+ <StatusBadge variant={badgeVariant} dot>
260
+ {expiryFormatter.format(new Date(row.expiresAt))}
261
+ </StatusBadge>
262
+ <span className="text-xs text-muted-foreground">
263
+ {t(
264
+ 'wms.backend.dashboard.expiry.available',
265
+ '{quantity} available',
266
+ { quantity: quantityFormatter.format(row.availableQuantity) },
267
+ )}
268
+ </span>
269
+ </div>
270
+ </div>
271
+ <div className="flex shrink-0 items-center gap-1">
272
+ {canAdjust ? (
273
+ <RowActions
274
+ items={[
275
+ {
276
+ id: 'change-status',
277
+ label: t('wms.backend.lots.actions.changeStatus', 'Change status'),
278
+ onSelect: () => onChangeStatus(row),
279
+ },
280
+ {
281
+ id: 'move',
282
+ label: t('wms.backend.lots.actions.move', 'Move'),
283
+ onSelect: () => onMove(row),
284
+ },
285
+ ]}
286
+ />
287
+ ) : null}
288
+ <Button asChild type="button" variant="ghost" size="icon" className="size-7 shrink-0">
289
+ <Link
290
+ href={`/backend/wms/lot/${row.id}`}
291
+ aria-label={t('wms.backend.dashboard.expiry.openLot', 'Open lot')}
292
+ >
293
+ <ExternalLink className="size-3.5" />
294
+ </Link>
295
+ </Button>
296
+ </div>
297
+ </li>
298
+ )
299
+ })}
300
+ </ul>
301
+ )}
302
+ </div>
303
+ )
304
+ }
305
+
306
+ const movementStatusMap: Record<string, StatusBadgeVariant> = {
307
+ receipt: 'success',
308
+ return_receive: 'success',
309
+ adjust: 'warning',
310
+ transfer: 'info',
311
+ pick: 'info',
312
+ pack: 'info',
313
+ cycle_count: 'neutral',
314
+ putaway: 'info',
315
+ ship: 'success',
316
+ }
317
+
318
+ export default function WmsOperationalDashboardPage() {
319
+ const t = useT()
320
+ const locale = useLocale()
321
+ const access = useWmsInventoryMutationAccess()
322
+ const [warehouseId, setWarehouseId] = React.useState<string>('all')
323
+ const [adjustOpen, setAdjustOpen] = React.useState(false)
324
+ const [cycleOpen, setCycleOpen] = React.useState(false)
325
+ const [changeStatusOpen, setChangeStatusOpen] = React.useState(false)
326
+ const [moveOpen, setMoveOpen] = React.useState(false)
327
+ const [receiveOpen, setReceiveOpen] = React.useState(false)
328
+ const [activeExpiryLot, setActiveExpiryLot] = React.useState<OperationalDashboardExpiryLotRow | null>(null)
329
+
330
+ const openExpiryChangeStatus = React.useCallback((row: OperationalDashboardExpiryLotRow) => {
331
+ setActiveExpiryLot(row)
332
+ setChangeStatusOpen(true)
333
+ }, [])
334
+
335
+ const openExpiryMove = React.useCallback((row: OperationalDashboardExpiryLotRow) => {
336
+ setActiveExpiryLot(row)
337
+ setMoveOpen(true)
338
+ }, [])
339
+
340
+ const activityTimeFormatter = React.useMemo(
341
+ () =>
342
+ createDateTimeFormatter(locale, {
343
+ month: '2-digit',
344
+ day: '2-digit',
345
+ hour: '2-digit',
346
+ minute: '2-digit',
347
+ }),
348
+ [locale],
349
+ )
350
+
351
+ const lastUpdatedFormatter = React.useMemo(
352
+ () =>
353
+ createDateTimeFormatter(locale, {
354
+ hour: '2-digit',
355
+ minute: '2-digit',
356
+ }),
357
+ [locale],
358
+ )
359
+
360
+ const expiryDateFormatter = React.useMemo(
361
+ () =>
362
+ createDateTimeFormatter(locale, {
363
+ month: '2-digit',
364
+ day: '2-digit',
365
+ year: '2-digit',
366
+ }),
367
+ [locale],
368
+ )
369
+
370
+ const quantityFormatter = React.useMemo(
371
+ () => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }),
372
+ [locale],
373
+ )
374
+
375
+ const warehousesQuery = useQuery({
376
+ queryKey: ['wms-dashboard', 'warehouses'],
377
+ queryFn: async () => {
378
+ const params = new URLSearchParams({ page: '1', pageSize: '100', sortField: 'name', sortDir: 'asc' })
379
+ const call = await apiCall<PagedWarehouses>(`/api/wms/warehouses?${params.toString()}`)
380
+ if (!call.ok) {
381
+ await raiseCrudError(call.response, t('wms.backend.dashboard.errors.warehouses', 'Failed to load warehouses.'))
382
+ }
383
+ return call.result?.items ?? []
384
+ },
385
+ })
386
+
387
+ const hasWarehouses = (warehousesQuery.data ?? []).length > 0
388
+
389
+ const locationsCountQuery = useQuery({
390
+ queryKey: ['wms-dashboard', 'locations-count'],
391
+ queryFn: async () => {
392
+ const params = new URLSearchParams({ page: '1', pageSize: '1' })
393
+ const call = await apiCall<{ total?: number }>(`/api/wms/locations?${params.toString()}`)
394
+ if (!call.ok) {
395
+ await raiseCrudError(call.response, t('wms.backend.dashboard.errors.locations', 'Failed to load locations.'))
396
+ }
397
+ return call.result?.total ?? 0
398
+ },
399
+ enabled: hasWarehouses,
400
+ })
401
+
402
+ const dashboardQuery = useQuery({
403
+ queryKey: ['wms-dashboard', 'operational', warehouseId],
404
+ queryFn: async () => {
405
+ const params = new URLSearchParams()
406
+ if (warehouseId !== 'all') params.set('warehouseId', warehouseId)
407
+ const call = await apiCall<OperationalDashboardPayload>(
408
+ `/api/wms/dashboard/operational?${params.toString()}`,
409
+ )
410
+ if (!call.ok) {
411
+ await raiseCrudError(call.response, t('wms.backend.dashboard.errors.load', 'Failed to load dashboard.'))
412
+ }
413
+ return call.result as OperationalDashboardPayload
414
+ },
415
+ refetchInterval: AUTO_REFRESH_MS,
416
+ })
417
+
418
+ const selectedWarehouse = warehousesQuery.data?.find((warehouse) => warehouse.id === warehouseId)
419
+ const warehouseLabel =
420
+ warehouseId === 'all'
421
+ ? t('wms.backend.dashboard.filters.allWarehouses', 'All warehouses')
422
+ : selectedWarehouse?.name || selectedWarehouse?.code || warehouseId
423
+
424
+ const firstRunMode = React.useMemo<'no-warehouses' | 'no-locations' | null>(
425
+ () =>
426
+ resolveDashboardFirstRunMode({
427
+ warehousesLoading: warehousesQuery.isLoading,
428
+ warehousesError: warehousesQuery.isError,
429
+ hasWarehouses,
430
+ locationsLoading: locationsCountQuery.isLoading,
431
+ locationsError: locationsCountQuery.isError,
432
+ locationsCount: locationsCountQuery.data ?? 0,
433
+ }),
434
+ [
435
+ warehousesQuery.isLoading,
436
+ warehousesQuery.isError,
437
+ hasWarehouses,
438
+ locationsCountQuery.isLoading,
439
+ locationsCountQuery.isError,
440
+ locationsCountQuery.data,
441
+ ],
442
+ )
443
+
444
+ const kpiConfig = React.useMemo(() => {
445
+ const buildLotsHref = (expiryWindow: 'expiringSoon' | 'pastDue') => {
446
+ const params = new URLSearchParams({ expiryWindow })
447
+ if (warehouseId !== 'all') params.set('warehouseId', warehouseId)
448
+ return `/backend/wms/lots?${params.toString()}`
449
+ }
450
+ const buildInventoryHref = (lowStock?: 'belowReorder' | 'belowSafety') => {
451
+ const params = new URLSearchParams()
452
+ if (warehouseId !== 'all') params.set('warehouseId', warehouseId)
453
+ if (lowStock) params.set('lowStock', lowStock)
454
+ const query = params.toString()
455
+ return query ? `/backend/wms/inventory?${query}` : '/backend/wms/inventory'
456
+ }
457
+ const inventoryHref = buildInventoryHref()
458
+ const reservationsHref = warehouseId === 'all'
459
+ ? '/backend/wms/reservations'
460
+ : `/backend/wms/reservations?warehouseId=${encodeURIComponent(warehouseId)}`
461
+ const movementsHref = warehouseId === 'all'
462
+ ? '/backend/wms/movements'
463
+ : `/backend/wms/movements?warehouseId=${encodeURIComponent(warehouseId)}`
464
+
465
+ return {
466
+ lowStock: {
467
+ title: t('wms.backend.dashboard.kpis.lowStock.title', 'Low stock'),
468
+ caption: t('wms.backend.dashboard.kpis.lowStock.caption', 'Below reorder point'),
469
+ ctaLabel: t('wms.backend.dashboard.kpis.lowStock.cta', 'View low stock'),
470
+ href: buildInventoryHref('belowReorder'),
471
+ resolveBadge: (kpi: OperationalDashboardKpi) => ({
472
+ variant: 'warning' as const,
473
+ label: t('wms.backend.dashboard.kpis.lowStock.badgeActive', '{count} active', { count: kpi.count }),
474
+ }),
475
+ },
476
+ reorderCritical: {
477
+ title: t('wms.backend.dashboard.kpis.reorderCritical.title', 'Reorder critical'),
478
+ caption: t('wms.backend.dashboard.kpis.reorderCritical.caption', 'Below safety stock'),
479
+ ctaLabel: t('wms.backend.dashboard.kpis.reorderCritical.cta', 'View critical'),
480
+ href: buildInventoryHref('belowSafety'),
481
+ resolveBadge: (kpi: OperationalDashboardKpi) => ({
482
+ variant: 'error' as const,
483
+ label: t('wms.backend.dashboard.kpis.reorderCritical.badge', '{count} critical', { count: kpi.count }),
484
+ }),
485
+ },
486
+ expiringSoon: {
487
+ title: t('wms.backend.dashboard.kpis.expiringSoon.title', 'Expiring soon'),
488
+ caption: t('wms.backend.dashboard.kpis.expiringSoon.caption', 'Lots expiring in 30 days'),
489
+ ctaLabel: t('wms.backend.dashboard.kpis.expiringSoon.cta', 'View expiry'),
490
+ href: buildLotsHref('expiringSoon'),
491
+ resolveBadge: (kpi: OperationalDashboardKpi) => ({
492
+ variant: 'warning' as const,
493
+ label: t('wms.backend.dashboard.kpis.expiringSoon.badge', '{count} lots', { count: kpi.count }),
494
+ }),
495
+ },
496
+ pastDue: {
497
+ title: t('wms.backend.dashboard.kpis.pastDue.title', 'Past due'),
498
+ caption: t('wms.backend.dashboard.kpis.pastDue.caption', 'Expired lots with on-hand stock'),
499
+ ctaLabel: t('wms.backend.dashboard.kpis.pastDue.cta', 'View past due'),
500
+ href: buildLotsHref('pastDue'),
501
+ resolveBadge: (kpi: OperationalDashboardKpi) => ({
502
+ variant: 'error' as const,
503
+ label: t('wms.backend.dashboard.kpis.pastDue.badge', '{count} lots', { count: kpi.count }),
504
+ }),
505
+ },
506
+ agingReservations: {
507
+ title: t('wms.backend.dashboard.kpis.agingReservations.title', 'Aging reservations'),
508
+ caption: t('wms.backend.dashboard.kpis.agingReservations.caption', 'Active holds older than 7 days'),
509
+ ctaLabel: t('wms.backend.dashboard.kpis.agingReservations.cta', 'View reservations'),
510
+ href: reservationsHref,
511
+ resolveBadge: (kpi: OperationalDashboardKpi) => ({
512
+ variant: 'warning' as const,
513
+ label: t('wms.backend.dashboard.kpis.agingReservations.badge', '{count} aging', { count: kpi.count }),
514
+ }),
515
+ },
516
+ todaysMoves: {
517
+ title: t('wms.backend.dashboard.kpis.todaysMoves.title', "Today's moves"),
518
+ caption: t('wms.backend.dashboard.kpis.todaysMoves.caption', 'Movements posted today'),
519
+ ctaLabel: t('wms.backend.dashboard.kpis.todaysMoves.cta', 'View ledger'),
520
+ href: movementsHref,
521
+ resolveBadge: (kpi: OperationalDashboardKpi) => {
522
+ if (kpi.deltaSinceYesterday === null) {
523
+ return { variant: 'neutral' as const, label: null }
524
+ }
525
+ return {
526
+ variant: kpi.deltaSinceYesterday >= 0 ? ('success' as const) : ('neutral' as const),
527
+ label:
528
+ kpi.deltaSinceYesterday >= 0
529
+ ? t('wms.backend.dashboard.kpis.todaysMoves.badgeUp', '+{count} vs yesterday', {
530
+ count: kpi.deltaSinceYesterday,
531
+ })
532
+ : t('wms.backend.dashboard.kpis.todaysMoves.badgeDown', '{count} vs yesterday', {
533
+ count: kpi.deltaSinceYesterday,
534
+ }),
535
+ }
536
+ },
537
+ },
538
+ }
539
+ }, [t, warehouseId])
540
+
541
+ const monthlyTrendData = React.useMemo(
542
+ () =>
543
+ (dashboardQuery.data?.monthlyTrends ?? []).map((point) => ({
544
+ month: point.month,
545
+ receive: point.receive,
546
+ allocate: point.allocate,
547
+ })),
548
+ [dashboardQuery.data?.monthlyTrends],
549
+ )
550
+
551
+ const expiryLotsByCategory = React.useMemo(() => {
552
+ const lots = dashboardQuery.data?.expiryLots ?? []
553
+ return {
554
+ expiringSoon: lots.filter((lot) => lot.category === 'expiringSoon'),
555
+ pastDue: lots.filter((lot) => lot.category === 'pastDue'),
556
+ }
557
+ }, [dashboardQuery.data?.expiryLots])
558
+
559
+ const movementsHref = kpiConfig.todaysMoves.href
560
+
561
+ const activityColumns = React.useMemo<ColumnDef<OperationalDashboardActivityRow>[]>(
562
+ () => [
563
+ {
564
+ id: 'event',
565
+ header: t('wms.backend.dashboard.activity.columns.event', 'Event'),
566
+ accessorKey: 'movementType',
567
+ cell: ({ row }) => {
568
+ const movementLabel = t(
569
+ `wms.backend.dashboard.activity.types.${row.original.movementType}`,
570
+ row.original.movementType,
571
+ )
572
+ const badgeVariant = movementStatusMap[row.original.movementType] ?? 'neutral'
573
+ return (
574
+ <StatusBadge variant={badgeVariant} dot>
575
+ {movementLabel}
576
+ </StatusBadge>
577
+ )
578
+ },
579
+ meta: { maxWidth: '7rem' },
580
+ },
581
+ {
582
+ id: 'details',
583
+ header: t('wms.backend.dashboard.activity.columns.details', 'Details'),
584
+ cell: ({ row }) => {
585
+ const subtitle = formatActivitySubtitle(row.original, t)
586
+ return (
587
+ <div className="min-w-0">
588
+ <p className="truncate text-sm font-medium">{formatActivityTitle(row.original, t)}</p>
589
+ {subtitle ? (
590
+ <p className="truncate text-sm text-muted-foreground">{subtitle}</p>
591
+ ) : null}
592
+ </div>
593
+ )
594
+ },
595
+ },
596
+ {
597
+ id: 'location',
598
+ header: t('wms.backend.dashboard.activity.columns.location', 'Location'),
599
+ accessorKey: 'locationLabel',
600
+ cell: ({ row }) => (
601
+ <p className="truncate text-sm text-muted-foreground">{row.original.locationLabel}</p>
602
+ ),
603
+ meta: { maxWidth: '11rem', truncate: true },
604
+ },
605
+ {
606
+ id: 'time',
607
+ header: t('wms.backend.dashboard.activity.columns.time', 'Time'),
608
+ accessorKey: 'performedAt',
609
+ cell: ({ row }) => (
610
+ <p className="text-xs leading-4 text-muted-foreground">
611
+ {activityTimeFormatter.format(new Date(row.original.performedAt))}
612
+ </p>
613
+ ),
614
+ meta: { maxWidth: '5rem' },
615
+ },
616
+ {
617
+ id: 'actions',
618
+ header: t('wms.backend.dashboard.activity.columns.actions', 'Actions'),
619
+ cell: ({ row }) => (
620
+ <Button asChild type="button" variant="ghost" size="icon" className="size-7">
621
+ <Link
622
+ href={`${movementsHref}#${row.original.id}`}
623
+ aria-label={t('wms.backend.dashboard.activity.open', 'Open movement')}
624
+ >
625
+ <ExternalLink className="size-3.5" />
626
+ </Link>
627
+ </Button>
628
+ ),
629
+ meta: { maxWidth: '2.5rem' },
630
+ },
631
+ ],
632
+ [activityTimeFormatter, movementsHref, t],
633
+ )
634
+
635
+ const subtitle = dashboardQuery.data?.lastUpdatedAt
636
+ ? t('wms.backend.dashboard.subtitle', 'Live activity for today · auto-refresh 60s · last update {time}', {
637
+ time: lastUpdatedFormatter.format(new Date(dashboardQuery.data.lastUpdatedAt)),
638
+ })
639
+ : t('wms.backend.dashboard.subtitleLoading', 'Live activity for today · auto-refresh 60s')
640
+
641
+ return (
642
+ <Page>
643
+ <PageBody className="space-y-6">
644
+ <PageHeader
645
+ title={t('wms.backend.dashboard.title', 'Operational dashboard')}
646
+ description={subtitle}
647
+ actions={(
648
+ <>
649
+ <Select value={warehouseId} onValueChange={setWarehouseId}>
650
+ <SelectTrigger className="w-full max-w-xs sm:w-56">
651
+ <SelectValue placeholder={warehouseLabel} />
652
+ </SelectTrigger>
653
+ <SelectContent>
654
+ <SelectItem value="all">
655
+ {t('wms.backend.dashboard.filters.allWarehouses', 'All warehouses')}
656
+ </SelectItem>
657
+ {(warehousesQuery.data ?? []).map((warehouse) => (
658
+ <SelectItem key={warehouse.id} value={warehouse.id}>
659
+ {warehouse.name || warehouse.code || warehouse.id}
660
+ </SelectItem>
661
+ ))}
662
+ </SelectContent>
663
+ </Select>
664
+ <Button
665
+ type="button"
666
+ variant="outline"
667
+ onClick={() => { void dashboardQuery.refetch() }}
668
+ disabled={dashboardQuery.isFetching}
669
+ >
670
+ <RefreshCw className={`size-4 ${dashboardQuery.isFetching ? 'animate-spin' : ''}`} />
671
+ {t('wms.backend.dashboard.actions.refresh', 'Refresh')}
672
+ </Button>
673
+ </>
674
+ )}
675
+ />
676
+
677
+ {dashboardQuery.isLoading ? (
678
+ <LoadingMessage label={t('wms.backend.dashboard.loading', 'Loading dashboard…')} />
679
+ ) : null}
680
+
681
+ {dashboardQuery.isError ? (
682
+ <ErrorMessage
683
+ label={t('wms.backend.dashboard.errors.load', 'Failed to load dashboard.')}
684
+ action={(
685
+ <Button type="button" variant="outline" size="sm" onClick={() => { void dashboardQuery.refetch() }}>
686
+ {t('wms.backend.dashboard.actions.refresh', 'Refresh')}
687
+ </Button>
688
+ )}
689
+ />
690
+ ) : null}
691
+
692
+ {firstRunMode === 'no-warehouses' ? (
693
+ <section className="rounded-lg border-2 border-dashed border-border bg-card p-8 text-center shadow-sm">
694
+ <div className="mx-auto max-w-sm">
695
+ <div className="mx-auto mb-4 flex size-14 items-center justify-center rounded-full bg-muted">
696
+ <WarehouseIcon2 className="size-7 text-muted-foreground" />
697
+ </div>
698
+ <h2 className="text-lg font-semibold">
699
+ {t('wms.backend.dashboard.firstRun.title', 'Set up your warehouse')}
700
+ </h2>
701
+ <p className="mt-2 text-sm text-muted-foreground">
702
+ {t(
703
+ 'wms.backend.dashboard.firstRun.description',
704
+ 'Create a warehouse, add storage locations, then receive your first stock to start tracking inventory.',
705
+ )}
706
+ </p>
707
+ <div className="mt-6 flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
708
+ <LinkButton asChild variant="primary" size="sm">
709
+ <Link href="/backend/config/wms">
710
+ <WarehouseIcon2 className="size-4" />
711
+ {t('wms.backend.dashboard.firstRun.actions.createWarehouse', 'Create warehouse')}
712
+ </Link>
713
+ </LinkButton>
714
+ <LinkButton asChild variant="gray" size="sm">
715
+ <Link href="/backend/config/wms">
716
+ <MapPin className="size-4" />
717
+ {t('wms.backend.dashboard.firstRun.actions.addLocations', 'Add locations')}
718
+ </Link>
719
+ </LinkButton>
720
+ {access.canReceive ? (
721
+ <Button type="button" variant="outline" size="sm" onClick={() => setReceiveOpen(true)}>
722
+ <ArrowDown className="size-4" />
723
+ {t('wms.backend.dashboard.firstRun.actions.receiveStock', 'Receive first stock')}
724
+ </Button>
725
+ ) : null}
726
+ </div>
727
+ </div>
728
+ </section>
729
+ ) : null}
730
+
731
+ {firstRunMode === 'no-locations' ? (
732
+ <section className="rounded-lg border-2 border-dashed border-border bg-card p-8 text-center shadow-sm">
733
+ <div className="mx-auto max-w-sm">
734
+ <div className="mx-auto mb-4 flex size-14 items-center justify-center rounded-full bg-muted">
735
+ <MapPin className="size-7 text-muted-foreground" />
736
+ </div>
737
+ <h2 className="text-lg font-semibold">
738
+ {t('wms.backend.dashboard.firstRun.noLocations.title', 'Add storage locations')}
739
+ </h2>
740
+ <p className="mt-2 text-sm text-muted-foreground">
741
+ {t(
742
+ 'wms.backend.dashboard.firstRun.noLocations.description',
743
+ 'Your warehouse is set up — add at least one storage location before you can receive stock.',
744
+ )}
745
+ </p>
746
+ <div className="mt-6 flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
747
+ <LinkButton asChild variant="primary" size="sm">
748
+ <Link href="/backend/config/wms">
749
+ <MapPin className="size-4" />
750
+ {t('wms.backend.dashboard.firstRun.actions.addLocations', 'Add locations')}
751
+ </Link>
752
+ </LinkButton>
753
+ </div>
754
+ </div>
755
+ </section>
756
+ ) : null}
757
+
758
+ {dashboardQuery.data ? (
759
+ <>
760
+ <section className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
761
+ {dashboardQuery.data.kpis.map((kpi) => {
762
+ const config = kpiConfig[kpi.id]
763
+ const badge = config.resolveBadge(kpi)
764
+ return (
765
+ <DashboardKpiCard
766
+ key={kpi.id}
767
+ kpi={kpi}
768
+ title={config.title}
769
+ caption={config.caption}
770
+ badgeLabel={badge.label}
771
+ badgeVariant={badge.variant}
772
+ ctaLabel={config.ctaLabel}
773
+ href={config.href}
774
+ />
775
+ )
776
+ })}
777
+ </section>
778
+
779
+ <section className="rounded-lg border bg-card shadow-sm">
780
+ <div className="border-b px-5 py-4">
781
+ <h2 className="text-base font-semibold">
782
+ {t('wms.backend.dashboard.expiry.title', 'Expiry watch')}
783
+ </h2>
784
+ <p className="mt-1 text-sm text-muted-foreground">
785
+ {t(
786
+ 'wms.backend.dashboard.expiry.description',
787
+ 'Upcoming and overdue lots with on-hand stock in the selected warehouse scope.',
788
+ )}
789
+ </p>
790
+ </div>
791
+ <div className="grid gap-6 p-5 md:grid-cols-2">
792
+ <ExpiryLotList
793
+ title={t('wms.backend.dashboard.expiry.expiringSoon.title', 'Expiring soon')}
794
+ emptyLabel={t(
795
+ 'wms.backend.dashboard.expiry.expiringSoon.empty',
796
+ 'No lots expiring within the next 30 days.',
797
+ )}
798
+ viewAllLabel={t(
799
+ 'wms.backend.dashboard.expiry.expiringSoon.viewAll',
800
+ 'View all expiring →',
801
+ )}
802
+ viewAllHref={kpiConfig.expiringSoon.href}
803
+ rows={expiryLotsByCategory.expiringSoon}
804
+ expiryFormatter={expiryDateFormatter}
805
+ quantityFormatter={quantityFormatter}
806
+ canAdjust={access.canAdjust}
807
+ onChangeStatus={openExpiryChangeStatus}
808
+ onMove={openExpiryMove}
809
+ t={t}
810
+ />
811
+ <ExpiryLotList
812
+ title={t('wms.backend.dashboard.expiry.pastDue.title', 'Past due')}
813
+ emptyLabel={t(
814
+ 'wms.backend.dashboard.expiry.pastDue.empty',
815
+ 'No expired lots with available stock.',
816
+ )}
817
+ viewAllLabel={t(
818
+ 'wms.backend.dashboard.expiry.pastDue.viewAll',
819
+ 'View all past due →',
820
+ )}
821
+ viewAllHref={kpiConfig.pastDue.href}
822
+ rows={expiryLotsByCategory.pastDue}
823
+ expiryFormatter={expiryDateFormatter}
824
+ quantityFormatter={quantityFormatter}
825
+ canAdjust={access.canAdjust}
826
+ onChangeStatus={openExpiryChangeStatus}
827
+ onMove={openExpiryMove}
828
+ t={t}
829
+ />
830
+ </div>
831
+ </section>
832
+
833
+ <section className="rounded-lg border bg-card shadow-sm">
834
+ <div className="flex items-center justify-between gap-3 border-b px-5 py-4">
835
+ <h2 className="text-base font-semibold">
836
+ {t('wms.backend.dashboard.trends.title', 'Monthly trends')}
837
+ </h2>
838
+ <div className="flex items-center gap-4 text-xs text-muted-foreground">
839
+ <span className="inline-flex items-center gap-2">
840
+ <span className="size-2.5 rounded-sm bg-chart-1" aria-hidden="true" />
841
+ {t('wms.backend.dashboard.trends.receive', 'Receive')}
842
+ </span>
843
+ <span className="inline-flex items-center gap-2">
844
+ <span className="size-2.5 rounded-sm bg-chart-2" aria-hidden="true" />
845
+ {t('wms.backend.dashboard.trends.allocate', 'Allocate')}
846
+ </span>
847
+ </div>
848
+ </div>
849
+ <div className="p-5">
850
+ <BarChart
851
+ data={monthlyTrendData}
852
+ index="month"
853
+ categories={['receive', 'allocate']}
854
+ showLegend={false}
855
+ categoryLabels={{
856
+ receive: t('wms.backend.dashboard.trends.receive', 'Receive'),
857
+ allocate: t('wms.backend.dashboard.trends.allocate', 'Allocate'),
858
+ }}
859
+ emptyMessage={t('wms.backend.dashboard.trends.empty', 'No movement trends yet.')}
860
+ className="border-0 bg-transparent p-0 shadow-none"
861
+ />
862
+ </div>
863
+ </section>
864
+
865
+ <DataTable<OperationalDashboardActivityRow>
866
+ title={t('wms.backend.dashboard.activity.title', 'Recent activity')}
867
+ columns={activityColumns}
868
+ data={dashboardQuery.data.recentActivity}
869
+ disableRowClick
870
+ emptyState={t('wms.backend.dashboard.activity.empty', 'No recent movements yet.')}
871
+ actions={(
872
+ <Button asChild type="button" variant="ghost" size="sm">
873
+ <Link href={movementsHref}>
874
+ {t('wms.backend.dashboard.activity.viewAll', 'View all movements →')}
875
+ </Link>
876
+ </Button>
877
+ )}
878
+ />
879
+
880
+ <section className="flex flex-col gap-4 rounded-lg border bg-card px-5 py-4 shadow-sm lg:flex-row lg:items-center lg:justify-between">
881
+ <div>
882
+ <h2 className="text-base font-semibold">
883
+ {t('wms.backend.dashboard.quickActions.title', 'Quick actions')}
884
+ </h2>
885
+ <p className="text-sm text-muted-foreground">
886
+ {t(
887
+ 'wms.backend.dashboard.quickActions.description',
888
+ 'Open a drawer without leaving the dashboard',
889
+ )}
890
+ </p>
891
+ </div>
892
+ <div className="flex flex-wrap gap-2">
893
+ <Button asChild type="button" variant="outline">
894
+ <Link href={kpiConfig.lowStock.href}>
895
+ <Package className="size-4" />
896
+ {t('wms.backend.dashboard.quickActions.inventory', 'Open inventory console')}
897
+ </Link>
898
+ </Button>
899
+ {access.canAdjust ? (
900
+ <Button type="button" variant="outline" onClick={() => setAdjustOpen(true)}>
901
+ <SlidersHorizontal className="size-4" />
902
+ {t('wms.backend.dashboard.quickActions.adjust', 'Adjust inventory')}
903
+ </Button>
904
+ ) : null}
905
+ {access.canCycleCount ? (
906
+ <Button type="button" variant="outline" onClick={() => setCycleOpen(true)}>
907
+ <ClipboardList className="size-4" />
908
+ {t('wms.backend.dashboard.quickActions.cycleCount', 'Cycle count')}
909
+ </Button>
910
+ ) : null}
911
+ <Button asChild type="button" variant="outline">
912
+ <Link href={movementsHref}>
913
+ {t('wms.backend.dashboard.quickActions.movements', 'View movements')}
914
+ </Link>
915
+ </Button>
916
+ </div>
917
+ </section>
918
+ </>
919
+ ) : null}
920
+ </PageBody>
921
+
922
+ {access.canReceive ? (
923
+ <ReceiveInventoryDialog open={receiveOpen} onOpenChange={setReceiveOpen} access={access} />
924
+ ) : null}
925
+ {access.canAdjust ? (
926
+ <AdjustInventoryDialog open={adjustOpen} onOpenChange={setAdjustOpen} access={access} />
927
+ ) : null}
928
+ {access.canCycleCount ? (
929
+ <CycleCountWizardDialog open={cycleOpen} onOpenChange={setCycleOpen} access={access} />
930
+ ) : null}
931
+ {access.canAdjust && activeExpiryLot ? (
932
+ <>
933
+ <ChangeLotStatusDialog
934
+ open={changeStatusOpen}
935
+ onOpenChange={setChangeStatusOpen}
936
+ access={access}
937
+ lotId={activeExpiryLot.id}
938
+ currentStatus={activeExpiryLot.status}
939
+ lotUpdatedAt={activeExpiryLot.updatedAt}
940
+ onSuccess={() => {
941
+ void dashboardQuery.refetch()
942
+ }}
943
+ />
944
+ <MoveInventoryDialog
945
+ open={moveOpen}
946
+ onOpenChange={setMoveOpen}
947
+ access={access}
948
+ initialCatalogVariantId={activeExpiryLot.catalogVariantId}
949
+ initialWarehouseId={warehouseId === 'all' ? undefined : warehouseId}
950
+ initialLotId={activeExpiryLot.id}
951
+ initialAvailable={activeExpiryLot.availableQuantity}
952
+ lockSourceContext
953
+ />
954
+ </>
955
+ ) : null}
956
+ </Page>
957
+ )
958
+ }