feat(M09-D1): 完成商品目录与库存流水底座

This commit is contained in:
Codex
2026-08-11 03:51:18 +08:00
parent 2de796143f
commit 93af128002
37 changed files with 14683 additions and 58 deletions
+76 -1
View File
@@ -120,6 +120,15 @@ const cleaningSettlementIntegrityDownSql = read(
const cleaningSettlementIntegrityVerifySql = read(
'database/migrations/2026081006_m09c_cleaning_settlement_integrity.verify.sql'
);
const productInventoryUpSql = read(
'database/migrations/2026081107_m09d1_product_inventory_foundation.up.sql'
);
const productInventoryDownSql = read(
'database/migrations/2026081107_m09d1_product_inventory_foundation.down.sql'
);
const productInventoryVerifySql = read(
'database/migrations/2026081107_m09d1_product_inventory_foundation.verify.sql'
);
const coreTables = [
'qipai_schema_migrations',
@@ -536,4 +545,70 @@ assert.match(cleaningSettlementIntegrityVerifySql, /'2026081006'/);
assert.match(franchiseDownSql, /DROP TABLE IF EXISTS qipai_franchise_follow_ups/);
assert.match(franchiseVerifySql, /idx_qipai_franchise_follow_up_history/);
console.log('PASS: M01-B through M09-C migration contracts are present.');
for (const table of [
'qipai_product_categories',
'qipai_products',
'qipai_product_skus',
'qipai_product_store_listings',
'qipai_product_store_settings',
'qipai_product_store_hours',
'qipai_product_inventory',
'qipai_product_inventory_requests',
'qipai_product_inventory_ledger'
]) {
assert.match(productInventoryUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}`));
assert.match(productInventoryDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}`));
assert.match(productInventoryVerifySql, new RegExp(`'${table}'`));
}
assert.match(productInventoryUpSql, /ADD UNIQUE KEY uq_qipai_stores_tenant_id/);
assert.match(productInventoryUpSql, /ADD UNIQUE KEY uq_qipai_users_tenant_id/);
assert.match(productInventoryUpSql, /ADD COLUMN checksum CHAR\(64\)/);
assert.match(productInventoryUpSql, /store_id BIGINT UNSIGNED NOT NULL/);
assert.match(productInventoryUpSql, /uq_qipai_product_category_scope_id/);
assert.match(
productInventoryUpSql,
/FOREIGN KEY \(tenant_id, store_id, category_id\)[\s\S]*qipai_product_categories/
);
assert.match(productInventoryUpSql, /delivery_enabled TINYINT\(1\) NOT NULL DEFAULT 1/);
assert.match(productInventoryUpSql, /storage_enabled TINYINT\(1\) NOT NULL DEFAULT 0/);
assert.match(productInventoryUpSql, /default_inventory_policy VARCHAR\(16\)/);
assert.match(productInventoryUpSql, /default_inventory_policy IN \('TRACKED', 'UNLIMITED'\)/);
assert.match(productInventoryUpSql, /manual_paused_at DATETIME\(3\) NULL/);
assert.match(productInventoryUpSql, /manual_paused_until DATETIME\(3\) NULL/);
assert.match(productInventoryUpSql, /manual_pause_reason VARCHAR\(512\)/);
assert.match(productInventoryUpSql, /crosses_midnight TINYINT\(1\) NOT NULL DEFAULT 0/);
assert.match(productInventoryUpSql, /crosses_midnight = 1 AND close_minute <= open_minute/);
assert.match(productInventoryUpSql, /loss_quantity BIGINT UNSIGNED NOT NULL DEFAULT 0/);
assert.match(productInventoryUpSql, /loss_delta BIGINT NOT NULL DEFAULT 0/);
assert.match(productInventoryUpSql, /loss_after BIGINT UNSIGNED NOT NULL/);
for (const operation of [
'CONFIGURE', 'INBOUND', 'ADJUST', 'LOCK', 'RELEASE',
'DEDUCT', 'STOCKTAKE', 'LOSS', 'RETURN'
]) assert.match(productInventoryUpSql, new RegExp(`'${operation}'`));
assert.match(productInventoryUpSql, /uq_qipai_product_inventory_ledger_request/);
assert.match(productInventoryUpSql, /uq_qipai_product_inventory_request/);
assert.match(productInventoryUpSql, /fk_qipai_product_inventory_ledger_request/);
assert.match(productInventoryUpSql, /uq_qipai_product_inventory_ledger_version/);
assert.match(productInventoryUpSql, /qipai_product_inventory_ledger_no_update/);
assert.match(productInventoryUpSql, /qipai_product_inventory_ledger_no_delete/);
for (const permission of [
'product.catalog.read', 'product.catalog.write', 'inventory.read', 'inventory.adjust'
]) {
assert.match(productInventoryUpSql, new RegExp(permission.replace('.', '\\.')));
assert.match(productInventoryDownSql, new RegExp(permission.replace('.', '\\.')));
assert.match(productInventoryVerifySql, new RegExp(permission.replace('.', '\\.')));
}
assert.match(productInventoryUpSql, /r\.code = 'STAFF'/);
assert.match(productInventoryUpSql, /r\.code IN \('STORE_ADMIN', 'TENANT_ADMIN', 'PLATFORM_ADMIN'\)/);
assert.match(productInventoryDownSql, /DROP INDEX uq_qipai_stores_tenant_id/);
assert.match(productInventoryDownSql, /DROP INDEX uq_qipai_users_tenant_id/);
assert.match(productInventoryDownSql, /DROP COLUMN checksum/);
assert.match(
productInventoryUpSql,
/FOREIGN KEY \(tenant_id, operator_id\) REFERENCES qipai_users\(tenant_id, id\)/
);
assert.match(productInventoryVerifySql, /fully_granted_product_roles/);
assert.match(productInventoryVerifySql, /'uq_qipai_product_inventory_ledger_version'/);
assert.match(productInventoryVerifySql, /'2026081107'/);
console.log('PASS: M01-B through M09-D1 migration contracts are present.');