重写 120/121 迁移,统一内部 SKU、渠道 id、config_json 和旧规则转换。 后台渠道配置改读统一渠道表,卡券与人工配置不再写旧规则。 增加安全渠道切换,并排除已关闭、失败或过期的历史任务载体。 按生产 119 基线重跑迁移、完整履约财务链路和严格预检。
242 lines
9.4 KiB
TypeScript
242 lines
9.4 KiB
TypeScript
/**
|
|
* 本地财务流程演示数据:待分类、自动发货、人工、兜底、未分类。
|
|
* 用法:npx tsx scripts/seed-local-finance-demo.ts
|
|
*/
|
|
import { closeDb, query } from '../src/db/client.js'
|
|
|
|
const SHOPS = ['sim-voucher', 'sim-aff', 'sim-manual', 'sim-fallback', 'sim-unmapped']
|
|
const TODAY = '2026-11-02 10:00:00+08'
|
|
|
|
async function cleanup() {
|
|
await query(
|
|
`DELETE FROM fulfillment_tasks WHERE order_item_id IN (SELECT id FROM order_items WHERE order_id IN (SELECT id FROM orders WHERE shop_id = ANY($1)))`,
|
|
[SHOPS],
|
|
)
|
|
await query(
|
|
`DELETE FROM cost_snapshots WHERE order_item_id IN (SELECT id FROM order_items WHERE order_id IN (SELECT id FROM orders WHERE shop_id = ANY($1)))`,
|
|
[SHOPS],
|
|
)
|
|
await query(`DELETE FROM sales_daily_facts WHERE shop_id = ANY($1)`, [SHOPS])
|
|
await query(
|
|
`DELETE FROM internal_sku_channels
|
|
WHERE internal_sku IN ('模拟自动发货商品', '模拟三方卡券', '模拟人工商品')`,
|
|
)
|
|
await query(
|
|
`DELETE FROM order_items WHERE order_id IN (SELECT id FROM orders WHERE shop_id = ANY($1))`,
|
|
[SHOPS],
|
|
)
|
|
await query(`DELETE FROM orders WHERE shop_id = ANY($1)`, [SHOPS])
|
|
await query(
|
|
`DELETE FROM sku_channel_rules
|
|
WHERE match_key IN ('SIM_AFF_1', 'SIMV001', 'SIMM001',
|
|
'模拟自动发货商品', '模拟三方卡券', '模拟人工商品')`,
|
|
)
|
|
await query(
|
|
`DELETE FROM product_channel_capabilities
|
|
WHERE product_id IN (
|
|
SELECT id FROM product_catalog
|
|
WHERE canonical_sku IN ('aff:SIM_AFF_1', 'man:模拟人工商品')
|
|
)`,
|
|
)
|
|
await query(
|
|
`DELETE FROM channel_product_mappings
|
|
WHERE canonical_product_id IN (
|
|
SELECT id FROM product_catalog
|
|
WHERE canonical_sku IN (
|
|
'aff:SIM_AFF_1', 'man:模拟人工商品', 'tpv:模拟三方卡券', 'tpv:模拟三方卡券B'
|
|
)
|
|
)`,
|
|
)
|
|
await query(
|
|
`DELETE FROM cost_versions
|
|
WHERE canonical_product_id IN (
|
|
SELECT id FROM product_catalog
|
|
WHERE canonical_sku IN ('aff:SIM_AFF_1', 'tpv:模拟三方卡券', 'tpv:模拟三方卡券B')
|
|
)`,
|
|
)
|
|
await query(
|
|
`DELETE FROM manual_fulfillment_configs
|
|
WHERE sku_key IN ('模拟人工商品', '模拟自动发货商品')`,
|
|
)
|
|
await query(
|
|
`DELETE FROM product_catalog
|
|
WHERE canonical_sku IN (
|
|
'aff:SIM_AFF_1', 'man:模拟人工商品', 'tpv:模拟三方卡券', 'tpv:模拟三方卡券B'
|
|
)`,
|
|
)
|
|
}
|
|
|
|
async function createOrder(shop: string, orderNo: string, amountFen = 1000) {
|
|
const result = await query<{ id: number }>(
|
|
`INSERT INTO orders (provider, platform, shop_id, platform_order_id, order_status, pay_status, total_amount, paid_at, created_at, updated_at)
|
|
VALUES ('91kaquan','91kaquan',$1,$2,'paid','paid',$3,$4,NOW(),NOW()) RETURNING id`,
|
|
[shop, orderNo, amountFen, TODAY],
|
|
)
|
|
return Number(result.rows[0]!.id)
|
|
}
|
|
|
|
async function createItem(input: {
|
|
orderId: number
|
|
skuCode: string
|
|
skuName: string
|
|
channel?: string
|
|
status?: string
|
|
productId?: number | null
|
|
}) {
|
|
const result = await query<{ id: number }>(
|
|
`INSERT INTO order_items (
|
|
order_id, sku_code, sku_name, quantity, revenue_amount_fen, revenue_basis,
|
|
fulfillment_channel, fulfillment_status, fulfillment_source, canonical_product_id,
|
|
created_at, updated_at
|
|
) VALUES ($1,$2,$3,1,1000,'order_total',$4,$5,$6,$7,NOW(),NOW()) RETURNING id`,
|
|
[
|
|
input.orderId,
|
|
input.skuCode,
|
|
input.skuName,
|
|
input.channel || null,
|
|
input.status || 'pending',
|
|
input.channel ? 'auto_rule' : '',
|
|
input.productId || null,
|
|
],
|
|
)
|
|
return Number(result.rows[0]!.id)
|
|
}
|
|
|
|
async function main() {
|
|
await cleanup()
|
|
|
|
// 自动发货演示商品(带采购价)
|
|
const affProduct = await query<{ id: number }>(
|
|
`INSERT INTO product_catalog (canonical_sku, canonical_name, category, unit_name, status, attributes_json, created_at, updated_at)
|
|
VALUES ('aff:SIM_AFF_1','模拟自动发货商品','试玩','件','active','{}',NOW(),NOW()) RETURNING id`,
|
|
)
|
|
const affProductId = Number(affProduct.rows[0]!.id)
|
|
const affCostItem = await query<{ id: number }>(
|
|
`SELECT id FROM cost_items WHERE code='sys_affiliate_purchase'`,
|
|
)
|
|
await query(
|
|
`INSERT INTO cost_versions (cost_item_id, scope_type, canonical_product_id, amount_fen, rate_bps, effective_from, status, change_reason, created_at, updated_at)
|
|
VALUES ($1,'product',$2,500,0,'2026-10-01T00:00:00+08:00','active','本地演示采购价',NOW(),NOW())`,
|
|
[affCostItem.rows[0]!.id, affProductId],
|
|
)
|
|
await query(
|
|
`INSERT INTO product_channel_capabilities (product_id, channel, priority, is_fallback, enabled, source)
|
|
VALUES ($1,'affiliate_dash',10,FALSE,TRUE,'demo'), ($1,'manual',20,TRUE,TRUE,'demo')`,
|
|
[affProductId],
|
|
)
|
|
const manualProduct = await query<{ id: number }>(
|
|
`INSERT INTO product_catalog (
|
|
canonical_sku, canonical_name, category, unit_name, status,
|
|
attributes_json, created_at, updated_at
|
|
) VALUES (
|
|
'man:模拟人工商品', '模拟人工商品', '人工', '件', 'active',
|
|
'{}'::jsonb, NOW(), NOW()
|
|
) RETURNING id`,
|
|
)
|
|
const manualProductId = Number(manualProduct.rows[0]!.id)
|
|
await query(
|
|
`INSERT INTO product_channel_capabilities (
|
|
product_id, channel, priority, is_fallback, enabled, source
|
|
) VALUES ($1, 'manual', 10, FALSE, TRUE, 'demo')`,
|
|
[manualProductId],
|
|
)
|
|
const manualCostItem = await query<{ id: number }>(
|
|
`SELECT id FROM cost_items WHERE code = 'sys_manual_service'`,
|
|
)
|
|
await query(
|
|
`INSERT INTO cost_versions (
|
|
cost_item_id, scope_type, canonical_product_id, amount_fen, rate_bps,
|
|
effective_from, status, change_reason, created_at, updated_at
|
|
) VALUES
|
|
($1, 'product', $2, 100, 0, '2026-10-01T00:00:00+08:00',
|
|
'active', '自动发货失败转人工成本', NOW(), NOW()),
|
|
($1, 'product', $3, 100, 0, '2026-10-01T00:00:00+08:00',
|
|
'active', '本地演示人工成本', NOW(), NOW())`,
|
|
[manualCostItem.rows[0]!.id, affProductId, manualProductId],
|
|
)
|
|
await query(
|
|
`INSERT INTO internal_sku_channels (
|
|
internal_sku, channel, priority, is_fallback, enabled,
|
|
canonical_product_id, config_json, source, created_at, updated_at
|
|
) VALUES
|
|
('模拟自动发货商品', 'affiliate_dash', 10, FALSE, TRUE, $1,
|
|
'{"affiliateSku":"SIM_AFF_1"}'::jsonb, 'demo', NOW(), NOW()),
|
|
('模拟自动发货商品', 'manual', 90, TRUE, TRUE, $1,
|
|
'{}'::jsonb, 'demo', NOW(), NOW())`,
|
|
[affProductId],
|
|
)
|
|
// 人工演示配置
|
|
await query(
|
|
`INSERT INTO manual_fulfillment_configs (
|
|
sku_key, canonical_product_id, platform_key, credit_quantity,
|
|
cost_mode, enabled, note, created_at, updated_at
|
|
) VALUES
|
|
('模拟自动发货商品', $1, 'nanhang', 100, 'fixed', TRUE, '自动发货兜底', NOW(), NOW()),
|
|
('模拟人工商品', $2, 'nanhang', 100, 'fixed', TRUE, '本地演示', NOW(), NOW())`,
|
|
[affProductId, manualProductId],
|
|
)
|
|
await query(
|
|
`INSERT INTO internal_sku_channels (
|
|
internal_sku, channel, priority, is_fallback, enabled, source, created_at, updated_at
|
|
) VALUES ('模拟人工商品', 'manual', 10, FALSE, TRUE, 'demo', NOW(), NOW())`,
|
|
)
|
|
|
|
// 1. 三方卡券:待分类,指定后自动建商品
|
|
const voucherOrder = await createOrder('sim-voucher', 'SIM-VOUCHER-1', 1950)
|
|
await createItem({ orderId: voucherOrder, skuCode: 'SIMV001', skuName: '模拟三方卡券' })
|
|
|
|
// 2. 自动发货:规则命中
|
|
const affOrder = await createOrder('sim-aff', 'SIM-AFF-1', 1000)
|
|
await createItem({ orderId: affOrder, skuCode: 'SIM_AFF_1', skuName: '模拟自动发货商品' })
|
|
|
|
// 3. 人工:待分类,指定人工后挂配置
|
|
const manualOrder = await createOrder('sim-manual', 'SIM-MANUAL-1', 1000)
|
|
await createItem({ orderId: manualOrder, skuCode: 'SIMM001', skuName: '模拟人工商品' })
|
|
|
|
// 4. 自动发货失败:应转人工并冲销采购成本
|
|
const fallbackOrder = await createOrder('sim-fallback', 'SIM-FALLBACK-1', 1000)
|
|
const fallbackProduct = await query<{ id: number }>(
|
|
`SELECT id FROM product_catalog WHERE canonical_sku='aff:rock_black_tshirt' LIMIT 1`,
|
|
)
|
|
const fallbackItem = await createItem({
|
|
orderId: fallbackOrder,
|
|
skuCode: 'SIMFB001',
|
|
skuName: '黑色ROCK长袖T恤',
|
|
channel: 'affiliate_dash',
|
|
status: 'assigned',
|
|
productId: fallbackProduct.rows[0]?.id,
|
|
})
|
|
await query(
|
|
`INSERT INTO fulfillment_tasks (order_id, order_item_id, unit_index, task_no, provider, platform, shop_id, platform_order_id, profile_id, executor_key, task_status, delivery_status, created_at, updated_at)
|
|
VALUES ($1,$2,1,'SIM-FB-TASK','91kaquan','kuaishou','sim-fallback','SIM-FALLBACK-1',3,'affiliate_dash','closed','failed',NOW(),NOW())`,
|
|
[fallbackOrder, fallbackItem],
|
|
)
|
|
|
|
// 5. 未分类:无规则、无配置
|
|
const unmappedOrder = await createOrder('sim-unmapped', 'SIM-UNMAPPED-1', 1000)
|
|
await createItem({ orderId: unmappedOrder, skuCode: 'SIMUNMAPPED001', skuName: '模拟未分类商品' })
|
|
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
shops: SHOPS,
|
|
voucherOrder,
|
|
affOrder,
|
|
manualOrder,
|
|
fallbackOrder,
|
|
unmappedOrder,
|
|
note: '等待调度器处理;可在后台履约渠道-待分类队列、商品与成本中心观察',
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
)
|
|
}
|
|
|
|
main()
|
|
.catch((error) => {
|
|
console.error(error)
|
|
process.exitCode = 1
|
|
})
|
|
.finally(() => closeDb())
|