feat(M09-D2): 完成商品订单与库存占用生命周期

This commit is contained in:
Codex
2026-08-11 07:04:23 +08:00
parent 93af128002
commit 1a3ea7bdf0
30 changed files with 3773 additions and 91 deletions
+74 -1
View File
@@ -129,6 +129,15 @@ const productInventoryDownSql = read(
const productInventoryVerifySql = read(
'database/migrations/2026081107_m09d1_product_inventory_foundation.verify.sql'
);
const productOrderPaymentUpSql = read(
'database/migrations/2026081108_m09d2_product_order_payment_inventory.up.sql'
);
const productOrderPaymentDownSql = read(
'database/migrations/2026081108_m09d2_product_order_payment_inventory.down.sql'
);
const productOrderPaymentVerifySql = read(
'database/migrations/2026081108_m09d2_product_order_payment_inventory.verify.sql'
);
const coreTables = [
'qipai_schema_migrations',
@@ -611,4 +620,68 @@ 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.');
for (const table of [
'qipai_product_orders',
'qipai_product_order_items',
'qipai_product_order_events',
'qipai_product_payments',
'qipai_product_refunds',
'qipai_product_payment_callbacks',
'qipai_product_refund_events'
]) {
assert.match(productOrderPaymentUpSql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}\\b`));
assert.match(productOrderPaymentDownSql, new RegExp(`DROP TABLE IF EXISTS ${table}\\b`));
assert.match(productOrderPaymentVerifySql, new RegExp(`'${table}'`));
}
assert.doesNotMatch(productOrderPaymentUpSql, /\bDECIMAL\b/i);
for (const state of [
'PENDING_PAYMENT', 'PAID', 'ACCEPTED', 'DELIVERING',
'READY_FOR_SELF_SERVICE', 'COMPLETED', 'CANCELLED',
'REFUNDING', 'REFUNDED', 'REFUND_FAILED'
]) assert.match(productOrderPaymentUpSql, new RegExp(`'${state}'`));
assert.match(productOrderPaymentUpSql, /fulfillment_mode = 'DELIVERY' AND room_order_id IS NOT NULL/);
assert.match(productOrderPaymentUpSql, /fulfillment_mode = 'SELF_SERVICE' AND room_order_id IS NULL/);
assert.match(productOrderPaymentUpSql, /subtotal_cents = unit_price_cents \* quantity/);
for (const snapshotColumn of [
'product_code', 'product_name', 'sku_code', 'sku_name',
'unit_name', 'attributes_snapshot', 'unit_price_cents', 'quantity', 'subtotal_cents'
]) assert.match(productOrderPaymentUpSql, new RegExp(`\\b${snapshotColumn}\\b`));
assert.match(productOrderPaymentUpSql, /order_no VARCHAR\(64\) CHARACTER SET ascii COLLATE ascii_bin/);
assert.match(productOrderPaymentUpSql, /payment_no VARCHAR\(64\) CHARACTER SET ascii COLLATE ascii_bin/);
assert.match(productOrderPaymentUpSql, /refund_no VARCHAR\(64\) CHARACTER SET ascii COLLATE ascii_bin/);
assert.match(productOrderPaymentUpSql, /callback_id VARCHAR\(128\) CHARACTER SET ascii COLLATE ascii_bin/);
assert.match(productOrderPaymentUpSql, /inventory_business_id VARCHAR\(128\) COLLATE utf8mb4_bin/);
assert.match(productOrderPaymentUpSql, /request_fingerprint CHAR\(64\) CHARACTER SET ascii COLLATE ascii_bin/);
assert.match(productOrderPaymentUpSql, /uq_qipai_product_order_client_request/);
assert.match(productOrderPaymentUpSql, /uq_qipai_product_payment_client_request/);
assert.match(productOrderPaymentUpSql, /uq_qipai_product_payment_provider_id/);
assert.match(productOrderPaymentUpSql, /uq_qipai_product_payment_callback_provider/);
assert.match(productOrderPaymentUpSql, /uq_qipai_product_refund_client_request/);
assert.match(productOrderPaymentUpSql, /uq_qipai_product_refund_provider_id/);
assert.match(productOrderPaymentUpSql, /uq_qipai_product_payment_active_order/);
assert.match(productOrderPaymentUpSql, /status IN \('PENDING', 'PROCESSING'\) THEN order_id/);
assert.match(productOrderPaymentUpSql, /uq_qipai_product_refund_active_payment/);
assert.match(productOrderPaymentUpSql, /status IN \('PENDING', 'PROCESSING'\) THEN payment_id/);
assert.match(productOrderPaymentUpSql, /fk_qipai_product_order_inventory_request/);
assert.match(productOrderPaymentUpSql, /fk_qipai_product_refund_inventory_request/);
assert.match(productOrderPaymentUpSql, /inventory_disposition = 'RESTOCK'/);
assert.match(productOrderPaymentUpSql, /inventory_disposition = 'NO_RESTOCK'/);
assert.match(productOrderPaymentUpSql, /inventory_status IN \('LOCKED', 'DEDUCTED', 'RELEASED', 'RETURNED'\)/);
assert.match(productOrderPaymentUpSql, /'COMPENSATION_REQUIRED', 'REFUNDED'/);
assert.match(productOrderPaymentUpSql, /qipai_product_order_items_no_update/);
assert.match(productOrderPaymentUpSql, /qipai_product_order_items_no_delete/);
assert.match(productOrderPaymentUpSql, /qipai_product_order_events_no_update/);
assert.match(productOrderPaymentUpSql, /qipai_product_order_events_no_delete/);
assert.match(productOrderPaymentUpSql, /qipai_product_refund_events_no_update/);
assert.match(productOrderPaymentUpSql, /qipai_product_refund_events_no_delete/);
for (const permission of ['goods.order.read', 'goods.order.manage']) {
const pattern = new RegExp(permission.replace('.', '\\.'));
assert.match(productOrderPaymentUpSql, pattern);
assert.match(productOrderPaymentDownSql, pattern);
assert.match(productOrderPaymentVerifySql, pattern);
}
assert.match(productOrderPaymentUpSql, /r\.code IN \('STAFF', 'STORE_ADMIN', 'TENANT_ADMIN', 'PLATFORM_ADMIN'\)/);
assert.match(productOrderPaymentVerifySql, /fully_granted_goods_order_roles/);
assert.match(productOrderPaymentVerifySql, /'2026081108'/);
console.log('PASS: M01-B through M09-D2 migration contracts are present.');