@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.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/admin/api/warehouses-client.d.ts +22 -0
- package/dist/admin/api/warehouses-client.d.ts.map +1 -0
- package/dist/admin/api/warehouses-client.js +32 -0
- package/dist/admin/api/warehouses-client.js.map +1 -0
- package/dist/admin/index.d.ts +43 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +190 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/AvailabilityNotificationsPage.d.ts +15 -0
- package/dist/admin/pages/AvailabilityNotificationsPage.d.ts.map +1 -0
- package/dist/admin/pages/AvailabilityNotificationsPage.js +111 -0
- package/dist/admin/pages/AvailabilityNotificationsPage.js.map +1 -0
- package/dist/admin/pages/InventoryPage.d.ts +17 -0
- package/dist/admin/pages/InventoryPage.d.ts.map +1 -0
- package/dist/admin/pages/InventoryPage.js +135 -0
- package/dist/admin/pages/InventoryPage.js.map +1 -0
- package/dist/admin/pages/LowStockPage.d.ts +16 -0
- package/dist/admin/pages/LowStockPage.d.ts.map +1 -0
- package/dist/admin/pages/LowStockPage.js +61 -0
- package/dist/admin/pages/LowStockPage.js.map +1 -0
- package/dist/admin/pages/StockImportWizard.d.ts +17 -0
- package/dist/admin/pages/StockImportWizard.d.ts.map +1 -0
- package/dist/admin/pages/StockImportWizard.js +111 -0
- package/dist/admin/pages/StockImportWizard.js.map +1 -0
- package/dist/admin/pages/WarehouseEditor.d.ts +12 -0
- package/dist/admin/pages/WarehouseEditor.d.ts.map +1 -0
- package/dist/admin/pages/WarehouseEditor.js +194 -0
- package/dist/admin/pages/WarehouseEditor.js.map +1 -0
- package/dist/admin/pages/WarehousesList.d.ts +12 -0
- package/dist/admin/pages/WarehousesList.d.ts.map +1 -0
- package/dist/admin/pages/WarehousesList.js +69 -0
- package/dist/admin/pages/WarehousesList.js.map +1 -0
- package/dist/admin/zones/ChannelWarehouses.d.ts +49 -0
- package/dist/admin/zones/ChannelWarehouses.d.ts.map +1 -0
- package/dist/admin/zones/ChannelWarehouses.js +109 -0
- package/dist/admin/zones/ChannelWarehouses.js.map +1 -0
- package/dist/backend/demo/reset.d.ts +18 -0
- package/dist/backend/demo/reset.d.ts.map +1 -0
- package/dist/backend/demo/reset.js +20 -0
- package/dist/backend/demo/reset.js.map +1 -0
- package/dist/backend/demo/rows.d.ts +38 -0
- package/dist/backend/demo/rows.d.ts.map +1 -0
- package/dist/backend/demo/rows.js +38 -0
- package/dist/backend/demo/rows.js.map +1 -0
- package/dist/backend/demo/seed.d.ts +26 -0
- package/dist/backend/demo/seed.d.ts.map +1 -0
- package/dist/backend/demo/seed.js +65 -0
- package/dist/backend/demo/seed.js.map +1 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts +11 -0
- package/dist/backend/email-templates/transactional-defaults.d.ts.map +1 -0
- package/dist/backend/email-templates/transactional-defaults.js +49 -0
- package/dist/backend/email-templates/transactional-defaults.js.map +1 -0
- package/dist/backend/entities/availability-notification.entity.d.ts +49 -0
- package/dist/backend/entities/availability-notification.entity.d.ts.map +1 -0
- package/dist/backend/entities/availability-notification.entity.js +93 -0
- package/dist/backend/entities/availability-notification.entity.js.map +1 -0
- package/dist/backend/entities/inventory-threshold.entity.d.ts +24 -0
- package/dist/backend/entities/inventory-threshold.entity.d.ts.map +1 -0
- package/dist/backend/entities/inventory-threshold.entity.js +61 -0
- package/dist/backend/entities/inventory-threshold.entity.js.map +1 -0
- package/dist/backend/entities/product-warehouse-low-stock-threshold.entity.d.ts +15 -0
- package/dist/backend/entities/product-warehouse-low-stock-threshold.entity.d.ts.map +1 -0
- package/dist/backend/entities/product-warehouse-low-stock-threshold.entity.js +51 -0
- package/dist/backend/entities/product-warehouse-low-stock-threshold.entity.js.map +1 -0
- package/dist/backend/entities/stock-allocation.entity.d.ts +22 -0
- package/dist/backend/entities/stock-allocation.entity.d.ts.map +1 -0
- package/dist/backend/entities/stock-allocation.entity.js +69 -0
- package/dist/backend/entities/stock-allocation.entity.js.map +1 -0
- package/dist/backend/entities/stock-level.entity.d.ts +24 -0
- package/dist/backend/entities/stock-level.entity.d.ts.map +1 -0
- package/dist/backend/entities/stock-level.entity.js +74 -0
- package/dist/backend/entities/stock-level.entity.js.map +1 -0
- package/dist/backend/entities/warehouse-channel-assignment.entity.d.ts +17 -0
- package/dist/backend/entities/warehouse-channel-assignment.entity.d.ts.map +1 -0
- package/dist/backend/entities/warehouse-channel-assignment.entity.js +60 -0
- package/dist/backend/entities/warehouse-channel-assignment.entity.js.map +1 -0
- package/dist/backend/entities/warehouse.entity.d.ts +37 -0
- package/dist/backend/entities/warehouse.entity.d.ts.map +1 -0
- package/dist/backend/entities/warehouse.entity.js +90 -0
- package/dist/backend/entities/warehouse.entity.js.map +1 -0
- package/dist/backend/index.d.ts +139 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +351 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +113 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +75 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/prompt-tools.d.ts +23 -0
- package/dist/backend/prompt-tools.d.ts.map +1 -0
- package/dist/backend/prompt-tools.js +77 -0
- package/dist/backend/prompt-tools.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +62 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +383 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.d.ts +40 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +263 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/audit-references.d.ts +15 -0
- package/dist/backend/services/audit-references.d.ts.map +1 -0
- package/dist/backend/services/audit-references.js +27 -0
- package/dist/backend/services/audit-references.js.map +1 -0
- package/dist/backend/services/availability-notification-service.d.ts +155 -0
- package/dist/backend/services/availability-notification-service.d.ts.map +1 -0
- package/dist/backend/services/availability-notification-service.js +363 -0
- package/dist/backend/services/availability-notification-service.js.map +1 -0
- package/dist/backend/services/availability-worker.d.ts +57 -0
- package/dist/backend/services/availability-worker.d.ts.map +1 -0
- package/dist/backend/services/availability-worker.js +135 -0
- package/dist/backend/services/availability-worker.js.map +1 -0
- package/dist/backend/services/csv-stock-importer.d.ts +52 -0
- package/dist/backend/services/csv-stock-importer.d.ts.map +1 -0
- package/dist/backend/services/csv-stock-importer.js +154 -0
- package/dist/backend/services/csv-stock-importer.js.map +1 -0
- package/dist/backend/services/display-band-resolver.d.ts +19 -0
- package/dist/backend/services/display-band-resolver.d.ts.map +1 -0
- package/dist/backend/services/display-band-resolver.js +21 -0
- package/dist/backend/services/display-band-resolver.js.map +1 -0
- package/dist/backend/services/effective-fulfilment-strategy.d.ts +16 -0
- package/dist/backend/services/effective-fulfilment-strategy.d.ts.map +1 -0
- package/dist/backend/services/effective-fulfilment-strategy.js +16 -0
- package/dist/backend/services/effective-fulfilment-strategy.js.map +1 -0
- package/dist/backend/services/fulfilment-strategy-resolver.d.ts +13 -0
- package/dist/backend/services/fulfilment-strategy-resolver.d.ts.map +1 -0
- package/dist/backend/services/fulfilment-strategy-resolver.js +13 -0
- package/dist/backend/services/fulfilment-strategy-resolver.js.map +1 -0
- package/dist/backend/services/inventory-read-port.d.ts +65 -0
- package/dist/backend/services/inventory-read-port.d.ts.map +1 -0
- package/dist/backend/services/inventory-read-port.js +168 -0
- package/dist/backend/services/inventory-read-port.js.map +1 -0
- package/dist/backend/services/inventory-reservation-apply-port.d.ts +62 -0
- package/dist/backend/services/inventory-reservation-apply-port.d.ts.map +1 -0
- package/dist/backend/services/inventory-reservation-apply-port.js +134 -0
- package/dist/backend/services/inventory-reservation-apply-port.js.map +1 -0
- package/dist/backend/services/low-stock-alert-service.d.ts +87 -0
- package/dist/backend/services/low-stock-alert-service.d.ts.map +1 -0
- package/dist/backend/services/low-stock-alert-service.js +164 -0
- package/dist/backend/services/low-stock-alert-service.js.map +1 -0
- package/dist/backend/services/product-threshold-write.service.d.ts +38 -0
- package/dist/backend/services/product-threshold-write.service.d.ts.map +1 -0
- package/dist/backend/services/product-threshold-write.service.js +80 -0
- package/dist/backend/services/product-threshold-write.service.js.map +1 -0
- package/dist/backend/services/stock-import.service.d.ts +43 -0
- package/dist/backend/services/stock-import.service.d.ts.map +1 -0
- package/dist/backend/services/stock-import.service.js +124 -0
- package/dist/backend/services/stock-import.service.js.map +1 -0
- package/dist/backend/services/stock-level-service.d.ts +146 -0
- package/dist/backend/services/stock-level-service.d.ts.map +1 -0
- package/dist/backend/services/stock-level-service.js +489 -0
- package/dist/backend/services/stock-level-service.js.map +1 -0
- package/dist/backend/services/threshold-admin-service.d.ts +73 -0
- package/dist/backend/services/threshold-admin-service.d.ts.map +1 -0
- package/dist/backend/services/threshold-admin-service.js +167 -0
- package/dist/backend/services/threshold-admin-service.js.map +1 -0
- package/dist/backend/services/threshold-resolver.d.ts +23 -0
- package/dist/backend/services/threshold-resolver.d.ts.map +1 -0
- package/dist/backend/services/threshold-resolver.js +29 -0
- package/dist/backend/services/threshold-resolver.js.map +1 -0
- package/dist/backend/services/threshold-settings-mirror.d.ts +55 -0
- package/dist/backend/services/threshold-settings-mirror.d.ts.map +1 -0
- package/dist/backend/services/threshold-settings-mirror.js +84 -0
- package/dist/backend/services/threshold-settings-mirror.js.map +1 -0
- package/dist/backend/services/warehouse-channel-reconciler.d.ts +19 -0
- package/dist/backend/services/warehouse-channel-reconciler.d.ts.map +1 -0
- package/dist/backend/services/warehouse-channel-reconciler.js +65 -0
- package/dist/backend/services/warehouse-channel-reconciler.js.map +1 -0
- package/dist/backend/services/warehouse-channel-service.d.ts +57 -0
- package/dist/backend/services/warehouse-channel-service.d.ts.map +1 -0
- package/dist/backend/services/warehouse-channel-service.js +205 -0
- package/dist/backend/services/warehouse-channel-service.js.map +1 -0
- package/dist/backend/services/warehouse-country-reference.d.ts +14 -0
- package/dist/backend/services/warehouse-country-reference.d.ts.map +1 -0
- package/dist/backend/services/warehouse-country-reference.js +27 -0
- package/dist/backend/services/warehouse-country-reference.js.map +1 -0
- package/dist/backend/services/warehouse-service.d.ts +86 -0
- package/dist/backend/services/warehouse-service.d.ts.map +1 -0
- package/dist/backend/services/warehouse-service.js +267 -0
- package/dist/backend/services/warehouse-service.js.map +1 -0
- package/dist/manifest.d.ts +219 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +377 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260503T182812_inventory_workflow.d.ts +39 -0
- package/dist/migrations/20260503T182812_inventory_workflow.d.ts.map +1 -0
- package/dist/migrations/20260503T182812_inventory_workflow.js +205 -0
- package/dist/migrations/20260503T182812_inventory_workflow.js.map +1 -0
- package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.d.ts +14 -0
- package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.d.ts.map +1 -0
- package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.js +18 -0
- package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.js.map +1 -0
- package/dist/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.d.ts +28 -0
- package/dist/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.d.ts.map +1 -0
- package/dist/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.js +48 -0
- package/dist/migrations/20260611T140348_inventory_per_warehouse_low_stock_thresholds.js.map +1 -0
- package/dist/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.d.ts +44 -0
- package/dist/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.d.ts.map +1 -0
- package/dist/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.js +81 -0
- package/dist/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.js.map +1 -0
- package/dist/migrations/20260830T182139_inventory_organization_attribution.d.ts +109 -0
- package/dist/migrations/20260830T182139_inventory_organization_attribution.d.ts.map +1 -0
- package/dist/migrations/20260830T182139_inventory_organization_attribution.js +178 -0
- package/dist/migrations/20260830T182139_inventory_organization_attribution.js.map +1 -0
- package/dist/migrations/20260912T125716_inventory_organization_warehouses.d.ts +34 -0
- package/dist/migrations/20260912T125716_inventory_organization_warehouses.d.ts.map +1 -0
- package/dist/migrations/20260912T125716_inventory_organization_warehouses.js +50 -0
- package/dist/migrations/20260912T125716_inventory_organization_warehouses.js.map +1 -0
- package/dist/migrations/index.d.ts +32 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +39 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +183 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/inventory.md +177 -0
- package/i18n/en.json +32 -0
- package/i18n/pl.json +32 -0
- package/package.json +105 -0
- 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":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,OAAO,yCAA0C,SAAQ,SAAS;IAC7D,KAAK,CAAC,EAAE;QACf,+DAA+D;QAC/D,gBAAgB;QAChB,+DAA+D;QAC/D,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;KAiBX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,kEAAkE,CAAC,CAAC;QAEhF,4EAA4E;QAC5E,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;QAEH,+DAA+D;QAC/D,6CAA6C;QAC7C,+DAA+D;QAC/D,IAAI,CAAC,MAAM,CAAC,iEAAiE,CAAC,CAAC;QAC/E,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,sEAAsE,CAAC,CAAC;QACpF,IAAI,CAAC,MAAM,CAAC,6DAA6D,CAAC,CAAC;QAC3E,IAAI,CAAC,MAAM,CAAC;;;;;;;KAOX,CAAC,CAAC;QAEH,+DAA+D;QAC/D,mCAAmC;QACnC,+DAA+D;QAC/D,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;KAYX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;QACH,oEAAoE;QACpE,IAAI,CAAC,MAAM,CAAC;;;;;KAKX,CAAC,CAAC;QAEH,+DAA+D;QAC/D,yCAAyC;QACzC,+DAA+D;QAC/D,IAAI,CAAC,MAAM,CAAC,gFAAgF,CAAC,CAAC;QAC9F,IAAI,CAAC,MAAM,CAAC,qGAAqG,CAAC,CAAC;QACnH,IAAI,CAAC,MAAM,CAAC,4FAA4F,CAAC,CAAC;QAC1G,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,gGAAgG,CAAC,CAAC;QAE9G,+DAA+D;QAC/D,wCAAwC;QACxC,+DAA+D;QAC/D,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;KAiBX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC;;;KAGX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;QAEH,+DAA+D;QAC/D,uBAAuB;QACvB,+DAA+D;QAC/D,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;KAcX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,0EAA0E,CAAC,CAAC;IAC1F,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,2EAA2E;QAC3E,8EAA8E;QAC9E,IAAI,CAAC,MAAM,CAAC,mDAAmD,CAAC,CAAC;QACjE,IAAI,CAAC,MAAM,CAAC,sDAAsD,CAAC,CAAC;QACpE,IAAI,CAAC,MAAM,CAAC,+DAA+D,CAAC,CAAC;QAE7E,IAAI,CAAC,MAAM,CAAC,uFAAuF,CAAC,CAAC;QACrG,IAAI,CAAC,MAAM,CAAC,0FAA0F,CAAC,CAAC;QACxG,IAAI,CAAC,MAAM,CAAC,+CAA+C,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,CAAC,0EAA0E,CAAC,CAAC;QACxF,IAAI,CAAC,MAAM,CAAC,yEAAyE,CAAC,CAAC;QACvF,IAAI,CAAC,MAAM,CAAC,2FAA2F,CAAC,CAAC;QAEzG,IAAI,CAAC,MAAM,CAAC,uEAAuE,CAAC,CAAC;QACrF,IAAI,CAAC,MAAM,CAAC,kEAAkE,CAAC,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC;;;KAGX,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,4CAA4C,CAAC,CAAC;IAC5D,CAAC;CACF"}
|
|
@@ -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
|
package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.d.ts.map
ADDED
|
@@ -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
|
package/dist/migrations/20260611T140347_inventory_warehouse_default_low_stock_threshold.js.map
ADDED
|
@@ -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"}
|