@endora-commerce/mod-inventory 0.100.0

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 (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/admin/api/warehouses-client.d.ts +22 -0
  4. package/dist/admin/api/warehouses-client.d.ts.map +1 -0
  5. package/dist/admin/api/warehouses-client.js +32 -0
  6. package/dist/admin/api/warehouses-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +43 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +190 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/AvailabilityNotificationsPage.d.ts +15 -0
  12. package/dist/admin/pages/AvailabilityNotificationsPage.d.ts.map +1 -0
  13. package/dist/admin/pages/AvailabilityNotificationsPage.js +111 -0
  14. package/dist/admin/pages/AvailabilityNotificationsPage.js.map +1 -0
  15. package/dist/admin/pages/InventoryPage.d.ts +17 -0
  16. package/dist/admin/pages/InventoryPage.d.ts.map +1 -0
  17. package/dist/admin/pages/InventoryPage.js +135 -0
  18. package/dist/admin/pages/InventoryPage.js.map +1 -0
  19. package/dist/admin/pages/LowStockPage.d.ts +16 -0
  20. package/dist/admin/pages/LowStockPage.d.ts.map +1 -0
  21. package/dist/admin/pages/LowStockPage.js +61 -0
  22. package/dist/admin/pages/LowStockPage.js.map +1 -0
  23. package/dist/admin/pages/StockImportWizard.d.ts +17 -0
  24. package/dist/admin/pages/StockImportWizard.d.ts.map +1 -0
  25. package/dist/admin/pages/StockImportWizard.js +111 -0
  26. package/dist/admin/pages/StockImportWizard.js.map +1 -0
  27. package/dist/admin/pages/WarehouseEditor.d.ts +12 -0
  28. package/dist/admin/pages/WarehouseEditor.d.ts.map +1 -0
  29. package/dist/admin/pages/WarehouseEditor.js +194 -0
  30. package/dist/admin/pages/WarehouseEditor.js.map +1 -0
  31. package/dist/admin/pages/WarehousesList.d.ts +12 -0
  32. package/dist/admin/pages/WarehousesList.d.ts.map +1 -0
  33. package/dist/admin/pages/WarehousesList.js +69 -0
  34. package/dist/admin/pages/WarehousesList.js.map +1 -0
  35. package/dist/admin/zones/ChannelWarehouses.d.ts +49 -0
  36. package/dist/admin/zones/ChannelWarehouses.d.ts.map +1 -0
  37. package/dist/admin/zones/ChannelWarehouses.js +109 -0
  38. package/dist/admin/zones/ChannelWarehouses.js.map +1 -0
  39. package/dist/backend/demo/reset.d.ts +18 -0
  40. package/dist/backend/demo/reset.d.ts.map +1 -0
  41. package/dist/backend/demo/reset.js +20 -0
  42. package/dist/backend/demo/reset.js.map +1 -0
  43. package/dist/backend/demo/rows.d.ts +38 -0
  44. package/dist/backend/demo/rows.d.ts.map +1 -0
  45. package/dist/backend/demo/rows.js +38 -0
  46. package/dist/backend/demo/rows.js.map +1 -0
  47. package/dist/backend/demo/seed.d.ts +26 -0
  48. package/dist/backend/demo/seed.d.ts.map +1 -0
  49. package/dist/backend/demo/seed.js +65 -0
  50. package/dist/backend/demo/seed.js.map +1 -0
  51. package/dist/backend/email-templates/transactional-defaults.d.ts +11 -0
  52. package/dist/backend/email-templates/transactional-defaults.d.ts.map +1 -0
  53. package/dist/backend/email-templates/transactional-defaults.js +49 -0
  54. package/dist/backend/email-templates/transactional-defaults.js.map +1 -0
  55. package/dist/backend/entities/availability-notification.entity.d.ts +49 -0
  56. package/dist/backend/entities/availability-notification.entity.d.ts.map +1 -0
  57. package/dist/backend/entities/availability-notification.entity.js +93 -0
  58. package/dist/backend/entities/availability-notification.entity.js.map +1 -0
  59. package/dist/backend/entities/inventory-threshold.entity.d.ts +24 -0
  60. package/dist/backend/entities/inventory-threshold.entity.d.ts.map +1 -0
  61. package/dist/backend/entities/inventory-threshold.entity.js +61 -0
  62. package/dist/backend/entities/inventory-threshold.entity.js.map +1 -0
  63. package/dist/backend/entities/product-warehouse-low-stock-threshold.entity.d.ts +15 -0
  64. package/dist/backend/entities/product-warehouse-low-stock-threshold.entity.d.ts.map +1 -0
  65. package/dist/backend/entities/product-warehouse-low-stock-threshold.entity.js +51 -0
  66. package/dist/backend/entities/product-warehouse-low-stock-threshold.entity.js.map +1 -0
  67. package/dist/backend/entities/stock-allocation.entity.d.ts +22 -0
  68. package/dist/backend/entities/stock-allocation.entity.d.ts.map +1 -0
  69. package/dist/backend/entities/stock-allocation.entity.js +69 -0
  70. package/dist/backend/entities/stock-allocation.entity.js.map +1 -0
  71. package/dist/backend/entities/stock-level.entity.d.ts +24 -0
  72. package/dist/backend/entities/stock-level.entity.d.ts.map +1 -0
  73. package/dist/backend/entities/stock-level.entity.js +74 -0
  74. package/dist/backend/entities/stock-level.entity.js.map +1 -0
  75. package/dist/backend/entities/warehouse-channel-assignment.entity.d.ts +17 -0
  76. package/dist/backend/entities/warehouse-channel-assignment.entity.d.ts.map +1 -0
  77. package/dist/backend/entities/warehouse-channel-assignment.entity.js +60 -0
  78. package/dist/backend/entities/warehouse-channel-assignment.entity.js.map +1 -0
  79. package/dist/backend/entities/warehouse.entity.d.ts +37 -0
  80. package/dist/backend/entities/warehouse.entity.d.ts.map +1 -0
  81. package/dist/backend/entities/warehouse.entity.js +90 -0
  82. package/dist/backend/entities/warehouse.entity.js.map +1 -0
  83. package/dist/backend/index.d.ts +139 -0
  84. package/dist/backend/index.d.ts.map +1 -0
  85. package/dist/backend/index.js +351 -0
  86. package/dist/backend/index.js.map +1 -0
  87. package/dist/backend/plugin.d.ts +113 -0
  88. package/dist/backend/plugin.d.ts.map +1 -0
  89. package/dist/backend/plugin.js +75 -0
  90. package/dist/backend/plugin.js.map +1 -0
  91. package/dist/backend/prompt-tools.d.ts +23 -0
  92. package/dist/backend/prompt-tools.d.ts.map +1 -0
  93. package/dist/backend/prompt-tools.js +77 -0
  94. package/dist/backend/prompt-tools.js.map +1 -0
  95. package/dist/backend/routes.admin.d.ts +62 -0
  96. package/dist/backend/routes.admin.d.ts.map +1 -0
  97. package/dist/backend/routes.admin.js +383 -0
  98. package/dist/backend/routes.admin.js.map +1 -0
  99. package/dist/backend/routes.d.ts +40 -0
  100. package/dist/backend/routes.d.ts.map +1 -0
  101. package/dist/backend/routes.js +263 -0
  102. package/dist/backend/routes.js.map +1 -0
  103. package/dist/backend/services/audit-references.d.ts +15 -0
  104. package/dist/backend/services/audit-references.d.ts.map +1 -0
  105. package/dist/backend/services/audit-references.js +27 -0
  106. package/dist/backend/services/audit-references.js.map +1 -0
  107. package/dist/backend/services/availability-notification-service.d.ts +155 -0
  108. package/dist/backend/services/availability-notification-service.d.ts.map +1 -0
  109. package/dist/backend/services/availability-notification-service.js +363 -0
  110. package/dist/backend/services/availability-notification-service.js.map +1 -0
  111. package/dist/backend/services/availability-worker.d.ts +57 -0
  112. package/dist/backend/services/availability-worker.d.ts.map +1 -0
  113. package/dist/backend/services/availability-worker.js +135 -0
  114. package/dist/backend/services/availability-worker.js.map +1 -0
  115. package/dist/backend/services/csv-stock-importer.d.ts +52 -0
  116. package/dist/backend/services/csv-stock-importer.d.ts.map +1 -0
  117. package/dist/backend/services/csv-stock-importer.js +154 -0
  118. package/dist/backend/services/csv-stock-importer.js.map +1 -0
  119. package/dist/backend/services/display-band-resolver.d.ts +19 -0
  120. package/dist/backend/services/display-band-resolver.d.ts.map +1 -0
  121. package/dist/backend/services/display-band-resolver.js +21 -0
  122. package/dist/backend/services/display-band-resolver.js.map +1 -0
  123. package/dist/backend/services/effective-fulfilment-strategy.d.ts +16 -0
  124. package/dist/backend/services/effective-fulfilment-strategy.d.ts.map +1 -0
  125. package/dist/backend/services/effective-fulfilment-strategy.js +16 -0
  126. package/dist/backend/services/effective-fulfilment-strategy.js.map +1 -0
  127. package/dist/backend/services/fulfilment-strategy-resolver.d.ts +13 -0
  128. package/dist/backend/services/fulfilment-strategy-resolver.d.ts.map +1 -0
  129. package/dist/backend/services/fulfilment-strategy-resolver.js +13 -0
  130. package/dist/backend/services/fulfilment-strategy-resolver.js.map +1 -0
  131. package/dist/backend/services/inventory-read-port.d.ts +65 -0
  132. package/dist/backend/services/inventory-read-port.d.ts.map +1 -0
  133. package/dist/backend/services/inventory-read-port.js +168 -0
  134. package/dist/backend/services/inventory-read-port.js.map +1 -0
  135. package/dist/backend/services/inventory-reservation-apply-port.d.ts +62 -0
  136. package/dist/backend/services/inventory-reservation-apply-port.d.ts.map +1 -0
  137. package/dist/backend/services/inventory-reservation-apply-port.js +134 -0
  138. package/dist/backend/services/inventory-reservation-apply-port.js.map +1 -0
  139. package/dist/backend/services/low-stock-alert-service.d.ts +87 -0
  140. package/dist/backend/services/low-stock-alert-service.d.ts.map +1 -0
  141. package/dist/backend/services/low-stock-alert-service.js +164 -0
  142. package/dist/backend/services/low-stock-alert-service.js.map +1 -0
  143. package/dist/backend/services/product-threshold-write.service.d.ts +38 -0
  144. package/dist/backend/services/product-threshold-write.service.d.ts.map +1 -0
  145. package/dist/backend/services/product-threshold-write.service.js +80 -0
  146. package/dist/backend/services/product-threshold-write.service.js.map +1 -0
  147. package/dist/backend/services/stock-import.service.d.ts +43 -0
  148. package/dist/backend/services/stock-import.service.d.ts.map +1 -0
  149. package/dist/backend/services/stock-import.service.js +124 -0
  150. package/dist/backend/services/stock-import.service.js.map +1 -0
  151. package/dist/backend/services/stock-level-service.d.ts +146 -0
  152. package/dist/backend/services/stock-level-service.d.ts.map +1 -0
  153. package/dist/backend/services/stock-level-service.js +489 -0
  154. package/dist/backend/services/stock-level-service.js.map +1 -0
  155. package/dist/backend/services/threshold-admin-service.d.ts +73 -0
  156. package/dist/backend/services/threshold-admin-service.d.ts.map +1 -0
  157. package/dist/backend/services/threshold-admin-service.js +167 -0
  158. package/dist/backend/services/threshold-admin-service.js.map +1 -0
  159. package/dist/backend/services/threshold-resolver.d.ts +23 -0
  160. package/dist/backend/services/threshold-resolver.d.ts.map +1 -0
  161. package/dist/backend/services/threshold-resolver.js +29 -0
  162. package/dist/backend/services/threshold-resolver.js.map +1 -0
  163. package/dist/backend/services/threshold-settings-mirror.d.ts +55 -0
  164. package/dist/backend/services/threshold-settings-mirror.d.ts.map +1 -0
  165. package/dist/backend/services/threshold-settings-mirror.js +84 -0
  166. package/dist/backend/services/threshold-settings-mirror.js.map +1 -0
  167. package/dist/backend/services/warehouse-channel-reconciler.d.ts +19 -0
  168. package/dist/backend/services/warehouse-channel-reconciler.d.ts.map +1 -0
  169. package/dist/backend/services/warehouse-channel-reconciler.js +65 -0
  170. package/dist/backend/services/warehouse-channel-reconciler.js.map +1 -0
  171. package/dist/backend/services/warehouse-channel-service.d.ts +57 -0
  172. package/dist/backend/services/warehouse-channel-service.d.ts.map +1 -0
  173. package/dist/backend/services/warehouse-channel-service.js +205 -0
  174. package/dist/backend/services/warehouse-channel-service.js.map +1 -0
  175. package/dist/backend/services/warehouse-country-reference.d.ts +14 -0
  176. package/dist/backend/services/warehouse-country-reference.d.ts.map +1 -0
  177. package/dist/backend/services/warehouse-country-reference.js +27 -0
  178. package/dist/backend/services/warehouse-country-reference.js.map +1 -0
  179. package/dist/backend/services/warehouse-service.d.ts +86 -0
  180. package/dist/backend/services/warehouse-service.d.ts.map +1 -0
  181. package/dist/backend/services/warehouse-service.js +267 -0
  182. package/dist/backend/services/warehouse-service.js.map +1 -0
  183. package/dist/manifest.d.ts +219 -0
  184. package/dist/manifest.d.ts.map +1 -0
  185. package/dist/manifest.js +377 -0
  186. package/dist/manifest.js.map +1 -0
  187. package/dist/migrations/20260503T182812_inventory_workflow.d.ts +39 -0
  188. package/dist/migrations/20260503T182812_inventory_workflow.d.ts.map +1 -0
  189. package/dist/migrations/20260503T182812_inventory_workflow.js +205 -0
  190. package/dist/migrations/20260503T182812_inventory_workflow.js.map +1 -0
  191. package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.d.ts +14 -0
  192. package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.d.ts.map +1 -0
  193. package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.js +18 -0
  194. package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.js.map +1 -0
  195. package/dist/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.d.ts +28 -0
  196. package/dist/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.d.ts.map +1 -0
  197. package/dist/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.js +48 -0
  198. package/dist/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.js.map +1 -0
  199. package/dist/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.d.ts +44 -0
  200. package/dist/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.d.ts.map +1 -0
  201. package/dist/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.js +81 -0
  202. package/dist/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.js.map +1 -0
  203. package/dist/migrations/20260830T182139_inventory_organization_attribution.d.ts +109 -0
  204. package/dist/migrations/20260830T182139_inventory_organization_attribution.d.ts.map +1 -0
  205. package/dist/migrations/20260830T182139_inventory_organization_attribution.js +178 -0
  206. package/dist/migrations/20260830T182139_inventory_organization_attribution.js.map +1 -0
  207. package/dist/migrations/20260912T125716_inventory_organization_warehouses.d.ts +34 -0
  208. package/dist/migrations/20260912T125716_inventory_organization_warehouses.d.ts.map +1 -0
  209. package/dist/migrations/20260912T125716_inventory_organization_warehouses.js +50 -0
  210. package/dist/migrations/20260912T125716_inventory_organization_warehouses.js.map +1 -0
  211. package/dist/migrations/index.d.ts +32 -0
  212. package/dist/migrations/index.d.ts.map +1 -0
  213. package/dist/migrations/index.js +39 -0
  214. package/dist/migrations/index.js.map +1 -0
  215. package/dist/ports/index.d.ts +183 -0
  216. package/dist/ports/index.d.ts.map +1 -0
  217. package/dist/ports/index.js +2 -0
  218. package/dist/ports/index.js.map +1 -0
  219. package/docs/inventory.md +177 -0
  220. package/i18n/en.json +32 -0
  221. package/i18n/pl.json +32 -0
  222. package/package.json +105 -0
  223. package/tailwind.css +14 -0
@@ -0,0 +1,167 @@
1
+ import { HttpError } from '@endora-commerce/platform/http';
2
+ import { InventoryThreshold } from '../entities/inventory-threshold.entity.js';
3
+ /**
4
+ * ThresholdAdminService (US5 / FR-017).
5
+ *
6
+ * Reads + writes the inventory display-band thresholds. Storage:
7
+ * - global → singleton row in `inventory_thresholds` (scopeKind='global')
8
+ * - perCategory → `categories.inventory_threshold_*` columns
9
+ * - perProduct → `inventory_thresholds` rows with scopeKind='product'
10
+ *
11
+ * The split exists for read-path performance: category thresholds inline
12
+ * with the category row keep the storefront resolver one query light.
13
+ */
14
+ export class ThresholdAdminService {
15
+ emFactory;
16
+ catalogCategories;
17
+ catalogCategoryWrites;
18
+ catalogProducts;
19
+ auditLog;
20
+ constructor(emFactory,
21
+ /** `catalogCategoryReadPort`, owned by `catalog` (feature 075, Phase C). */
22
+ catalogCategories,
23
+ /**
24
+ * `catalogCategoryWritePort`, owned by `catalog`. The per-category half of
25
+ * these thresholds lives in three columns on `catalog`'s `categories` row,
26
+ * so the write belongs to `catalog` and this module hands it the patch.
27
+ */
28
+ catalogCategoryWrites,
29
+ /** `catalogProductReadPort`, owned by `catalog` — the per-product existence check. */
30
+ catalogProducts, auditLog) {
31
+ this.emFactory = emFactory;
32
+ this.catalogCategories = catalogCategories;
33
+ this.catalogCategoryWrites = catalogCategoryWrites;
34
+ this.catalogProducts = catalogProducts;
35
+ this.auditLog = auditLog;
36
+ }
37
+ async read() {
38
+ const em = this.emFactory();
39
+ const globalRow = await em.findOne(InventoryThreshold, {
40
+ scopeKind: 'global',
41
+ scopeId: null,
42
+ });
43
+ const global = {
44
+ high: globalRow?.thresholdHigh ?? 100,
45
+ medium: globalRow?.thresholdMedium ?? 20,
46
+ low: globalRow?.thresholdLow ?? 1,
47
+ };
48
+ const perCategoryRows = await this.catalogCategories.listWithInventoryThresholds();
49
+ const perCategory = perCategoryRows.map((c) => ({
50
+ categoryId: c.id,
51
+ high: c.inventoryThresholdHigh,
52
+ medium: c.inventoryThresholdMedium,
53
+ low: c.inventoryThresholdLow,
54
+ }));
55
+ const perProductRows = await em.find(InventoryThreshold, { scopeKind: 'product' });
56
+ const perProduct = perProductRows.map((r) => ({
57
+ productId: r.scopeId,
58
+ high: r.thresholdHigh ?? null,
59
+ medium: r.thresholdMedium ?? null,
60
+ low: r.thresholdLow ?? null,
61
+ }));
62
+ return { global, perCategory, perProduct };
63
+ }
64
+ async patch(input, auditCtx) {
65
+ const em = this.emFactory();
66
+ // Validate every scope before applying any of it (feature 075, Phase C).
67
+ // The category half is `catalog`'s write now, so it no longer shares this
68
+ // module's flush: an unknown product id used to roll the whole patch back
69
+ // because nothing had been flushed yet, and it still applies nothing —
70
+ // because nothing has been *written* yet.
71
+ if (input.perCategory) {
72
+ for (const c of input.perCategory) {
73
+ const category = await this.catalogCategories.findById(c.categoryId);
74
+ if (!category) {
75
+ throw new HttpError(404, 'NOT_FOUND', `Category ${c.categoryId} not found`);
76
+ }
77
+ }
78
+ }
79
+ if (input.perProduct) {
80
+ for (const p of input.perProduct) {
81
+ const product = await this.catalogProducts.findById(p.productId);
82
+ if (!product) {
83
+ throw new HttpError(404, 'PRODUCT_NOT_FOUND', `Product ${p.productId} not found`);
84
+ }
85
+ }
86
+ }
87
+ if (input.global) {
88
+ await this.applyGlobal(em, input.global);
89
+ }
90
+ if (input.perProduct) {
91
+ for (const p of input.perProduct) {
92
+ await this.applyProduct(em, p);
93
+ }
94
+ }
95
+ await em.flush();
96
+ if (input.perCategory) {
97
+ for (const c of input.perCategory) {
98
+ await this.catalogCategoryWrites.setInventoryThresholds(c.categoryId, {
99
+ ...(c.high !== undefined ? { high: c.high } : {}),
100
+ ...(c.medium !== undefined ? { medium: c.medium } : {}),
101
+ ...(c.low !== undefined ? { low: c.low } : {}),
102
+ });
103
+ }
104
+ }
105
+ const view = await this.read();
106
+ // Feature 024 — audit threshold patches as a single
107
+ // `low_stock_threshold.update` row per request. Bulk patches across
108
+ // global / perCategory / perProduct collapse into one summary row so
109
+ // the dashboard isn't flooded (FR-011, SC-010).
110
+ if (this.auditLog && auditCtx) {
111
+ const touched = {};
112
+ if (input.global)
113
+ touched['global'] = input.global;
114
+ if (input.perCategory)
115
+ touched['perCategoryCount'] = input.perCategory.length;
116
+ if (input.perProduct)
117
+ touched['perProductCount'] = input.perProduct.length;
118
+ await this.auditLog.record({
119
+ actorAdminUserId: auditCtx.actorAdminUserId,
120
+ ...(auditCtx.impersonatedCustomerAccountId !== undefined
121
+ ? { impersonatedCustomerAccountId: auditCtx.impersonatedCustomerAccountId }
122
+ : {}),
123
+ action: 'low_stock_threshold.update',
124
+ objectType: 'low_stock_threshold',
125
+ objectId: 'global',
126
+ stateAfter: touched,
127
+ ...(auditCtx.ipAddress !== undefined ? { ipAddress: auditCtx.ipAddress } : {}),
128
+ ...(auditCtx.userAgent !== undefined ? { userAgent: auditCtx.userAgent } : {}),
129
+ ...(auditCtx.requestId !== undefined ? { requestId: auditCtx.requestId } : {}),
130
+ });
131
+ }
132
+ return view;
133
+ }
134
+ async applyGlobal(em, patch) {
135
+ let row = await em.findOne(InventoryThreshold, { scopeKind: 'global', scopeId: null });
136
+ if (!row) {
137
+ row = em.create(InventoryThreshold, { scopeKind: 'global', scopeId: null });
138
+ em.persist(row);
139
+ }
140
+ if (patch.high !== undefined)
141
+ row.thresholdHigh = patch.high;
142
+ if (patch.medium !== undefined)
143
+ row.thresholdMedium = patch.medium;
144
+ if (patch.low !== undefined)
145
+ row.thresholdLow = patch.low;
146
+ }
147
+ async applyProduct(em, patch) {
148
+ let row = await em.findOne(InventoryThreshold, {
149
+ scopeKind: 'product',
150
+ scopeId: patch.productId,
151
+ });
152
+ if (!row) {
153
+ row = em.create(InventoryThreshold, {
154
+ scopeKind: 'product',
155
+ scopeId: patch.productId,
156
+ });
157
+ em.persist(row);
158
+ }
159
+ if (patch.high !== undefined)
160
+ row.thresholdHigh = patch.high;
161
+ if (patch.medium !== undefined)
162
+ row.thresholdMedium = patch.medium;
163
+ if (patch.low !== undefined)
164
+ row.thresholdLow = patch.low;
165
+ }
166
+ }
167
+ //# sourceMappingURL=threshold-admin-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threshold-admin-service.js","sourceRoot":"","sources":["../../../src/backend/services/threshold-admin-service.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AA4B/E;;;;;;;;;;GAUG;AACH,MAAM,OAAO,qBAAqB;IAEb;IAEA;IAMA;IAEA;IACA;IAZnB,YACmB,SAA8B;IAC/C,4EAA4E;IAC3D,iBAA0C;IAC3D;;;;OAIG;IACc,qBAA+C;IAChE,sFAAsF;IACrE,eAAuC,EACvC,QAAoB;QAXpB,cAAS,GAAT,SAAS,CAAqB;QAE9B,sBAAiB,GAAjB,iBAAiB,CAAyB;QAM1C,0BAAqB,GAArB,qBAAqB,CAA0B;QAE/C,oBAAe,GAAf,eAAe,CAAwB;QACvC,aAAQ,GAAR,QAAQ,CAAY;IACpC,CAAC;IAEJ,KAAK,CAAC,IAAI;QACR,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAE5B,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE;YACrD,SAAS,EAAE,QAAQ;YACnB,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QACH,MAAM,MAAM,GAAoB;YAC9B,IAAI,EAAE,SAAS,EAAE,aAAa,IAAI,GAAG;YACrC,MAAM,EAAE,SAAS,EAAE,eAAe,IAAI,EAAE;YACxC,GAAG,EAAE,SAAS,EAAE,YAAY,IAAI,CAAC;SAClC,CAAC;QAEF,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,2BAA2B,EAAE,CAAC;QACnF,MAAM,WAAW,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9C,UAAU,EAAE,CAAC,CAAC,EAAE;YAChB,IAAI,EAAE,CAAC,CAAC,sBAAsB;YAC9B,MAAM,EAAE,CAAC,CAAC,wBAAwB;YAClC,GAAG,EAAE,CAAC,CAAC,qBAAqB;SAC7B,CAAC,CAAC,CAAC;QAEJ,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;QACnF,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5C,SAAS,EAAE,CAAC,CAAC,OAAQ;YACrB,IAAI,EAAE,CAAC,CAAC,aAAa,IAAI,IAAI;YAC7B,MAAM,EAAE,CAAC,CAAC,eAAe,IAAI,IAAI;YACjC,GAAG,EAAE,CAAC,CAAC,YAAY,IAAI,IAAI;SAC5B,CAAC,CAAC,CAAC;QAEJ,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,KAAK,CACT,KAAsB,EACtB,QAAgC;QAEhC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAE5B,yEAAyE;QACzE,0EAA0E;QAC1E,0EAA0E;QAC1E,uEAAuE;QACvE,0CAA0C;QAC1C,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;gBAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;gBACrE,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,UAAU,YAAY,CAAC,CAAC;gBAC9E,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACrB,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gBACjE,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,mBAAmB,EAAE,WAAW,CAAC,CAAC,SAAS,YAAY,CAAC,CAAC;gBACpF,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACrB,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;gBACjC,MAAM,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QACD,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;gBAClC,MAAM,IAAI,CAAC,qBAAqB,CAAC,sBAAsB,CAAC,CAAC,CAAC,UAAU,EAAE;oBACpE,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACjD,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACvD,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC/C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAE/B,oDAAoD;QACpD,oEAAoE;QACpE,qEAAqE;QACrE,gDAAgD;QAChD,IAAI,IAAI,CAAC,QAAQ,IAAI,QAAQ,EAAE,CAAC;YAC9B,MAAM,OAAO,GAA4B,EAAE,CAAC;YAC5C,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;YACnD,IAAI,KAAK,CAAC,WAAW;gBAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC;YAC9E,IAAI,KAAK,CAAC,UAAU;gBAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;YAC3E,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACzB,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;gBAC3C,GAAG,CAAC,QAAQ,CAAC,6BAA6B,KAAK,SAAS;oBACtD,CAAC,CAAC,EAAE,6BAA6B,EAAE,QAAQ,CAAC,6BAA6B,EAAE;oBAC3E,CAAC,CAAC,EAAE,CAAC;gBACP,MAAM,EAAE,4BAA4B;gBACpC,UAAU,EAAE,qBAAqB;gBACjC,QAAQ,EAAE,QAAQ;gBAClB,UAAU,EAAE,OAAO;gBACnB,GAAG,CAAC,QAAQ,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9E,GAAG,CAAC,QAAQ,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9E,GAAG,CAAC,QAAQ,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/E,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,EAAiB,EAAE,KAA6B;QACxE,IAAI,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACvF,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,kBAAkB,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5E,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,GAAG,CAAC,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC;QAC7D,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;YAAE,GAAG,CAAC,eAAe,GAAG,KAAK,CAAC,MAAM,CAAC;QACnE,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;YAAE,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC;IAC5D,CAAC;IAEO,KAAK,CAAC,YAAY,CACxB,EAAiB,EACjB,KAAqD;QAErD,IAAI,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE;YAC7C,SAAS,EAAE,SAAS;YACpB,OAAO,EAAE,KAAK,CAAC,SAAS;SACzB,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,kBAAkB,EAAE;gBAClC,SAAS,EAAE,SAAS;gBACpB,OAAO,EAAE,KAAK,CAAC,SAAS;aACzB,CAAC,CAAC;YACH,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,GAAG,CAAC,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC;QAC7D,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;YAAE,GAAG,CAAC,eAAe,GAAG,KAAK,CAAC,MAAM,CAAC;QACnE,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;YAAE,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC;IAC5D,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Threshold resolver — pure function (feature 010 / FR-017).
3
+ *
4
+ * Resolves the `(high, medium, low)` threshold triple from the
5
+ * priority chain Product → Category → Global. Each threshold key is
6
+ * resolved independently; a missing value at one level falls through
7
+ * to the next, so a product can override only `low` while inheriting
8
+ * `high` and `medium` from the global scope.
9
+ */
10
+ export interface ThresholdLevel {
11
+ high: number | null;
12
+ medium: number | null;
13
+ low: number | null;
14
+ }
15
+ export interface ResolveThresholdsInput {
16
+ productThresholds?: ThresholdLevel | null;
17
+ /** Each category in the product's category list, ordered by relevance.
18
+ * The resolver returns the first defined value walking the array. */
19
+ categoryThresholds?: ThresholdLevel[];
20
+ globalThresholds: ThresholdLevel;
21
+ }
22
+ export declare function resolveThresholds(input: ResolveThresholdsInput): ThresholdLevel;
23
+ //# sourceMappingURL=threshold-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threshold-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/threshold-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAC1C;0EACsE;IACtE,kBAAkB,CAAC,EAAE,cAAc,EAAE,CAAC;IACtC,gBAAgB,EAAE,cAAc,CAAC;CAClC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,cAAc,CAU/E"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Threshold resolver — pure function (feature 010 / FR-017).
3
+ *
4
+ * Resolves the `(high, medium, low)` threshold triple from the
5
+ * priority chain Product → Category → Global. Each threshold key is
6
+ * resolved independently; a missing value at one level falls through
7
+ * to the next, so a product can override only `low` while inheriting
8
+ * `high` and `medium` from the global scope.
9
+ */
10
+ export function resolveThresholds(input) {
11
+ const product = input.productThresholds ?? null;
12
+ const categories = input.categoryThresholds ?? [];
13
+ const global = input.globalThresholds;
14
+ return {
15
+ high: pick('high', product, categories, global),
16
+ medium: pick('medium', product, categories, global),
17
+ low: pick('low', product, categories, global),
18
+ };
19
+ }
20
+ function pick(key, product, categories, global) {
21
+ if (product && product[key] !== null && product[key] !== undefined)
22
+ return product[key];
23
+ for (const cat of categories) {
24
+ if (cat[key] !== null && cat[key] !== undefined)
25
+ return cat[key];
26
+ }
27
+ return global[key];
28
+ }
29
+ //# sourceMappingURL=threshold-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threshold-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/threshold-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,MAAM,UAAU,iBAAiB,CAAC,KAA6B;IAC7D,MAAM,OAAO,GAAG,KAAK,CAAC,iBAAiB,IAAI,IAAI,CAAC;IAChD,MAAM,UAAU,GAAG,KAAK,CAAC,kBAAkB,IAAI,EAAE,CAAC;IAClD,MAAM,MAAM,GAAG,KAAK,CAAC,gBAAgB,CAAC;IAEtC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC;QAC/C,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC;QACnD,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CACX,GAA8B,EAC9B,OAA8B,EAC9B,UAA4B,EAC5B,MAAsB;IAEtB,IAAI,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACxF,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
3
+ export interface SettingsValueChangedPayload {
4
+ settingCode: string;
5
+ salesChannelIds: string[];
6
+ valueType: string;
7
+ }
8
+ /**
9
+ * ThresholdSettingsMirror (US5 / T058).
10
+ *
11
+ * The `inventory.global_threshold_{high,medium,low}` settings live in
12
+ * the Settings module so admins can edit them through the Module
13
+ * Settings UI alongside every other inventory key. The display-band
14
+ * resolver, however, reads the global thresholds from
15
+ * `inventory_thresholds` because that's where category and product
16
+ * thresholds also live (one read path, three scopes — research §R3).
17
+ *
18
+ * This subscriber bridges the two: when a settings.value_changed event
19
+ * carries one of the three threshold keys, we mirror the new value
20
+ * into the singleton `(scope_kind='global', scope_id=null)` row.
21
+ *
22
+ * The mirror is best-effort — failures are logged but never propagate
23
+ * back to the settings writer. The seeded defaults in migration 030
24
+ * keep the resolver happy if the mirror has never run.
25
+ */
26
+ export declare class ThresholdSettingsMirror {
27
+ private readonly emFactory;
28
+ private readonly settingsService;
29
+ /** Resolves the channel id used to read the settings value. System
30
+ * default is fine because the global thresholds are not per-channel.
31
+ * Resolved lazily so the mirror does not block module construction
32
+ * on channel lookups. */
33
+ private readonly resolveSettingsChannelId;
34
+ private readonly onError?;
35
+ constructor(emFactory: () => EntityManager, settingsService: SettingsReadPort,
36
+ /** Resolves the channel id used to read the settings value. System
37
+ * default is fine because the global thresholds are not per-channel.
38
+ * Resolved lazily so the mirror does not block module construction
39
+ * on channel lookups. */
40
+ resolveSettingsChannelId: () => Promise<string | null>, onError?: ((err: unknown) => void) | undefined);
41
+ /**
42
+ * `settings.value_changed` — mirror one of the three global threshold codes
43
+ * into the singleton `inventory_thresholds` row.
44
+ *
45
+ * The registration lives in this module's `backend.ts` and goes through
46
+ * `ctx.subscribe` (issue #107): as a bare `eventBus.on` this kept writing the
47
+ * mirror row while `inventory` was switched off. The read it performs sees the
48
+ * new value because the settings write seam drops the cache and awaits the
49
+ * drop before it emits (issue #45) — not because of where this handler sits
50
+ * in the dispatch order.
51
+ */
52
+ onSettingChanged(payload: SettingsValueChangedPayload): Promise<void>;
53
+ private mirror;
54
+ }
55
+ //# sourceMappingURL=threshold-settings-mirror.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threshold-settings-mirror.d.ts","sourceRoot":"","sources":["../../../src/backend/services/threshold-settings-mirror.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAIzE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAUD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC;;;8BAG0B;IAC1B,OAAO,CAAC,QAAQ,CAAC,wBAAwB;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAPR,SAAS,EAAE,MAAM,aAAa,EAC9B,eAAe,EAAE,gBAAgB;IAClD;;;8BAG0B;IACT,wBAAwB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EACtD,OAAO,CAAC,GAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,aAAA;IAGnD;;;;;;;;;;OAUG;IACG,gBAAgB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;YAW7D,MAAM;CAsBrB"}
@@ -0,0 +1,84 @@
1
+ import { z } from 'zod';
2
+ import { InventoryThreshold } from '../entities/inventory-threshold.entity.js';
3
+ import { INVENTORY_SETTING_CODES } from '../../manifest.js';
4
+ const NUMBER_OR_NULL = z.number().int().nullable();
5
+ const KEY_TO_FIELD = {
6
+ [INVENTORY_SETTING_CODES.GLOBAL_THRESHOLD_HIGH]: 'thresholdHigh',
7
+ [INVENTORY_SETTING_CODES.GLOBAL_THRESHOLD_MEDIUM]: 'thresholdMedium',
8
+ [INVENTORY_SETTING_CODES.GLOBAL_THRESHOLD_LOW]: 'thresholdLow',
9
+ };
10
+ /**
11
+ * ThresholdSettingsMirror (US5 / T058).
12
+ *
13
+ * The `inventory.global_threshold_{high,medium,low}` settings live in
14
+ * the Settings module so admins can edit them through the Module
15
+ * Settings UI alongside every other inventory key. The display-band
16
+ * resolver, however, reads the global thresholds from
17
+ * `inventory_thresholds` because that's where category and product
18
+ * thresholds also live (one read path, three scopes — research §R3).
19
+ *
20
+ * This subscriber bridges the two: when a settings.value_changed event
21
+ * carries one of the three threshold keys, we mirror the new value
22
+ * into the singleton `(scope_kind='global', scope_id=null)` row.
23
+ *
24
+ * The mirror is best-effort — failures are logged but never propagate
25
+ * back to the settings writer. The seeded defaults in migration 030
26
+ * keep the resolver happy if the mirror has never run.
27
+ */
28
+ export class ThresholdSettingsMirror {
29
+ emFactory;
30
+ settingsService;
31
+ resolveSettingsChannelId;
32
+ onError;
33
+ constructor(emFactory, settingsService,
34
+ /** Resolves the channel id used to read the settings value. System
35
+ * default is fine because the global thresholds are not per-channel.
36
+ * Resolved lazily so the mirror does not block module construction
37
+ * on channel lookups. */
38
+ resolveSettingsChannelId, onError) {
39
+ this.emFactory = emFactory;
40
+ this.settingsService = settingsService;
41
+ this.resolveSettingsChannelId = resolveSettingsChannelId;
42
+ this.onError = onError;
43
+ }
44
+ /**
45
+ * `settings.value_changed` — mirror one of the three global threshold codes
46
+ * into the singleton `inventory_thresholds` row.
47
+ *
48
+ * The registration lives in this module's `backend.ts` and goes through
49
+ * `ctx.subscribe` (issue #107): as a bare `eventBus.on` this kept writing the
50
+ * mirror row while `inventory` was switched off. The read it performs sees the
51
+ * new value because the settings write seam drops the cache and awaits the
52
+ * drop before it emits (issue #45) — not because of where this handler sits
53
+ * in the dispatch order.
54
+ */
55
+ async onSettingChanged(payload) {
56
+ const field = KEY_TO_FIELD[payload.settingCode];
57
+ if (!field)
58
+ return;
59
+ try {
60
+ await this.mirror(payload.settingCode, field);
61
+ }
62
+ catch (err) {
63
+ const log = this.onError ?? ((e) => console.warn('threshold-mirror failed', e));
64
+ log(err);
65
+ }
66
+ }
67
+ async mirror(settingCode, field) {
68
+ // command-coverage-ignore: derived-state sync — mirrors a Settings value into
69
+ // the global InventoryThreshold row; the Settings write is the audited source.
70
+ const channelId = await this.resolveSettingsChannelId();
71
+ if (!channelId)
72
+ return;
73
+ const value = await this.settingsService.get(settingCode, channelId, NUMBER_OR_NULL);
74
+ const em = this.emFactory();
75
+ let row = await em.findOne(InventoryThreshold, { scopeKind: 'global', scopeId: null });
76
+ if (!row) {
77
+ row = em.create(InventoryThreshold, { scopeKind: 'global', scopeId: null });
78
+ em.persist(row);
79
+ }
80
+ row[field] = value;
81
+ await em.flush();
82
+ }
83
+ }
84
+ //# sourceMappingURL=threshold-settings-mirror.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threshold-settings-mirror.js","sourceRoot":"","sources":["../../../src/backend/services/threshold-settings-mirror.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAQ5D,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;AAEnD,MAAM,YAAY,GAAyE;IACzF,CAAC,uBAAuB,CAAC,qBAAqB,CAAC,EAAE,eAAe;IAChE,CAAC,uBAAuB,CAAC,uBAAuB,CAAC,EAAE,iBAAiB;IACpE,CAAC,uBAAuB,CAAC,oBAAoB,CAAC,EAAE,cAAc;CAC/D,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,uBAAuB;IAEf;IACA;IAKA;IACA;IARnB,YACmB,SAA8B,EAC9B,eAAiC;IAClD;;;8BAG0B;IACT,wBAAsD,EACtD,OAAgC;QAPhC,cAAS,GAAT,SAAS,CAAqB;QAC9B,oBAAe,GAAf,eAAe,CAAkB;QAKjC,6BAAwB,GAAxB,wBAAwB,CAA8B;QACtD,YAAO,GAAP,OAAO,CAAyB;IAChD,CAAC;IAEJ;;;;;;;;;;OAUG;IACH,KAAK,CAAC,gBAAgB,CAAC,OAAoC;QACzD,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE,CAAC,CAAC,CAAC,CAAC;YACzF,GAAG,CAAC,GAAG,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,MAAM,CAClB,WAAmB,EACnB,KAA2D;QAE3D,8EAA8E;QAC9E,+EAA+E;QAC/E,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,wBAAwB,EAAE,CAAC;QACxD,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,CAC1C,WAAW,EACX,SAAS,EACT,cAAc,CACf,CAAC;QACF,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,IAAI,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACvF,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,kBAAkB,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5E,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;QACD,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;QACnB,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,19 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ /**
3
+ * WarehouseChannelReconciler — idempotent boot-time check that every
4
+ * active sales channel has at least one warehouse assigned and that
5
+ * exactly one of those assignments carries `isDefault = true`.
6
+ *
7
+ * Why a runtime reconciler: the inventory migration cannot know about
8
+ * future channels because the DefaultChannelReconciler creates the
9
+ * platform's first channel at boot time, AFTER migrations run. This
10
+ * reconciler runs after every boot and catches up.
11
+ */
12
+ export declare class WarehouseChannelReconciler {
13
+ private readonly em;
14
+ constructor(em: EntityManager);
15
+ run(): Promise<{
16
+ assignmentsCreated: number;
17
+ }>;
18
+ }
19
+ //# sourceMappingURL=warehouse-channel-reconciler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"warehouse-channel-reconciler.d.ts","sourceRoot":"","sources":["../../../src/backend/services/warehouse-channel-reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAK3D;;;;;;;;;GASG;AACH,qBAAa,0BAA0B;IACzB,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,aAAa;IAExC,GAAG,IAAI,OAAO,CAAC;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;CA6CrD"}
@@ -0,0 +1,65 @@
1
+ import { WarehouseChannelAssignment } from '../entities/warehouse-channel-assignment.entity.js';
2
+ import { Warehouse, DEFAULT_WAREHOUSE_ID } from '../entities/warehouse.entity.js';
3
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
4
+ /**
5
+ * WarehouseChannelReconciler — idempotent boot-time check that every
6
+ * active sales channel has at least one warehouse assigned and that
7
+ * exactly one of those assignments carries `isDefault = true`.
8
+ *
9
+ * Why a runtime reconciler: the inventory migration cannot know about
10
+ * future channels because the DefaultChannelReconciler creates the
11
+ * platform's first channel at boot time, AFTER migrations run. This
12
+ * reconciler runs after every boot and catches up.
13
+ */
14
+ export class WarehouseChannelReconciler {
15
+ em;
16
+ constructor(em) {
17
+ this.em = em;
18
+ }
19
+ async run() {
20
+ // command-coverage-ignore: startup reconciler — backfills the default
21
+ // warehouse↔channel assignment for channels missing one, an idempotent
22
+ // system-invariant repair, not an operator-initiated audited write.
23
+ // The kernel's own entity, not `select id from sales_channels` (feature 075,
24
+ // D-87). `sales_channels` is the kernel's table since feature 072 moved the
25
+ // resolution machinery there, and a module relating into the kernel by ORM
26
+ // is the sanctioned access.
27
+ //
28
+ // Read through `this.em`, not through a knex handle and not through
29
+ // `salesChannelResolutionPort` — both take their own pooled connection, so
30
+ // the channel list would be read from outside any transaction the caller
31
+ // holds open while the assignments below are written through `this.em` from
32
+ // inside it (issue #207).
33
+ const channels = await this.em.find(SalesChannel, {}, { fields: ['id'] });
34
+ if (channels.length === 0)
35
+ return { assignmentsCreated: 0 };
36
+ const defaultWarehouse = await this.em.findOne(Warehouse, { id: DEFAULT_WAREHOUSE_ID });
37
+ if (!defaultWarehouse) {
38
+ // Migration 030 should have seeded this — bail rather than silently
39
+ // creating a row with a dangling foreign key.
40
+ return { assignmentsCreated: 0 };
41
+ }
42
+ let created = 0;
43
+ for (const channel of channels) {
44
+ const existing = await this.em.find(WarehouseChannelAssignment, {
45
+ salesChannelId: channel.id,
46
+ });
47
+ if (existing.length === 0) {
48
+ const row = this.em.create(WarehouseChannelAssignment, {
49
+ warehouseId: DEFAULT_WAREHOUSE_ID,
50
+ salesChannelId: channel.id,
51
+ isDefault: true,
52
+ });
53
+ await this.em.persistAndFlush(row);
54
+ created += 1;
55
+ }
56
+ else if (!existing.some((a) => a.isDefault)) {
57
+ // Channel has assignments but no default — promote the first one.
58
+ existing[0].isDefault = true;
59
+ await this.em.flush();
60
+ }
61
+ }
62
+ return { assignmentsCreated: created };
63
+ }
64
+ }
65
+ //# sourceMappingURL=warehouse-channel-reconciler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"warehouse-channel-reconciler.js","sourceRoot":"","sources":["../../../src/backend/services/warehouse-channel-reconciler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,oDAAoD,CAAC;AAChG,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAEhE;;;;;;;;;GASG;AACH,MAAM,OAAO,0BAA0B;IACR;IAA7B,YAA6B,EAAiB;QAAjB,OAAE,GAAF,EAAE,CAAe;IAAG,CAAC;IAElD,KAAK,CAAC,GAAG;QACP,sEAAsE;QACtE,uEAAuE;QACvE,oEAAoE;QACpE,6EAA6E;QAC7E,4EAA4E;QAC5E,2EAA2E;QAC3E,4BAA4B;QAC5B,EAAE;QACF,oEAAoE;QACpE,2EAA2E;QAC3E,yEAAyE;QACzE,4EAA4E;QAC5E,0BAA0B;QAC1B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1E,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC;QAE5D,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,oBAAoB,EAAE,CAAC,CAAC;QACxF,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,oEAAoE;YACpE,8CAA8C;YAC9C,OAAO,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC;QACnC,CAAC;QAED,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,0BAA0B,EAAE;gBAC9D,cAAc,EAAE,OAAO,CAAC,EAAE;aAC3B,CAAC,CAAC;YACH,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,0BAA0B,EAAE;oBACrD,WAAW,EAAE,oBAAoB;oBACjC,cAAc,EAAE,OAAO,CAAC,EAAE;oBAC1B,SAAS,EAAE,IAAI;iBAChB,CAAC,CAAC;gBACH,MAAM,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;gBACnC,OAAO,IAAI,CAAC,CAAC;YACf,CAAC;iBAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC9C,kEAAkE;gBAClE,QAAQ,CAAC,CAAC,CAAE,CAAC,SAAS,GAAG,IAAI,CAAC;gBAC9B,MAAM,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC;QACH,CAAC;QACD,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC;IACzC,CAAC;CACF"}
@@ -0,0 +1,57 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
3
+ export interface ChannelWarehouseAssignmentDTO {
4
+ id: string;
5
+ warehouseId: string;
6
+ warehouseName: string;
7
+ warehouseCode: string;
8
+ isDefault: boolean;
9
+ sortOrder: number;
10
+ assignedAt: string;
11
+ }
12
+ export interface AssignToChannelInput {
13
+ warehouseId: string;
14
+ isDefault?: boolean;
15
+ sortOrder?: number;
16
+ }
17
+ export interface PatchAssignmentInput {
18
+ isDefault?: boolean;
19
+ sortOrder?: number;
20
+ }
21
+ /**
22
+ * WarehouseChannelService (US3) — manages the m:n binding between
23
+ * warehouses and sales channels with at most one `isDefault = true`
24
+ * row per channel.
25
+ *
26
+ * Invariants:
27
+ * - Every active channel must have at least one warehouse (refuse
28
+ * `unassign` when this would leave the channel orphaned).
29
+ * - At most one assignment per channel carries `isDefault = true`
30
+ * (`setDefault` demotes the previous default in the same flush).
31
+ */
32
+ export declare class WarehouseChannelService {
33
+ #private;
34
+ private readonly emFactory;
35
+ /** Feature 054 — audits assignment writes co-transactionally when provided. */
36
+ private readonly auditLog?;
37
+ constructor(emFactory: () => EntityManager,
38
+ /** Feature 054 — audits assignment writes co-transactionally when provided. */
39
+ auditLog?: AuditPort | undefined);
40
+ listForChannel(channelId: string): Promise<ChannelWarehouseAssignmentDTO[]>;
41
+ listChannelsForWarehouse(warehouseId: string): Promise<Array<{
42
+ salesChannelId: string;
43
+ isDefault: boolean;
44
+ sortOrder: number;
45
+ }>>;
46
+ assign(channelId: string, input: AssignToChannelInput): Promise<ChannelWarehouseAssignmentDTO>;
47
+ patch(channelId: string, assignmentId: string, input: PatchAssignmentInput): Promise<ChannelWarehouseAssignmentDTO>;
48
+ unassign(channelId: string, assignmentId: string): Promise<void>;
49
+ /**
50
+ * Resolves the warehouses bound to a sales channel for the storefront
51
+ * cumulative-on-hand readout (research §R7).
52
+ */
53
+ resolveCandidateWarehouseIds(channelId: string): Promise<string[]>;
54
+ private demoteOtherDefaults;
55
+ private toDTO;
56
+ }
57
+ //# sourceMappingURL=warehouse-channel-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"warehouse-channel-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/warehouse-channel-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAMlE,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,uBAAuB;;IAEhC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAFT,SAAS,EAAE,MAAM,aAAa;IAC/C,+EAA+E;IAC9D,QAAQ,CAAC,EAAE,SAAS,YAAA;IA6BjC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,6BAA6B,EAAE,CAAC;IAmB3E,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QACjE,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,OAAO,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;IAUG,MAAM,CACV,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,6BAA6B,CAAC;IAiDnC,KAAK,CACT,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,6BAA6B,CAAC;IA+BnC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmCtE;;;OAGG;IACG,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YAQ1D,mBAAmB;IAWjC,OAAO,CAAC,KAAK;CAcd"}