@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,205 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * Inventory module — multi-warehouse rewrite (feature 010).
4
+ *
5
+ * What this migration does, in order (data-model.md §10):
6
+ * 1. Create `warehouses` table; seed the `Default` warehouse with
7
+ * deterministic UUID `00000000-0000-4000-8000-00000000d017`.
8
+ * 2. Add `warehouse_id` to `stock_levels`, backfill every existing
9
+ * row to the Default UUID, swap the unique index to include
10
+ * warehouse_id.
11
+ * 3. Create `warehouse_channel_assignments`; for every existing
12
+ * sales channel insert a `(default_warehouse, channel, isDefault=true)`
13
+ * seed row.
14
+ * 4. Extend `availability_notifications` with `email` + `status`
15
+ * columns, relax `customer_account_id` to nullable, add the
16
+ * "at least one recipient" CHECK constraint.
17
+ * 5. (moved) The stock-management columns on `products` and their
18
+ * `products_fulfilment_strategy_check` constraint, and
19
+ * 6. (moved) the three threshold columns on `categories`, are `catalog`'s:
20
+ * its entities map them, and since feature 134
21
+ * (`specs/134-paid-module-extraction/research.md` D15) they are created
22
+ * by `catalog`'s `Migration20260925T125527CatalogInventoryColumns`. This
23
+ * migration keeps its class name and position, and lost only those
24
+ * statements in `up()` and `down()` — with them, a database whose
25
+ * `catalog` migration already ran failed when `inventory` was installed
26
+ * later, and this module's hard uninstall dropped columns it does not own.
27
+ * 7. Create `inventory_thresholds`; insert the global row at
28
+ * foundation defaults (high=100, medium=20, low=1).
29
+ * 8. Create `stock_allocations`.
30
+ *
31
+ * The deterministic seed UUID lets retried migrations stay
32
+ * idempotent and lets other modules' tests reference the Default
33
+ * warehouse by id.
34
+ */
35
+ export class Migration20260503T182812InventoryWorkflow extends Migration {
36
+ async up() {
37
+ // ============================================================
38
+ // 1. warehouses
39
+ // ============================================================
40
+ this.addSql(`
41
+ create table "warehouses" (
42
+ "id" uuid not null,
43
+ "name" varchar(160) not null,
44
+ "code" varchar(64) not null,
45
+ "active" boolean not null default true,
46
+ "description" text null,
47
+ "address" jsonb null,
48
+ "contact_name" varchar(160) null,
49
+ "contact_email" varchar(320) null,
50
+ "contact_phone" varchar(64) null,
51
+ "created_at" timestamptz not null,
52
+ "updated_at" timestamptz not null,
53
+ constraint "warehouses_pkey" primary key ("id"),
54
+ constraint "warehouses_code_unique" unique ("code"),
55
+ constraint "warehouses_code_format_check" check ("code" ~ '^[a-z][a-z0-9_-]*[a-z0-9]$')
56
+ );
57
+ `);
58
+ this.addSql(`create index "warehouses_active_idx" on "warehouses" ("active");`);
59
+ // Seed Default warehouse with deterministic UUID for migration idempotency.
60
+ this.addSql(`
61
+ insert into "warehouses" ("id", "name", "code", "active", "created_at", "updated_at")
62
+ values ('00000000-0000-4000-8000-00000000d017', 'Default', 'default', true, now(), now())
63
+ on conflict ("id") do nothing;
64
+ `);
65
+ // ============================================================
66
+ // 2. stock_levels — extend with warehouse_id
67
+ // ============================================================
68
+ this.addSql(`alter table "stock_levels" add column "warehouse_id" uuid null;`);
69
+ this.addSql(`
70
+ update "stock_levels"
71
+ set "warehouse_id" = '00000000-0000-4000-8000-00000000d017'::uuid
72
+ where "warehouse_id" is null;
73
+ `);
74
+ this.addSql(`alter table "stock_levels" alter column "warehouse_id" set not null;`);
75
+ this.addSql(`drop index if exists "stock_levels_product_variant_unique";`);
76
+ this.addSql(`
77
+ create unique index "stock_levels_product_variant_warehouse_unique"
78
+ on "stock_levels" (
79
+ "product_id",
80
+ coalesce("variant_id", '00000000-0000-0000-0000-000000000000'::uuid),
81
+ "warehouse_id"
82
+ );
83
+ `);
84
+ // ============================================================
85
+ // 3. warehouse_channel_assignments
86
+ // ============================================================
87
+ this.addSql(`
88
+ create table "warehouse_channel_assignments" (
89
+ "id" uuid not null,
90
+ "warehouse_id" uuid not null,
91
+ "sales_channel_id" uuid not null,
92
+ "is_default" boolean not null default false,
93
+ "sort_order" int not null default 0,
94
+ "created_at" timestamptz not null,
95
+ constraint "warehouse_channel_assignments_pkey" primary key ("id"),
96
+ constraint "wca_warehouse_fk" foreign key ("warehouse_id") references "warehouses" ("id") on delete cascade,
97
+ constraint "wca_unique_pair" unique ("warehouse_id", "sales_channel_id")
98
+ );
99
+ `);
100
+ this.addSql(`
101
+ create unique index "wca_one_default_per_channel"
102
+ on "warehouse_channel_assignments" ("sales_channel_id")
103
+ where "is_default" = true;
104
+ `);
105
+ // Seed the Default warehouse as default for every existing channel.
106
+ this.addSql(`
107
+ insert into "warehouse_channel_assignments"
108
+ ("id", "warehouse_id", "sales_channel_id", "is_default", "sort_order", "created_at")
109
+ select gen_random_uuid(), '00000000-0000-4000-8000-00000000d017'::uuid, sc."id", true, 0, now()
110
+ from "sales_channels" sc;
111
+ `);
112
+ // ============================================================
113
+ // 4. availability_notifications — extend
114
+ // ============================================================
115
+ this.addSql(`alter table "availability_notifications" add column "email" varchar(320) null;`);
116
+ this.addSql(`alter table "availability_notifications" add column "status" varchar(16) not null default 'queued';`);
117
+ this.addSql(`alter table "availability_notifications" alter column "customer_account_id" drop not null;`);
118
+ this.addSql(`
119
+ alter table "availability_notifications"
120
+ add constraint "an_recipient_check"
121
+ check ("customer_account_id" is not null or "email" is not null);
122
+ `);
123
+ this.addSql(`
124
+ alter table "availability_notifications"
125
+ add constraint "an_status_check"
126
+ check ("status" in ('queued','notified','cancelled'));
127
+ `);
128
+ this.addSql(`create index "an_product_status_idx" on "availability_notifications" ("product_id", "status");`);
129
+ // ============================================================
130
+ // 7. inventory_thresholds + global seed
131
+ // ============================================================
132
+ this.addSql(`
133
+ create table "inventory_thresholds" (
134
+ "id" uuid not null,
135
+ "scope_kind" varchar(16) not null,
136
+ "scope_id" uuid null,
137
+ "threshold_high" int null,
138
+ "threshold_medium" int null,
139
+ "threshold_low" int null,
140
+ "created_at" timestamptz not null,
141
+ "updated_at" timestamptz not null,
142
+ constraint "inventory_thresholds_pkey" primary key ("id"),
143
+ constraint "it_scope_check" check (
144
+ ("scope_kind" = 'global' and "scope_id" is null)
145
+ or ("scope_kind" <> 'global' and "scope_id" is not null)
146
+ ),
147
+ constraint "it_scope_kind_check" check ("scope_kind" in ('global','category','product'))
148
+ );
149
+ `);
150
+ this.addSql(`
151
+ create unique index "it_global_unique" on "inventory_thresholds" ("scope_kind")
152
+ where "scope_kind" = 'global';
153
+ `);
154
+ this.addSql(`
155
+ create unique index "it_scoped_unique"
156
+ on "inventory_thresholds" ("scope_kind", "scope_id")
157
+ where "scope_kind" <> 'global';
158
+ `);
159
+ this.addSql(`
160
+ insert into "inventory_thresholds"
161
+ ("id", "scope_kind", "scope_id", "threshold_high", "threshold_medium", "threshold_low", "created_at", "updated_at")
162
+ values (gen_random_uuid(), 'global', null, 100, 20, 1, now(), now());
163
+ `);
164
+ // ============================================================
165
+ // 8. stock_allocations
166
+ // ============================================================
167
+ this.addSql(`
168
+ create table "stock_allocations" (
169
+ "id" uuid not null,
170
+ "order_item_id" uuid not null,
171
+ "warehouse_id" uuid not null,
172
+ "quantity" int not null,
173
+ "is_backorder" boolean not null default false,
174
+ "created_at" timestamptz not null,
175
+ "released_at" timestamptz null,
176
+ constraint "stock_allocations_pkey" primary key ("id"),
177
+ constraint "sa_warehouse_fk" foreign key ("warehouse_id") references "warehouses" ("id") on delete restrict,
178
+ constraint "sa_qty_positive" check ("quantity" > 0),
179
+ constraint "sa_unique_per_line" unique ("order_item_id", "warehouse_id")
180
+ );
181
+ `);
182
+ this.addSql(`create index "sa_warehouse_idx" on "stock_allocations" ("warehouse_id");`);
183
+ }
184
+ async down() {
185
+ // Best-effort rollback — stock_allocations + warehouse_channel_assignments
186
+ // data is lost; existing reservations may need a manual re-`reserved+=` pass.
187
+ this.addSql(`drop table if exists "stock_allocations" cascade;`);
188
+ this.addSql(`drop table if exists "inventory_thresholds" cascade;`);
189
+ this.addSql(`drop table if exists "warehouse_channel_assignments" cascade;`);
190
+ this.addSql(`alter table "availability_notifications" drop constraint if exists "an_status_check";`);
191
+ this.addSql(`alter table "availability_notifications" drop constraint if exists "an_recipient_check";`);
192
+ this.addSql(`drop index if exists "an_product_status_idx";`);
193
+ this.addSql(`alter table "availability_notifications" drop column if exists "status";`);
194
+ this.addSql(`alter table "availability_notifications" drop column if exists "email";`);
195
+ this.addSql(`alter table "availability_notifications" alter column "customer_account_id" set not null;`);
196
+ this.addSql(`drop index if exists "stock_levels_product_variant_warehouse_unique";`);
197
+ this.addSql(`alter table "stock_levels" drop column if exists "warehouse_id";`);
198
+ this.addSql(`
199
+ create unique index "stock_levels_product_variant_unique"
200
+ on "stock_levels" ("product_id", coalesce("variant_id", '00000000-0000-0000-0000-000000000000'::uuid));
201
+ `);
202
+ this.addSql(`drop table if exists "warehouses" cascade;`);
203
+ }
204
+ }
205
+ //# sourceMappingURL=20260503T182812_inventory_workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260503T182812_inventory_workflow.js","sourceRoot":"","sources":["../../src/migrations/20260503T182812_inventory_workflow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * Adds `default_low_stock_threshold` to `warehouses`. When a Product has
4
+ * no per-product `low_stock_threshold`, this column acts as the fallback
5
+ * threshold for that warehouse's contribution to the low-stock signal.
6
+ *
7
+ * Nullable so existing rows (including the seeded `Default` warehouse)
8
+ * stay untouched until an operator opts in.
9
+ */
10
+ export declare class Migration20260611T140347InventoryWarehouseDefaultLowStockThreshold extends Migration {
11
+ up(): Promise<void>;
12
+ down(): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=20260611T140347_inventory_warehouse_default_low_stock_threshold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260611T140347_inventory_warehouse_default_low_stock_threshold.d.ts","sourceRoot":"","sources":["../../src/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;GAOG;AACH,qBAAa,kEAAmE,SAAQ,SAAS;IAChF,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAMnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAKrC"}
@@ -0,0 +1,18 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * Adds `default_low_stock_threshold` to `warehouses`. When a Product has
4
+ * no per-product `low_stock_threshold`, this column acts as the fallback
5
+ * threshold for that warehouse's contribution to the low-stock signal.
6
+ *
7
+ * Nullable so existing rows (including the seeded `Default` warehouse)
8
+ * stay untouched until an operator opts in.
9
+ */
10
+ export class Migration20260611T140347InventoryWarehouseDefaultLowStockThreshold extends Migration {
11
+ async up() {
12
+ this.addSql(`alter table "warehouses" add column "default_low_stock_threshold" int null;`);
13
+ }
14
+ async down() {
15
+ this.addSql(`alter table "warehouses" drop column if exists "default_low_stock_threshold";`);
16
+ }
17
+ }
18
+ //# sourceMappingURL=20260611T140347_inventory_warehouse_default_low_stock_threshold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260611T140347_inventory_warehouse_default_low_stock_threshold.js","sourceRoot":"","sources":["../../src/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,OAAO,kEAAmE,SAAQ,SAAS;IACtF,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CACT,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CACT,+EAA+E,CAChF,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,28 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * Per-warehouse low-stock thresholds.
4
+ *
5
+ * Adds the `product_warehouse_low_stock_thresholds` table holding (product,
6
+ * warehouse) thresholds. Only consulted when the product is in
7
+ * `per_warehouse` mode.
8
+ *
9
+ * It used to add `products.low_stock_threshold_mode` ('cumulative' |
10
+ * 'per_warehouse', default 'cumulative') and
11
+ * `products_low_stock_threshold_mode_check` as well, and to drop both on
12
+ * revert. That column is `catalog`'s — its `Product` entity maps it — and since
13
+ * feature 134 (`specs/134-paid-module-extraction/research.md` D15) it is
14
+ * created by `catalog`'s `Migration20260925T125527CatalogInventoryColumns`.
15
+ * This migration keeps its class name and lost only those statements. The
16
+ * `pwlst_product_fk` reference to `products` stays: it is a foreign key from
17
+ * this module's own table to a module it declares.
18
+ *
19
+ * Resolution chain in `per_warehouse` mode (per warehouse):
20
+ * 1. Row in `product_warehouse_low_stock_thresholds`.
21
+ * 2. `warehouses.default_low_stock_threshold` fallback.
22
+ * 3. No threshold.
23
+ */
24
+ export declare class Migration20260611T140348InventoryPerWarehouseLowStockThresholds extends Migration {
25
+ up(): Promise<void>;
26
+ down(): Promise<void>;
27
+ }
28
+ //# sourceMappingURL=20260611T140348_inventory_per_warehouse_low_stock_thresholds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260611T140348_inventory_per_warehouse_low_stock_thresholds.d.ts","sourceRoot":"","sources":["../../src/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,+DAAgE,SAAQ,SAAS;IAC7E,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAKrC"}
@@ -0,0 +1,48 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * Per-warehouse low-stock thresholds.
4
+ *
5
+ * Adds the `product_warehouse_low_stock_thresholds` table holding (product,
6
+ * warehouse) thresholds. Only consulted when the product is in
7
+ * `per_warehouse` mode.
8
+ *
9
+ * It used to add `products.low_stock_threshold_mode` ('cumulative' |
10
+ * 'per_warehouse', default 'cumulative') and
11
+ * `products_low_stock_threshold_mode_check` as well, and to drop both on
12
+ * revert. That column is `catalog`'s — its `Product` entity maps it — and since
13
+ * feature 134 (`specs/134-paid-module-extraction/research.md` D15) it is
14
+ * created by `catalog`'s `Migration20260925T125527CatalogInventoryColumns`.
15
+ * This migration keeps its class name and lost only those statements. The
16
+ * `pwlst_product_fk` reference to `products` stays: it is a foreign key from
17
+ * this module's own table to a module it declares.
18
+ *
19
+ * Resolution chain in `per_warehouse` mode (per warehouse):
20
+ * 1. Row in `product_warehouse_low_stock_thresholds`.
21
+ * 2. `warehouses.default_low_stock_threshold` fallback.
22
+ * 3. No threshold.
23
+ */
24
+ export class Migration20260611T140348InventoryPerWarehouseLowStockThresholds extends Migration {
25
+ async up() {
26
+ this.addSql(`
27
+ create table "product_warehouse_low_stock_thresholds" (
28
+ "product_id" uuid not null,
29
+ "warehouse_id" uuid not null,
30
+ "threshold" int not null,
31
+ "created_at" timestamptz not null,
32
+ "updated_at" timestamptz not null,
33
+ constraint "product_warehouse_low_stock_thresholds_pkey"
34
+ primary key ("product_id", "warehouse_id"),
35
+ constraint "pwlst_threshold_nonneg" check ("threshold" >= 0),
36
+ constraint "pwlst_product_fk"
37
+ foreign key ("product_id") references "products" ("id") on delete cascade,
38
+ constraint "pwlst_warehouse_fk"
39
+ foreign key ("warehouse_id") references "warehouses" ("id") on delete cascade
40
+ );
41
+ `);
42
+ this.addSql(`create index "pwlst_warehouse_idx" on "product_warehouse_low_stock_thresholds" ("warehouse_id");`);
43
+ }
44
+ async down() {
45
+ this.addSql(`drop table if exists "product_warehouse_low_stock_thresholds" cascade;`);
46
+ }
47
+ }
48
+ //# sourceMappingURL=20260611T140348_inventory_per_warehouse_low_stock_thresholds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260611T140348_inventory_per_warehouse_low_stock_thresholds.js","sourceRoot":"","sources":["../../src/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,+DAAgE,SAAQ,SAAS;IACnF,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;KAeX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CACT,kGAAkG,CACnG,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CACT,wEAAwE,CACzE,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,44 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * `stock_allocations.order_item_id` gains the foreign key it never had
4
+ * (D-94.1, site 1 — the family D-90 opened with `shipments.order_id`).
5
+ *
6
+ * `placeOrder` locks `stock_levels` under `PESSIMISTIC_WRITE`, increments
7
+ * `reserved` and inserts one `stock_allocations` row per (order item,
8
+ * warehouse) — all on the placement `EntityManager`, so a placement that then
9
+ * fails releases the reservation by rolling back. D-78 point 2 rules such a
10
+ * seam permanent and *declared*, and what makes the declaration honest is a
11
+ * constraint. The omission was never a decision: the same `create table`
12
+ * (`20260503T182812_inventory_workflow.ts:199-211`) constrains `warehouse_id`
13
+ * with `sa_warehouse_fk` and leaves `order_item_id` — same statement, same
14
+ * `not null` shape, carrying the unique index — bare. It tracks the module
15
+ * boundary, nothing else.
16
+ *
17
+ * `on delete restrict`, for a sharper reason than symmetry with `payments`:
18
+ * an allocation row is one half of a counter. Deleting an `order_items` row
19
+ * under `cascade` would drop its allocations and leave `stock_levels.reserved`
20
+ * overcounted for ever. `restrict` says the only correct thing — release it
21
+ * first.
22
+ *
23
+ * The column accepted unconstrained values from 2026-05-03 until this
24
+ * migration, so the constraint is preceded by an explicit orphan report:
25
+ * an operator whose data has orphans gets a sentence naming the count, the
26
+ * reserved units at stake and the first ten values, not a bare `23503`.
27
+ * Rows returned mean stock is reserved for a line that no longer exists and
28
+ * no code path will ever release it. Remedy, in order: export the rows,
29
+ * delete them, and — because `stock_allocations` carries no product or
30
+ * variant — recompute `stock_levels.reserved` from the surviving allocations
31
+ * in a maintenance window (the query is in
32
+ * `specs/077-f3-consequence-rulings/co-transactional-fk-family.md` § D-94.2).
33
+ * Do not run the recompute blind: a deployment with no orphans does not need
34
+ * it, and it is the one step that touches rows the defect did not.
35
+ *
36
+ * `inventory` declares `orders` in its manifest `dependencies` for this edge
37
+ * (AGENTS.md § Migrations item 4). It is the one constraint of the four that
38
+ * closes no cycle.
39
+ */
40
+ export declare class Migration20260818T081243InventoryStockAllocationOrderItemFk extends Migration {
41
+ up(): Promise<void>;
42
+ down(): Promise<void>;
43
+ }
44
+ //# sourceMappingURL=20260818T081243_inventory_stock_allocation_order_item_fk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260818T081243_inventory_stock_allocation_order_item_fk.d.ts","sourceRoot":"","sources":["../../src/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBAAa,2DAA4D,SAAQ,SAAS;IACzE,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAsCnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAKrC"}
@@ -0,0 +1,81 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * `stock_allocations.order_item_id` gains the foreign key it never had
4
+ * (D-94.1, site 1 — the family D-90 opened with `shipments.order_id`).
5
+ *
6
+ * `placeOrder` locks `stock_levels` under `PESSIMISTIC_WRITE`, increments
7
+ * `reserved` and inserts one `stock_allocations` row per (order item,
8
+ * warehouse) — all on the placement `EntityManager`, so a placement that then
9
+ * fails releases the reservation by rolling back. D-78 point 2 rules such a
10
+ * seam permanent and *declared*, and what makes the declaration honest is a
11
+ * constraint. The omission was never a decision: the same `create table`
12
+ * (`20260503T182812_inventory_workflow.ts:199-211`) constrains `warehouse_id`
13
+ * with `sa_warehouse_fk` and leaves `order_item_id` — same statement, same
14
+ * `not null` shape, carrying the unique index — bare. It tracks the module
15
+ * boundary, nothing else.
16
+ *
17
+ * `on delete restrict`, for a sharper reason than symmetry with `payments`:
18
+ * an allocation row is one half of a counter. Deleting an `order_items` row
19
+ * under `cascade` would drop its allocations and leave `stock_levels.reserved`
20
+ * overcounted for ever. `restrict` says the only correct thing — release it
21
+ * first.
22
+ *
23
+ * The column accepted unconstrained values from 2026-05-03 until this
24
+ * migration, so the constraint is preceded by an explicit orphan report:
25
+ * an operator whose data has orphans gets a sentence naming the count, the
26
+ * reserved units at stake and the first ten values, not a bare `23503`.
27
+ * Rows returned mean stock is reserved for a line that no longer exists and
28
+ * no code path will ever release it. Remedy, in order: export the rows,
29
+ * delete them, and — because `stock_allocations` carries no product or
30
+ * variant — recompute `stock_levels.reserved` from the surviving allocations
31
+ * in a maintenance window (the query is in
32
+ * `specs/077-f3-consequence-rulings/co-transactional-fk-family.md` § D-94.2).
33
+ * Do not run the recompute blind: a deployment with no orphans does not need
34
+ * it, and it is the one step that touches rows the defect did not.
35
+ *
36
+ * `inventory` declares `orders` in its manifest `dependencies` for this edge
37
+ * (AGENTS.md § Migrations item 4). It is the one constraint of the four that
38
+ * closes no cycle.
39
+ */
40
+ export class Migration20260818T081243InventoryStockAllocationOrderItemFk extends Migration {
41
+ async up() {
42
+ this.addSql(`
43
+ do $$
44
+ declare
45
+ orphan_count bigint;
46
+ reserved_units bigint;
47
+ sample text;
48
+ begin
49
+ select count(*),
50
+ coalesce(sum(a."quantity") filter (where a."released_at" is null), 0)
51
+ into orphan_count, reserved_units
52
+ from "stock_allocations" a
53
+ left join "order_items" oi on oi."id" = a."order_item_id"
54
+ where oi."id" is null;
55
+
56
+ if orphan_count > 0 then
57
+ select string_agg(x."order_item_id"::text, ', ') into sample
58
+ from (
59
+ select distinct a."order_item_id"
60
+ from "stock_allocations" a
61
+ left join "order_items" oi on oi."id" = a."order_item_id"
62
+ where oi."id" is null
63
+ limit 10
64
+ ) x;
65
+ raise exception
66
+ 'stock_allocations_order_item_fk cannot be added: % orphaned stock_allocations.order_item_id value(s) reference no order_items row, holding % reserved unit(s) no code path will release (first ten: %). Export and delete those allocations, then recompute stock_levels.reserved from the surviving ones, then re-run the migration.',
67
+ orphan_count, reserved_units, sample;
68
+ end if;
69
+ end $$;
70
+ `);
71
+ this.addSql(`
72
+ alter table "stock_allocations"
73
+ add constraint "stock_allocations_order_item_fk" foreign key ("order_item_id")
74
+ references "order_items" ("id") on delete restrict;
75
+ `);
76
+ }
77
+ async down() {
78
+ this.addSql(`alter table "stock_allocations" drop constraint if exists "stock_allocations_order_item_fk";`);
79
+ }
80
+ }
81
+ //# sourceMappingURL=20260818T081243_inventory_stock_allocation_order_item_fk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260818T081243_inventory_stock_allocation_order_item_fk.js","sourceRoot":"","sources":["../../src/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,OAAO,2DAA4D,SAAQ,SAAS;IAC/E,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA4BX,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CACT,8FAA8F,CAC/F,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,109 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * D-187 — `availability_notifications` gains its organisation, and a row that
4
+ * names a customer account carries one.
5
+ *
6
+ * Feature 087 Group B, class 3 of 4 (`specs/087-tenant-scope-enforcement/`).
7
+ * `AvailabilityNotification` is `@CustomerScoped`, and until this migration the
8
+ * `allowed-set` arm of `customerFilterCond` had no column to grant on: a sales
9
+ * representative assigned to the buyer's own organisation was shown **none** of
10
+ * their back-in-stock subscriptions and told so, through the
11
+ * `ORGANIZATION_ATTRIBUTION_PENDING` notice the refusing arm records. The
12
+ * column is what turns that into an answer.
13
+ *
14
+ * ## The column and the read arrive together, on purpose
15
+ *
16
+ * `customerOrganizationColumn` asks the ORM's own metadata whether the filtered
17
+ * entity carries `organizationId`, **per query**, so the entity property in
18
+ * this same merge request is what switches the grant on — there is no third
19
+ * artefact, no flag and no staging. At the same instant the notice retires
20
+ * itself: it is keyed on the column's *absence*.
21
+ *
22
+ * That pairing is why this migration is not "column and backfill". From the
23
+ * moment the grant is live, a row inserted without an organisation is invisible
24
+ * to the representative who serves that organisation, on a screen that has just
25
+ * stopped explaining itself — and MikroORM applies no filter to `INSERT`
26
+ * (`r1-spike.md` §4, measured), so the filter cannot refuse it. The `CHECK`
27
+ * below is the only refusal an `INSERT` has.
28
+ *
29
+ * ## An implication, not an equivalence
30
+ *
31
+ * `customer_account_id is null or organization_id is not null` is FR-010 and
32
+ * FR-011 together and nothing wider: an **owned** row has an organisation, an
33
+ * **ownerless** row need not. This table is ownerless by construction for the
34
+ * storefront's anonymous "notify me when in stock" dialog — feature 010 dropped
35
+ * `NOT NULL` from `customer_account_id` and added `an_recipient_check`
36
+ * (`20260503T182812_inventory_workflow.ts`), whose whole purpose is to admit a
37
+ * row whose only recipient is an e-mail address — and who such a row belongs to
38
+ * is R-6's open question. The equivalence would answer it in the schema, which
39
+ * D-187 declines to do.
40
+ *
41
+ * Unlike `pwa`, nothing here can produce the opposite shape. There is no
42
+ * association write on this table: `customer_account_id` is written once, in
43
+ * `AvailabilityNotificationService.subscribe`'s `em.create`, and never assigned
44
+ * on an existing row (grep of `packages/modules/inventory/src` for
45
+ * `customerAccountId`: one write, the rest reads). So an ownerless row carrying
46
+ * an organisation is not reachable, and the shape the implication leaves open
47
+ * is empty here rather than held by a test.
48
+ *
49
+ * ## No foreign key
50
+ *
51
+ * Matching `Cart`, `Comparison` and `PushSubscription`, none of which carries
52
+ * one. D-187 withdraws `r1-spike.md` §8's `on delete set null` recommendation
53
+ * as contradicting FR-011: it produces a row that names an account and no
54
+ * organisation, which is precisely the state this migration makes unreachable,
55
+ * and under the constraint below it would abort an unrelated organisation
56
+ * delete with a message about a table the operator was not touching. If one is
57
+ * ever taken here it must be `restrict`, and it is not this migration's
58
+ * decision.
59
+ *
60
+ * ## Derive, then count, then refuse — never delete
61
+ *
62
+ * The derivation is the owning account's own organisation. **On this table the
63
+ * refusal is a real branch, not a formality**, and that is why it is written
64
+ * with as much care as the derivation: like `push_subscriptions` and unlike
65
+ * `comparisons`, `availability_notifications` has **no foreign key** on
66
+ * `customer_account_id` — the core foundation migration
67
+ * (`20260424T165847_core_foundation_init.ts`) declares the column and an index
68
+ * and constrains nothing — so a row naming an account that is gone is
69
+ * representable here.
70
+ *
71
+ * It is nevertheless empty today, and the reason is worth knowing rather than
72
+ * hoping: a customer account is never hard-deleted in this tree.
73
+ * `customer-account-lifecycle-ports.ts` soft-deletes, restores and
74
+ * **anonymises** — the last one rewrites the e-mail and the name and keeps the
75
+ * row — and every one of those still satisfies `organization_id NOT NULL`
76
+ * (D-178), so it still derives. Change any of that and the count below is the
77
+ * only thing that would notice.
78
+ *
79
+ * It raises with the count and up to twenty ids and deletes nothing (D-184), in
80
+ * the shape `20260825T141659_customer_accounts_organization_required.ts`
81
+ * established and `20260830T112911_carts_organization_attribution_check.ts` and
82
+ * `20260830T172022_pwa_organization_attribution.ts` repeated. Deleting would be
83
+ * particularly wrong here: a queued subscription is a promise to a customer
84
+ * that they will be told when the product returns, and a migration is not the
85
+ * place to break one.
86
+ *
87
+ * ## Ownership, and nothing to declare in the manifest
88
+ *
89
+ * `availability_notifications` is the one Group B table whose `create table` is
90
+ * not its module's — it comes from the core foundation migration. This column
91
+ * migration is still `inventory`'s, because the entity is, and module ownership
92
+ * is what the registry and a hard uninstall key on (D-142).
93
+ *
94
+ * `inventory` already declares both `customer_accounts` and `organizations` in
95
+ * its manifest `dependencies`, so the table this reads is created before this
96
+ * runs. No foreign key is added, so `fk-dependency-drift` has nothing to say
97
+ * either, and a migration naming another module's table is outside
98
+ * `check:module-boundary`'s population by that check's own rule.
99
+ *
100
+ * The standing guard is
101
+ * `backend/test/integration/tenancy/customer-scoped-organization-completeness.test.ts`,
102
+ * which derives its population from the ORM's metadata rather than from a list
103
+ * — so this class is covered by gaining the column, with no edit to that file.
104
+ */
105
+ export declare class Migration20260830T182139InventoryOrganizationAttribution extends Migration {
106
+ up(): Promise<void>;
107
+ down(): Promise<void>;
108
+ }
109
+ //# sourceMappingURL=20260830T182139_inventory_organization_attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260830T182139_inventory_organization_attribution.d.ts","sourceRoot":"","sources":["../../src/migrations/20260830T182139_inventory_organization_attribution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsGG;AACH,qBAAa,wDAAyD,SAAQ,SAAS;IACtE,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAqEnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAarC"}