@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,178 @@
|
|
|
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 class Migration20260830T182139InventoryOrganizationAttribution extends Migration {
|
|
106
|
+
async up() {
|
|
107
|
+
// 1. The column. Nullable, because an anonymous subscriber legitimately has
|
|
108
|
+
// none (FR-011) and `an_recipient_check` exists to admit exactly that
|
|
109
|
+
// row.
|
|
110
|
+
this.addSql(`alter table "availability_notifications" add column "organization_id" uuid null;`);
|
|
111
|
+
// 2. Derive the organisation from the account that owns the subscription.
|
|
112
|
+
// Every account carries one since D-178, including a soft-deleted or
|
|
113
|
+
// anonymised one, so this is total for every row whose account is still
|
|
114
|
+
// there. Step 3 is about the rows for which it is not.
|
|
115
|
+
this.addSql(`
|
|
116
|
+
update "availability_notifications" an
|
|
117
|
+
set "organization_id" = ca."organization_id"
|
|
118
|
+
from "customer_accounts" ca
|
|
119
|
+
where ca."id" = an."customer_account_id"
|
|
120
|
+
and an."organization_id" is null;
|
|
121
|
+
`);
|
|
122
|
+
// 3. Count what is left and refuse. This table has no foreign key on
|
|
123
|
+
// `customer_account_id`, so a dangling account id is representable and
|
|
124
|
+
// this branch is real. It deletes nothing (D-184).
|
|
125
|
+
this.addSql(`
|
|
126
|
+
do $$
|
|
127
|
+
declare
|
|
128
|
+
remaining bigint;
|
|
129
|
+
sample text;
|
|
130
|
+
begin
|
|
131
|
+
select count(*) into remaining
|
|
132
|
+
from "availability_notifications"
|
|
133
|
+
where "customer_account_id" is not null
|
|
134
|
+
and "organization_id" is null;
|
|
135
|
+
if remaining > 0 then
|
|
136
|
+
select string_agg(id::text, ', ') into sample from (
|
|
137
|
+
select "id" from "availability_notifications"
|
|
138
|
+
where "customer_account_id" is not null
|
|
139
|
+
and "organization_id" is null
|
|
140
|
+
order by "id" limit 20
|
|
141
|
+
) s;
|
|
142
|
+
raise exception
|
|
143
|
+
'D-187: % availability_notifications row(s) still name a customer account with organization_id IS NULL after derivation. First ids: %. Every customer account has an organization (D-178) and none is ever hard-deleted, so these rows name an account that is gone. Do not delete them - find out how they got here.',
|
|
144
|
+
remaining, sample;
|
|
145
|
+
end if;
|
|
146
|
+
end $$;
|
|
147
|
+
`);
|
|
148
|
+
// 4. The constraint.
|
|
149
|
+
this.addSql(`
|
|
150
|
+
alter table "availability_notifications"
|
|
151
|
+
add constraint "availability_notifications_organization_attribution_chk"
|
|
152
|
+
check ("customer_account_id" is null or "organization_id" is not null);
|
|
153
|
+
`);
|
|
154
|
+
// 5. The index the entity property declares. Whole rather than partial, and
|
|
155
|
+
// named `<table>_<column>_index`, because that is this table's own style
|
|
156
|
+
// for an entity-declared single-column index:
|
|
157
|
+
// `availability_notifications_customer_account_id_index` is what
|
|
158
|
+
// `@Index()` on `customerAccountId` produced and it is unfiltered. The
|
|
159
|
+
// `an_*` names on this table belong to the hand-written composite index
|
|
160
|
+
// and the two check constraints, which are a different kind of object.
|
|
161
|
+
// `pwa`'s index is partial for the same reason in reverse — it followed
|
|
162
|
+
// `push_subscriptions_customer_idx`, which is partial.
|
|
163
|
+
this.addSql(`
|
|
164
|
+
create index "availability_notifications_organization_id_index"
|
|
165
|
+
on "availability_notifications" ("organization_id");
|
|
166
|
+
`);
|
|
167
|
+
}
|
|
168
|
+
async down() {
|
|
169
|
+
// The constraint, the index and the column. The organisations step 2
|
|
170
|
+
// derived go with the column they were written into — there is nothing to
|
|
171
|
+
// preserve, because each of them was already implied by the account its row
|
|
172
|
+
// names.
|
|
173
|
+
this.addSql(`alter table "availability_notifications" drop constraint if exists "availability_notifications_organization_attribution_chk";`);
|
|
174
|
+
this.addSql(`drop index if exists "availability_notifications_organization_id_index";`);
|
|
175
|
+
this.addSql(`alter table "availability_notifications" drop column if exists "organization_id";`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=20260830T182139_inventory_organization_attribution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260830T182139_inventory_organization_attribution.js","sourceRoot":"","sources":["../../src/migrations/20260830T182139_inventory_organization_attribution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsGG;AACH,MAAM,OAAO,wDAAyD,SAAQ,SAAS;IAC5E,KAAK,CAAC,EAAE;QACf,4EAA4E;QAC5E,yEAAyE;QACzE,UAAU;QACV,IAAI,CAAC,MAAM,CACT,kFAAkF,CACnF,CAAC;QAEF,0EAA0E;QAC1E,wEAAwE;QACxE,2EAA2E;QAC3E,0DAA0D;QAC1D,IAAI,CAAC,MAAM,CAAC;;;;;;KAMX,CAAC,CAAC;QAEH,qEAAqE;QACrE,0EAA0E;QAC1E,sDAAsD;QACtD,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;KAsBX,CAAC,CAAC;QAEH,qBAAqB;QACrB,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAC;QAEH,4EAA4E;QAC5E,4EAA4E;QAC5E,iDAAiD;QACjD,oEAAoE;QACpE,0EAA0E;QAC1E,2EAA2E;QAC3E,0EAA0E;QAC1E,2EAA2E;QAC3E,0DAA0D;QAC1D,IAAI,CAAC,MAAM,CAAC;;;KAGX,CAAC,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,qEAAqE;QACrE,0EAA0E;QAC1E,4EAA4E;QAC5E,SAAS;QACT,IAAI,CAAC,MAAM,CACT,+HAA+H,CAChI,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,0EAA0E,CAAC,CAAC;QACxF,IAAI,CAAC,MAAM,CACT,mFAAmF,CACpF,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* The `organization_warehouses` bridge — the warehouses an organization is
|
|
4
|
+
* assigned (feature 026).
|
|
5
|
+
*
|
|
6
|
+
* It was created by `organizations`' frozen
|
|
7
|
+
* `Migration20260611T140349OrganizationsConsolidation` until
|
|
8
|
+
* `specs/120-migration-closure-bridge-ownership/` Phase 3, where its
|
|
9
|
+
* `warehouses` foreign key named a table `organizations` neither owns nor
|
|
10
|
+
* declares. That is D-226's bridge rule one namespace over: a bridge between
|
|
11
|
+
* an always-present near side (`organizations`) and a switchable far side
|
|
12
|
+
* (`inventory`) belongs to the far side, because only the far side can be
|
|
13
|
+
* ordered after both tables — `inventory` declares `organizations`, and
|
|
14
|
+
* `organizations` cannot declare `inventory` without inverting an edge that
|
|
15
|
+
* already runs the other way.
|
|
16
|
+
*
|
|
17
|
+
* So an instance that does not install `inventory` no longer carries a
|
|
18
|
+
* migration whose foreign key names `warehouses`, and one that does gets the
|
|
19
|
+
* bridge with its far side. The payoff is the one the rule promises:
|
|
20
|
+
* `module:uninstall --hard inventory` now reverts this table, because a hard
|
|
21
|
+
* uninstall reverts by registry `moduleId`.
|
|
22
|
+
*
|
|
23
|
+
* `if not exists`, because every database that has already applied the
|
|
24
|
+
* frozen migration has this table. The storage keys on the class name and
|
|
25
|
+
* holds no checksum, so the reduced frozen body is not re-offered there and
|
|
26
|
+
* this migration is the no-op it reads as; on a fresh database it is the
|
|
27
|
+
* creation. The statements are the frozen ones verbatim — same columns, same
|
|
28
|
+
* primary key, same two foreign keys — so the two paths reach one schema.
|
|
29
|
+
*/
|
|
30
|
+
export declare class Migration20260912T125716InventoryOrganizationWarehouses extends Migration {
|
|
31
|
+
up(): Promise<void>;
|
|
32
|
+
down(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=20260912T125716_inventory_organization_warehouses.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260912T125716_inventory_organization_warehouses.d.ts","sourceRoot":"","sources":["../../src/migrations/20260912T125716_inventory_organization_warehouses.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,uDAAwD,SAAQ,SAAS;IACrE,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* The `organization_warehouses` bridge — the warehouses an organization is
|
|
4
|
+
* assigned (feature 026).
|
|
5
|
+
*
|
|
6
|
+
* It was created by `organizations`' frozen
|
|
7
|
+
* `Migration20260611T140349OrganizationsConsolidation` until
|
|
8
|
+
* `specs/120-migration-closure-bridge-ownership/` Phase 3, where its
|
|
9
|
+
* `warehouses` foreign key named a table `organizations` neither owns nor
|
|
10
|
+
* declares. That is D-226's bridge rule one namespace over: a bridge between
|
|
11
|
+
* an always-present near side (`organizations`) and a switchable far side
|
|
12
|
+
* (`inventory`) belongs to the far side, because only the far side can be
|
|
13
|
+
* ordered after both tables — `inventory` declares `organizations`, and
|
|
14
|
+
* `organizations` cannot declare `inventory` without inverting an edge that
|
|
15
|
+
* already runs the other way.
|
|
16
|
+
*
|
|
17
|
+
* So an instance that does not install `inventory` no longer carries a
|
|
18
|
+
* migration whose foreign key names `warehouses`, and one that does gets the
|
|
19
|
+
* bridge with its far side. The payoff is the one the rule promises:
|
|
20
|
+
* `module:uninstall --hard inventory` now reverts this table, because a hard
|
|
21
|
+
* uninstall reverts by registry `moduleId`.
|
|
22
|
+
*
|
|
23
|
+
* `if not exists`, because every database that has already applied the
|
|
24
|
+
* frozen migration has this table. The storage keys on the class name and
|
|
25
|
+
* holds no checksum, so the reduced frozen body is not re-offered there and
|
|
26
|
+
* this migration is the no-op it reads as; on a fresh database it is the
|
|
27
|
+
* creation. The statements are the frozen ones verbatim — same columns, same
|
|
28
|
+
* primary key, same two foreign keys — so the two paths reach one schema.
|
|
29
|
+
*/
|
|
30
|
+
export class Migration20260912T125716InventoryOrganizationWarehouses extends Migration {
|
|
31
|
+
async up() {
|
|
32
|
+
this.addSql(`
|
|
33
|
+
create table if not exists "organization_warehouses" (
|
|
34
|
+
"organization_id" uuid not null,
|
|
35
|
+
"warehouse_id" uuid not null,
|
|
36
|
+
"created_at" timestamptz not null default now(),
|
|
37
|
+
constraint "organization_warehouses_pkey"
|
|
38
|
+
primary key ("organization_id", "warehouse_id"),
|
|
39
|
+
constraint "organization_warehouses_organization_fk"
|
|
40
|
+
foreign key ("organization_id") references "organizations" ("id") on delete cascade,
|
|
41
|
+
constraint "organization_warehouses_warehouse_fk"
|
|
42
|
+
foreign key ("warehouse_id") references "warehouses" ("id") on delete cascade
|
|
43
|
+
);
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
async down() {
|
|
47
|
+
this.addSql('drop table if exists "organization_warehouses" cascade;');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=20260912T125716_inventory_organization_warehouses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260912T125716_inventory_organization_warehouses.js","sourceRoot":"","sources":["../../src/migrations/20260912T125716_inventory_organization_warehouses.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,OAAO,uDAAwD,SAAQ,SAAS;IAC3E,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;KAYX,CAAC,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,yDAAyD,CAAC,CAAC;IACzE,CAAC;CACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which orders **this module's own** migrations
|
|
10
|
+
* and nothing else (feature 081). Where the block sits relative to every other
|
|
11
|
+
* module's is decided by the manifest `dependencies` graph.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay, and the asymmetry with `./backend` — which
|
|
14
|
+
* publishes an array and no entity class by name (D-168) — is deliberate.
|
|
15
|
+
* `db/migrations-registry.generated.ts` imports each class by name from this
|
|
16
|
+
* specifier, and a migration class name is contract in a way an entity class
|
|
17
|
+
* name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom
|
|
22
|
+
* is a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260503T182812InventoryWorkflow } from './20260503T182812_inventory_workflow.js';
|
|
25
|
+
import { Migration20260611T140347InventoryWarehouseDefaultLowStockThreshold } from './20260611T140347_inventory_warehouse_default_low_stock_threshold.js';
|
|
26
|
+
import { Migration20260611T140348InventoryPerWarehouseLowStockThresholds } from './20260611T140348_inventory_per_warehouse_low_stock_thresholds.js';
|
|
27
|
+
import { Migration20260818T081243InventoryStockAllocationOrderItemFk } from './20260818T081243_inventory_stock_allocation_order_item_fk.js';
|
|
28
|
+
import { Migration20260830T182139InventoryOrganizationAttribution } from './20260830T182139_inventory_organization_attribution.js';
|
|
29
|
+
import { Migration20260912T125716InventoryOrganizationWarehouses } from './20260912T125716_inventory_organization_warehouses.js';
|
|
30
|
+
export declare const migrations: (typeof Migration20260503T182812InventoryWorkflow)[];
|
|
31
|
+
export { Migration20260503T182812InventoryWorkflow, Migration20260611T140347InventoryWarehouseDefaultLowStockThreshold, Migration20260611T140348InventoryPerWarehouseLowStockThresholds, Migration20260818T081243InventoryStockAllocationOrderItemFk, Migration20260830T182139InventoryOrganizationAttribution, Migration20260912T125716InventoryOrganizationWarehouses, };
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,yCAAyC,EAAE,MAAM,yCAAyC,CAAC;AACpG,OAAO,EAAE,kEAAkE,EAAE,MAAM,sEAAsE,CAAC;AAC1J,OAAO,EAAE,+DAA+D,EAAE,MAAM,mEAAmE,CAAC;AACpJ,OAAO,EAAE,2DAA2D,EAAE,MAAM,+DAA+D,CAAC;AAC5I,OAAO,EAAE,wDAAwD,EAAE,MAAM,yDAAyD,CAAC;AACnI,OAAO,EAAE,uDAAuD,EAAE,MAAM,wDAAwD,CAAC;AAEjI,eAAO,MAAM,UAAU,sDAOtB,CAAC;AAEF,OAAO,EACL,yCAAyC,EACzC,kEAAkE,EAClE,+DAA+D,EAC/D,2DAA2D,EAC3D,wDAAwD,EACxD,uDAAuD,GACxD,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which orders **this module's own** migrations
|
|
10
|
+
* and nothing else (feature 081). Where the block sits relative to every other
|
|
11
|
+
* module's is decided by the manifest `dependencies` graph.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay, and the asymmetry with `./backend` — which
|
|
14
|
+
* publishes an array and no entity class by name (D-168) — is deliberate.
|
|
15
|
+
* `db/migrations-registry.generated.ts` imports each class by name from this
|
|
16
|
+
* specifier, and a migration class name is contract in a way an entity class
|
|
17
|
+
* name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom
|
|
22
|
+
* is a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260503T182812InventoryWorkflow } from './20260503T182812_inventory_workflow.js';
|
|
25
|
+
import { Migration20260611T140347InventoryWarehouseDefaultLowStockThreshold } from './20260611T140347_inventory_warehouse_default_low_stock_threshold.js';
|
|
26
|
+
import { Migration20260611T140348InventoryPerWarehouseLowStockThresholds } from './20260611T140348_inventory_per_warehouse_low_stock_thresholds.js';
|
|
27
|
+
import { Migration20260818T081243InventoryStockAllocationOrderItemFk } from './20260818T081243_inventory_stock_allocation_order_item_fk.js';
|
|
28
|
+
import { Migration20260830T182139InventoryOrganizationAttribution } from './20260830T182139_inventory_organization_attribution.js';
|
|
29
|
+
import { Migration20260912T125716InventoryOrganizationWarehouses } from './20260912T125716_inventory_organization_warehouses.js';
|
|
30
|
+
export const migrations = [
|
|
31
|
+
Migration20260503T182812InventoryWorkflow,
|
|
32
|
+
Migration20260611T140347InventoryWarehouseDefaultLowStockThreshold,
|
|
33
|
+
Migration20260611T140348InventoryPerWarehouseLowStockThresholds,
|
|
34
|
+
Migration20260818T081243InventoryStockAllocationOrderItemFk,
|
|
35
|
+
Migration20260830T182139InventoryOrganizationAttribution,
|
|
36
|
+
Migration20260912T125716InventoryOrganizationWarehouses,
|
|
37
|
+
];
|
|
38
|
+
export { Migration20260503T182812InventoryWorkflow, Migration20260611T140347InventoryWarehouseDefaultLowStockThreshold, Migration20260611T140348InventoryPerWarehouseLowStockThresholds, Migration20260818T081243InventoryStockAllocationOrderItemFk, Migration20260830T182139InventoryOrganizationAttribution, Migration20260912T125716InventoryOrganizationWarehouses, };
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,yCAAyC,EAAE,MAAM,yCAAyC,CAAC;AACpG,OAAO,EAAE,kEAAkE,EAAE,MAAM,sEAAsE,CAAC;AAC1J,OAAO,EAAE,+DAA+D,EAAE,MAAM,mEAAmE,CAAC;AACpJ,OAAO,EAAE,2DAA2D,EAAE,MAAM,+DAA+D,CAAC;AAC5I,OAAO,EAAE,wDAAwD,EAAE,MAAM,yDAAyD,CAAC;AACnI,OAAO,EAAE,uDAAuD,EAAE,MAAM,wDAAwD,CAAC;AAEjI,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,yCAAyC;IACzC,kEAAkE;IAClE,+DAA+D;IAC/D,2DAA2D;IAC3D,wDAAwD;IACxD,uDAAuD;CACxD,CAAC;AAEF,OAAO,EACL,yCAAyC,EACzC,kEAAkE,EAClE,+DAA+D,EAC/D,2DAA2D,EAC3D,wDAAwD,EACxD,uDAAuD,GACxD,CAAC"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The port interfaces `inventory` publishes whose signature carries a MikroORM
|
|
3
|
+
* `EntityManager`, and **nothing that exists at runtime** (feature 080, T048;
|
|
4
|
+
* D-169, D-171).
|
|
5
|
+
*
|
|
6
|
+
* `tsc` compiles this file to `export {};`. That is the property D-171 makes
|
|
7
|
+
* the boundary decision on — *a subpath is contract surface iff the module it
|
|
8
|
+
* resolves to exports no runtime binding* — and it is why this declaration has
|
|
9
|
+
* its own file rather than sitting on top of a service module, which exports
|
|
10
|
+
* the class beside it. A consumer naming that file names the owner's
|
|
11
|
+
* implementation, whatever `import type` erases; a consumer naming this one
|
|
12
|
+
* names a declaration and can name nothing else.
|
|
13
|
+
*
|
|
14
|
+
* **This is not yet a supported specifier and the ledger still counts it.**
|
|
15
|
+
* `inventory` is not a workspace package, so there is no `exports` map for this
|
|
16
|
+
* to be a subpath of, and `check:module-boundary` reads `orders`' relative
|
|
17
|
+
* import exactly as it read the two entity imports it replaces — D-171 says so
|
|
18
|
+
* in as many words: `resolveModulePackage` returns `null` for any specifier
|
|
19
|
+
* starting with `.`, so an unconverted reach has no subpath for the exemption
|
|
20
|
+
* to apply to, and reaching the exempt state takes three separable edits
|
|
21
|
+
* (package the owner, publish the interface, rewrite the specifier). This file
|
|
22
|
+
* is the second of those three, taken early because it is the one that does not
|
|
23
|
+
* need the module to move.
|
|
24
|
+
*
|
|
25
|
+
* The rest of this module's surface is in `@endora-commerce/contracts` and
|
|
26
|
+
* belongs there — `InventoryStockReadPort`, `InventoryFulfilmentPlanningPort`,
|
|
27
|
+
* `InventoryStockImportPort` and `InventoryProductThresholdWritePort` are
|
|
28
|
+
* contract DTOs end to end, and the planning port is pure over its arguments.
|
|
29
|
+
* This one qualifies for `./ports` on D-171's own test — *"does this signature
|
|
30
|
+
* stop the interface living in `packages/contracts`"* — and the answer is its
|
|
31
|
+
* `EntityManager` parameter, which that package may not name because `admin`
|
|
32
|
+
* and `storefront` both compile it (FR-034).
|
|
33
|
+
*
|
|
34
|
+
* No entity class leaves by this door, type-only included (D-168).
|
|
35
|
+
*/
|
|
36
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
37
|
+
/**
|
|
38
|
+
* What one candidate warehouse holds for one order line, under the write lock
|
|
39
|
+
* {@link InventoryReservationApplyPort.lockAvailabilityForPlacement} took — a
|
|
40
|
+
* published record and never the managed `stock_levels` row (D-77's first
|
|
41
|
+
* narrowing).
|
|
42
|
+
*
|
|
43
|
+
* `available` is `onHand - reserved` at the moment of the lock, and is `0` for
|
|
44
|
+
* a warehouse that holds no row for the line at all: an absent row and a row at
|
|
45
|
+
* zero are the same answer to *"how many can I promise"*, and the difference —
|
|
46
|
+
* whether an `insert` or an `update` follows — is this module's to know.
|
|
47
|
+
*/
|
|
48
|
+
export interface PlacementStockSnapshot {
|
|
49
|
+
readonly warehouseId: string;
|
|
50
|
+
readonly available: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Container name: `inventoryReservationApplyPort`. Owner: `inventory`.
|
|
54
|
+
*
|
|
55
|
+
* The stock reservation order placement performs, run on the **caller's**
|
|
56
|
+
* `EntityManager` (D-169). `orders` is the one consumer, and the four methods
|
|
57
|
+
* are the four steps of one protocol: lock the candidates, apply the plan,
|
|
58
|
+
* record the allocations once the order items exist, and release them if the
|
|
59
|
+
* order is later cancelled.
|
|
60
|
+
*
|
|
61
|
+
* **The seam is not a defect in the design, it *is* the design.**
|
|
62
|
+
* `stock_allocations_order_item_fk` (`stock_allocations.order_item_id` ->
|
|
63
|
+
* `order_items.id`, `on delete restrict`,
|
|
64
|
+
* `inventory/migrations/20260818T081243_inventory_stock_allocation_order_item_fk.ts`)
|
|
65
|
+
* means an allocation row cannot exist before its order item does, and the
|
|
66
|
+
* order items are not committed until placement returns — so a port that opened
|
|
67
|
+
* its own transaction could not satisfy a foreign key against rows it cannot
|
|
68
|
+
* see. The `PESSIMISTIC_WRITE` on `stock_levels` is the other half of the same
|
|
69
|
+
* fact: it has to be held by the transaction that writes the order, or two
|
|
70
|
+
* placements allocate the same unit
|
|
71
|
+
* (`test/contract/orders/place-stock-race.test.ts`), and a `reserved` increment
|
|
72
|
+
* committed separately would survive a placement that then rolled back. A
|
|
73
|
+
* foreign key needs the **table** and never the class (D-169), so that
|
|
74
|
+
* constraint stands while this module publishes no entity class by name.
|
|
75
|
+
*
|
|
76
|
+
* **The policy half is not here, and neither is the read half.** Which
|
|
77
|
+
* warehouse a line is allocated to is `InventoryFulfilmentPlanningPort`, pure
|
|
78
|
+
* over its arguments; the channel → warehouse binding and every standalone
|
|
79
|
+
* stock read are `InventoryStockReadPort`, on this module's own
|
|
80
|
+
* `EntityManager`. Both live in `@endora-commerce/contracts` and stay there. A
|
|
81
|
+
* read handed an `EntityManager` is a write seam re-opened to serve a read
|
|
82
|
+
* (D-169), and `candidatesFor` is the worked example of the difference: it
|
|
83
|
+
* answers the richer question and answers it outside the caller's transaction,
|
|
84
|
+
* which is precisely why it cannot take the lock this port's first method
|
|
85
|
+
* takes.
|
|
86
|
+
*
|
|
87
|
+
* **Owner off:** this module is switchable (`inventory.enabled`), and `orders`
|
|
88
|
+
* declares the edge `degrades-without` rather than binding it —
|
|
89
|
+
* `stock_allocations_order_item_fk` obliges `inventory` to declare `orders`, so
|
|
90
|
+
* the edge cannot be declared back, and an acknowledged edge would keep the
|
|
91
|
+
* bind and make that control unusable, because `orders` is non-deactivatable.
|
|
92
|
+
* So placement asks the effective-state seam for this module's own id once,
|
|
93
|
+
* before the reservation block, and skips it whole; the cancellation path asks
|
|
94
|
+
* the same question before releasing. The literal is deliberately not spelled
|
|
95
|
+
* here: `check-entry-presence`'s tree proof blanks that expression out of the
|
|
96
|
+
* one file of each module that carries it, and a second file carrying it in
|
|
97
|
+
* prose makes that proof ambiguous. With this module off an order is placed without
|
|
98
|
+
* reserving stock and a cancellation releases nothing — which is what
|
|
99
|
+
* `inventory.enabled`'s own description promises an operator, and what the
|
|
100
|
+
* platform did **not** do before D-94.4, when every placement locked
|
|
101
|
+
* `stock_levels`, incremented `reserved` and inserted `stock_allocations` rows
|
|
102
|
+
* with the module switched off (issue #188).
|
|
103
|
+
*/
|
|
104
|
+
export interface InventoryReservationApplyPort {
|
|
105
|
+
/**
|
|
106
|
+
* Lock the line's row in each candidate warehouse under
|
|
107
|
+
* `PESSIMISTIC_WRITE` and answer what each holds.
|
|
108
|
+
*
|
|
109
|
+
* `em` is **required** (D-169). Its one caller is `placeOrder`, which always
|
|
110
|
+
* passes the `EntityManager` its own transaction runs on; an optional
|
|
111
|
+
* parameter is what lets the same method double as a standalone read, and
|
|
112
|
+
* that is a lie about a lock whose whole content is that it is held until the
|
|
113
|
+
* order commits. The standalone read is a different method on a different
|
|
114
|
+
* port and takes no `EntityManager`: `InventoryStockReadPort.candidatesFor`.
|
|
115
|
+
*
|
|
116
|
+
* One entry per requested warehouse, in the order requested, so the caller
|
|
117
|
+
* can zip it against the channel binding it already holds.
|
|
118
|
+
*/
|
|
119
|
+
lockAvailabilityForPlacement(em: EntityManager, input: {
|
|
120
|
+
productId: string;
|
|
121
|
+
variantId: string | null;
|
|
122
|
+
warehouseIds: readonly string[];
|
|
123
|
+
}): Promise<PlacementStockSnapshot[]>;
|
|
124
|
+
/**
|
|
125
|
+
* Apply the allocation plan: raise `reserved` by the planned quantity in each
|
|
126
|
+
* named warehouse, opening a row at `onHand = 0` where the line has none.
|
|
127
|
+
*
|
|
128
|
+
* Runs on the same `em` as the lock above, and is the write that lock exists
|
|
129
|
+
* to protect. It is not flushed here: the caller's transaction flushes it
|
|
130
|
+
* beside the order items whose foreign key holds the allocation rows, which
|
|
131
|
+
* is the statement order the reservation has always had.
|
|
132
|
+
*/
|
|
133
|
+
reserveForPlacement(em: EntityManager, input: {
|
|
134
|
+
productId: string;
|
|
135
|
+
variantId: string | null;
|
|
136
|
+
allocations: ReadonlyArray<{
|
|
137
|
+
warehouseId: string;
|
|
138
|
+
quantity: number;
|
|
139
|
+
}>;
|
|
140
|
+
}): Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* Record one allocation row per `(order item, warehouse)` pair, so an admin
|
|
143
|
+
* can trace fulfilment provenance and so cancellation has something to
|
|
144
|
+
* release.
|
|
145
|
+
*
|
|
146
|
+
* Called **after** the order items are flushed and required to be:
|
|
147
|
+
* `stock_allocations_order_item_fk` is `on delete restrict`, so the row
|
|
148
|
+
* cannot exist before its order item does.
|
|
149
|
+
*/
|
|
150
|
+
recordAllocationsForOrderItems(em: EntityManager, input: {
|
|
151
|
+
allocations: ReadonlyArray<{
|
|
152
|
+
orderItemId: string;
|
|
153
|
+
warehouseId: string;
|
|
154
|
+
quantity: number;
|
|
155
|
+
isBackorder: boolean;
|
|
156
|
+
}>;
|
|
157
|
+
}): Promise<void>;
|
|
158
|
+
/**
|
|
159
|
+
* Release every un-released allocation held by the named order items,
|
|
160
|
+
* decrementing each affected `reserved` counter and stamping `released_at`.
|
|
161
|
+
*
|
|
162
|
+
* Idempotent: a second run over the same items releases nothing, because
|
|
163
|
+
* already-released rows are filtered out by `released_at is null`. `em` is
|
|
164
|
+
* the transaction the caller opened for the cancellation — a different
|
|
165
|
+
* transaction from placement's, and required for the same reason: the
|
|
166
|
+
* counter decrement and the `released_at` stamp are one operation.
|
|
167
|
+
*
|
|
168
|
+
* The caller passes the line's product and variant because `stock_allocations`
|
|
169
|
+
* records the warehouse and the order item and not the product; resolving it
|
|
170
|
+
* here would mean this module reading `order_items`, which belongs to
|
|
171
|
+
* `orders`.
|
|
172
|
+
*/
|
|
173
|
+
releaseForOrderItems(em: EntityManager, input: {
|
|
174
|
+
items: ReadonlyArray<{
|
|
175
|
+
orderItemId: string;
|
|
176
|
+
productId: string;
|
|
177
|
+
variantId: string | null;
|
|
178
|
+
}>;
|
|
179
|
+
}): Promise<{
|
|
180
|
+
released: number;
|
|
181
|
+
}>;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,MAAM,WAAW,6BAA6B;IAC5C;;;;;;;;;;;;;OAaG;IACH,4BAA4B,CAC1B,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;KACjC,GACA,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IACrC;;;;;;;;OAQG;IACH,mBAAmB,CACjB,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,WAAW,EAAE,aAAa,CAAC;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACvE,GACA,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;;;;;OAQG;IACH,8BAA8B,CAC5B,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE;QACL,WAAW,EAAE,aAAa,CAAC;YACzB,WAAW,EAAE,MAAM,CAAC;YACpB,WAAW,EAAE,MAAM,CAAC;YACpB,QAAQ,EAAE,MAAM,CAAC;YACjB,WAAW,EAAE,OAAO,CAAC;SACtB,CAAC,CAAC;KACJ,GACA,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAClB,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE;QACL,KAAK,EAAE,aAAa,CAAC;YACnB,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,CAAC;YAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;SAC1B,CAAC,CAAC;KACJ,GACA,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":""}
|