feat(M09-D1): 完成商品目录与库存流水底座
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const root = process.cwd();
|
||||
const read = (path) => readFileSync(join(root, path), 'utf8');
|
||||
|
||||
for (const suffix of ['up', 'down', 'verify']) {
|
||||
assert.ok(
|
||||
existsSync(join(root,
|
||||
`database/migrations/2026081107_m09d1_product_inventory_foundation.${suffix}.sql`)),
|
||||
`M09-D1 ${suffix} migration is missing`
|
||||
);
|
||||
}
|
||||
|
||||
const migration = read('database/migrations/2026081107_m09d1_product_inventory_foundation.up.sql');
|
||||
for (const pattern of [
|
||||
'qipai_product_categories',
|
||||
'qipai_products',
|
||||
'qipai_product_skus',
|
||||
'qipai_product_store_listings',
|
||||
'qipai_product_store_hours',
|
||||
'qipai_product_inventory',
|
||||
'qipai_product_inventory_requests',
|
||||
'qipai_product_inventory_ledger',
|
||||
'delivery_enabled',
|
||||
'storage_enabled',
|
||||
'crosses_midnight',
|
||||
'manual_paused_at',
|
||||
'loss_quantity',
|
||||
'loss_delta',
|
||||
'uq_qipai_product_inventory_request',
|
||||
'ADD COLUMN checksum',
|
||||
'uq_qipai_product_inventory_ledger_request',
|
||||
'uq_qipai_product_inventory_ledger_version',
|
||||
'PRODUCT_INVENTORY_LEDGER_IMMUTABLE',
|
||||
'product.catalog.read',
|
||||
'inventory.adjust'
|
||||
]) assert.ok(migration.includes(pattern), `migration is missing ${pattern}`);
|
||||
assert.match(migration, /sale_price_cents INT UNSIGNED/);
|
||||
assert.match(migration, /available_quantity BIGINT UNSIGNED/);
|
||||
assert.match(migration, /operation IN \([\s\S]*'INBOUND'[\s\S]*'STOCKTAKE'[\s\S]*'LOSS'/);
|
||||
|
||||
const catalog = read('backend/src/products/product-catalog-repository.ts');
|
||||
for (const pattern of [
|
||||
'ProductCatalogRepository',
|
||||
'expectedVersion',
|
||||
'PRODUCT_CATEGORY_VERSION_CONFLICT',
|
||||
'PRODUCT_VERSION_CONFLICT',
|
||||
'PRODUCT_SKU_VERSION_CONFLICT',
|
||||
'PRODUCT_LISTING_VERSION_CONFLICT',
|
||||
'PRODUCT_STORE_SETTINGS_VERSION_CONFLICT',
|
||||
'crossesMidnight',
|
||||
'manualPaused'
|
||||
]) assert.ok(catalog.includes(pattern), `product catalog is missing ${pattern}`);
|
||||
assert.match(catalog, /INSERT INTO qipai_audit_logs/);
|
||||
|
||||
const inventory = read('backend/src/inventory/inventory-service.ts');
|
||||
for (const pattern of [
|
||||
'InventoryService',
|
||||
'lockMany',
|
||||
'releaseMany',
|
||||
'deductMany',
|
||||
'INVENTORY_INSUFFICIENT_AVAILABLE',
|
||||
'INVENTORY_INSUFFICIENT_LOCKED',
|
||||
'INVENTORY_IDEMPOTENCY_CONFLICT',
|
||||
'FOR UPDATE',
|
||||
'qipai_product_inventory_ledger',
|
||||
'qipai_audit_logs'
|
||||
]) assert.ok(inventory.includes(pattern), `inventory service is missing ${pattern}`);
|
||||
|
||||
const productRoutes = read('backend/src/routes/products.ts');
|
||||
const inventoryRoutes = read('backend/src/routes/inventory.ts');
|
||||
assert.match(productRoutes, /product-categories/);
|
||||
assert.match(productRoutes, /\/admin-api\/products/);
|
||||
assert.match(productRoutes, /product-sales-settings/);
|
||||
assert.match(inventoryRoutes, /inventory\/stocks/);
|
||||
assert.match(inventoryRoutes, /\/adjust/);
|
||||
|
||||
const mysqlTest = read('backend/tests/mysql-migration-roundtrip.test.mjs');
|
||||
for (const pattern of [
|
||||
'assertProductInventoryFoundation',
|
||||
'INVENTORY_INSUFFICIENT_AVAILABLE',
|
||||
'PRODUCT_INVENTORY_LEDGER_IMMUTABLE',
|
||||
'concurrent inventory lock',
|
||||
'cross-midnight product hours'
|
||||
]) assert.ok(mysqlTest.includes(pattern), `live MySQL test is missing ${pattern}`);
|
||||
|
||||
console.log('PASS: M09-D1 catalog, store-hours, stock concurrency and immutable-ledger gates are present.');
|
||||
Reference in New Issue
Block a user