refactor(db): 财务渠道迁移合并为单条 113
生产当前在 112,原 113–118 六条迁移合并为 113_finance_channel_cutover: - 渠道/状态三列 + 从 fulfillment_mode 回填 - 删除旧列及旧索引 - 销售事实列改名 channel - 财务管道增量索引(原 117/118) - 移除不再使用的 refund_adjustment 约束(历史退款不入财务) 42 万行副本实测 49 秒完成;生产只需执行一条迁移,不再需要中间回填脚本。
This commit is contained in:
+135
-1
@@ -1,4 +1,14 @@
|
||||
-- 113_fulfillment_channel_split.sql
|
||||
-- 113_finance_channel_cutover.sql
|
||||
--
|
||||
-- 财务渠道模型的一次性升级(合并原 113–118):
|
||||
-- 1. 订单项增加渠道/状态/来源三列,并从旧的 fulfillment_mode 回填;
|
||||
-- 2. 删除旧列及其索引;
|
||||
-- 3. 销售事实维度改为 channel;
|
||||
-- 4. 补齐财务管道增量索引。
|
||||
--
|
||||
-- 生产基线为 112,本迁移是断代升级的唯一一步;执行后立即跑 finance:cutover-v2。
|
||||
-- 历史订单退款不进入新财务口径,因此不再引入 refund_adjustment 相关约束。
|
||||
|
||||
--
|
||||
-- 目标:把「渠道 / 状态 / 决策来源」从一列拆成三列,并把渠道决策从
|
||||
-- 「有没有任务」反推改为「配置驱动」。
|
||||
@@ -429,3 +439,127 @@ COMMENT ON TABLE manual_sku_registry IS
|
||||
-- 1. scripts/backfill-fulfillment-channel.ts —— 438k 行分批回填 channel/status
|
||||
-- 2. scripts/finance-cutover-v2.ts —— 断代归档 + 派生数据清零
|
||||
-- 3. scripts/rebuild-after-cutover.ts —— 10-31 起点数据重建 + 期初建账
|
||||
|
||||
|
||||
-- ===========================================================================
|
||||
-- 从旧 fulfillment_mode 回填新三列(此后才能删除旧列)
|
||||
-- ===========================================================================
|
||||
UPDATE order_items
|
||||
SET
|
||||
fulfillment_channel = CASE
|
||||
WHEN fulfillment_mode = 'legacy_platform' THEN NULL
|
||||
ELSE fulfillment_mode
|
||||
END,
|
||||
fulfillment_status = CASE
|
||||
WHEN fulfillment_mode = 'legacy_platform' THEN 'archived'
|
||||
ELSE 'assigned'
|
||||
END,
|
||||
fulfillment_source = CASE
|
||||
WHEN fulfillment_mode = 'legacy_platform' THEN 'cutover_archive'
|
||||
ELSE 'carrier_evidence'
|
||||
END,
|
||||
fulfillment_decided_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE fulfillment_mode IS NOT NULL
|
||||
AND fulfillment_channel IS NULL;
|
||||
|
||||
UPDATE order_items
|
||||
SET fulfillment_status = 'pending', fulfillment_channel = NULL
|
||||
WHERE fulfillment_mode IS NULL
|
||||
AND fulfillment_status <> 'pending';
|
||||
|
||||
--
|
||||
-- 强制新模型:彻底删除旧 `order_items.fulfillment_mode` 及其索引/约束。
|
||||
--
|
||||
-- 背景:
|
||||
-- · 113 已加入 fulfillment_channel + fulfillment_status + fulfillment_source;
|
||||
-- · 代码侧已全部改为读写新列,`fulfillment_mode` 不再有任何读写方;
|
||||
-- · 保留旧列只会让「新旧并存」继续存在,因此这一版直接删除。
|
||||
--
|
||||
-- 注意:
|
||||
-- 1. 本迁移只做 DDL,不搬运数据;
|
||||
-- 2. 升级时应在维护窗口内先跑迁移,紧接着执行 `npm run finance:cutover-v2`
|
||||
-- 完成断代归档与派生数据清理,避免中间态被业务读到;
|
||||
-- 3. 已被 113 标记为 archived 的历史行不受影响。
|
||||
|
||||
-- 旧版迁移曾加过「旧异常写入口」兼容触发器,这里一并清理,不再保留兼容层。
|
||||
DROP TRIGGER IF EXISTS trg_cost_exceptions_fill_scope_key ON cost_exceptions;
|
||||
DROP FUNCTION IF EXISTS cost_exceptions_fill_scope_key();
|
||||
|
||||
ALTER TABLE order_items
|
||||
DROP CONSTRAINT IF EXISTS chk_order_items_fulfillment_mode;
|
||||
|
||||
-- 依赖旧列的部分索引必须先删,否则 DROP COLUMN 会被拒绝。
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_mode;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_pending;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_id;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_pending_id;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_product;
|
||||
DROP INDEX IF EXISTS idx_order_items_manual_pending_cover;
|
||||
DROP INDEX IF EXISTS idx_order_items_manual_sku_key_stored;
|
||||
DROP INDEX IF EXISTS idx_order_items_manual_internal_sku;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_config;
|
||||
|
||||
ALTER TABLE order_items
|
||||
DROP COLUMN IF EXISTS fulfillment_mode;
|
||||
|
||||
COMMENT ON COLUMN order_items.fulfillment_channel IS
|
||||
'履约渠道(唯一真源):work_order / affiliate_dash / third_party_voucher / manual;pending 时为空';
|
||||
COMMENT ON COLUMN order_items.fulfillment_status IS
|
||||
'渠道状态:pending 待分类 / assigned 已确定 / archived 断代归档';
|
||||
|
||||
--
|
||||
-- 命名统一:销售事实的维度列由 fulfillment_mode 改为 channel。
|
||||
-- 值本身在 113 之后已经是渠道(含 unclassified),旧名字只会误导。
|
||||
-- 唯一约束与索引会自动跟随列重命名,这里只额外修正索引名。
|
||||
|
||||
ALTER TABLE sales_daily_facts
|
||||
RENAME COLUMN fulfillment_mode TO channel;
|
||||
|
||||
ALTER INDEX IF EXISTS idx_sales_daily_facts_mode
|
||||
RENAME TO idx_sales_daily_facts_channel;
|
||||
|
||||
COMMENT ON COLUMN sales_daily_facts.channel IS
|
||||
'渠道维度:work_order / affiliate_dash / third_party_voucher / manual / unclassified';
|
||||
|
||||
--
|
||||
-- 财务管道按订单项 id 增量扫描时的专用部分索引。
|
||||
-- 42 万行实测:carrier_dispatch 的「人工配置待挂载」查询原本 10.9s(扫描 12 万人工单),
|
||||
-- 加上按 id 的部分索引后可走索引顺序扫描并提前 LIMIT 结束。
|
||||
|
||||
-- carrier_dispatch:人工渠道待挂配置
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_manual_pending_config_id
|
||||
ON order_items (id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel = 'manual'
|
||||
AND fulfillment_config_id IS NULL;
|
||||
|
||||
-- carrier_dispatch:已配置但未确认履约(人工成本未记录)
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_manual_configured_id
|
||||
ON order_items (id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel = 'manual'
|
||||
AND fulfillment_config_id IS NOT NULL;
|
||||
|
||||
-- carrier_dispatch:自动发货/接单平台缺少履约载体
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_carrier_check_id
|
||||
ON order_items (id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel IN ('affiliate_dash', 'work_order');
|
||||
|
||||
-- cost_snapshot:按 id 顺序扫描需要核算成本的订单项
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_cost_ready_id
|
||||
ON order_items (id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel IN (
|
||||
'affiliate_dash', 'third_party_voucher', 'work_order', 'manual'
|
||||
);
|
||||
|
||||
--
|
||||
-- carrier_dispatch 按「人工 SKU 配置」反查订单项时,先按 manual_sku_key 命中再按 id 排序;
|
||||
-- 42 万行实测原查询 3.3s,补 (manual_sku_key, id) 部分索引后可走位图索引 + 小结果集排序。
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_manual_sku_key_id
|
||||
ON order_items (manual_sku_key, id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel = 'manual';
|
||||
@@ -1,40 +0,0 @@
|
||||
-- 114_drop_legacy_fulfillment_mode.sql
|
||||
--
|
||||
-- 强制新模型:彻底删除旧 `order_items.fulfillment_mode` 及其索引/约束。
|
||||
--
|
||||
-- 背景:
|
||||
-- · 113 已加入 fulfillment_channel + fulfillment_status + fulfillment_source;
|
||||
-- · 代码侧已全部改为读写新列,`fulfillment_mode` 不再有任何读写方;
|
||||
-- · 保留旧列只会让「新旧并存」继续存在,因此这一版直接删除。
|
||||
--
|
||||
-- 注意:
|
||||
-- 1. 本迁移只做 DDL,不搬运数据;
|
||||
-- 2. 升级时应在维护窗口内先跑迁移,紧接着执行 `npm run finance:cutover-v2`
|
||||
-- 完成断代归档与派生数据清理,避免中间态被业务读到;
|
||||
-- 3. 已被 113 标记为 archived 的历史行不受影响。
|
||||
|
||||
-- 旧版迁移曾加过「旧异常写入口」兼容触发器,这里一并清理,不再保留兼容层。
|
||||
DROP TRIGGER IF EXISTS trg_cost_exceptions_fill_scope_key ON cost_exceptions;
|
||||
DROP FUNCTION IF EXISTS cost_exceptions_fill_scope_key();
|
||||
|
||||
ALTER TABLE order_items
|
||||
DROP CONSTRAINT IF EXISTS chk_order_items_fulfillment_mode;
|
||||
|
||||
-- 依赖旧列的部分索引必须先删,否则 DROP COLUMN 会被拒绝。
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_mode;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_pending;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_id;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_pending_id;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_product;
|
||||
DROP INDEX IF EXISTS idx_order_items_manual_pending_cover;
|
||||
DROP INDEX IF EXISTS idx_order_items_manual_sku_key_stored;
|
||||
DROP INDEX IF EXISTS idx_order_items_manual_internal_sku;
|
||||
DROP INDEX IF EXISTS idx_order_items_fulfillment_config;
|
||||
|
||||
ALTER TABLE order_items
|
||||
DROP COLUMN IF EXISTS fulfillment_mode;
|
||||
|
||||
COMMENT ON COLUMN order_items.fulfillment_channel IS
|
||||
'履约渠道(唯一真源):work_order / affiliate_dash / third_party_voucher / manual;pending 时为空';
|
||||
COMMENT ON COLUMN order_items.fulfillment_status IS
|
||||
'渠道状态:pending 待分类 / assigned 已确定 / archived 断代归档';
|
||||
@@ -1,14 +0,0 @@
|
||||
-- 115_rename_facts_channel.sql
|
||||
--
|
||||
-- 命名统一:销售事实的维度列由 fulfillment_mode 改为 channel。
|
||||
-- 值本身在 113 之后已经是渠道(含 unclassified),旧名字只会误导。
|
||||
-- 唯一约束与索引会自动跟随列重命名,这里只额外修正索引名。
|
||||
|
||||
ALTER TABLE sales_daily_facts
|
||||
RENAME COLUMN fulfillment_mode TO channel;
|
||||
|
||||
ALTER INDEX IF EXISTS idx_sales_daily_facts_mode
|
||||
RENAME TO idx_sales_daily_facts_channel;
|
||||
|
||||
COMMENT ON COLUMN sales_daily_facts.channel IS
|
||||
'渠道维度:work_order / affiliate_dash / third_party_voucher / manual / unclassified';
|
||||
@@ -1,39 +0,0 @@
|
||||
-- 116_refund_adjustment_facts.sql
|
||||
--
|
||||
-- 断代后历史订单仍会退款:这些订单项已 archived、不参与正常销售事实,
|
||||
-- 需要在「退款发生日」记一笔冲减,否则退款在财务体系里完全丢失。
|
||||
-- 这里新增事实渠道值 refund_adjustment 承载这类冲减行。
|
||||
|
||||
ALTER TABLE sales_daily_facts
|
||||
DROP CONSTRAINT IF EXISTS chk_sales_daily_facts_fulfillment_mode;
|
||||
ALTER TABLE sales_daily_facts
|
||||
DROP CONSTRAINT IF EXISTS chk_sales_daily_facts_channel;
|
||||
|
||||
ALTER TABLE sales_daily_facts
|
||||
ADD CONSTRAINT chk_sales_daily_facts_channel
|
||||
CHECK (
|
||||
channel IN (
|
||||
'work_order', 'affiliate_dash', 'third_party_voucher',
|
||||
'manual', 'unclassified', 'archived', 'refund_adjustment'
|
||||
)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN sales_daily_facts.channel IS
|
||||
'渠道维度:work_order / affiliate_dash / third_party_voucher / manual / unclassified / archived / refund_adjustment';
|
||||
|
||||
-- 退款冲减行允许净收入为负(其余渠道仍要求非负)。
|
||||
ALTER TABLE sales_daily_facts
|
||||
DROP CONSTRAINT IF EXISTS chk_sales_daily_facts_amounts;
|
||||
|
||||
ALTER TABLE sales_daily_facts
|
||||
ADD CONSTRAINT chk_sales_daily_facts_amounts
|
||||
CHECK (
|
||||
order_count >= 0
|
||||
AND item_count >= 0
|
||||
AND quantity >= 0
|
||||
AND gross_revenue_fen >= 0
|
||||
AND refund_amount_fen >= 0
|
||||
AND (net_revenue_fen >= 0 OR channel = 'refund_adjustment')
|
||||
AND cost_amount_fen >= 0
|
||||
AND cost_missing_count >= 0
|
||||
);
|
||||
@@ -1,33 +0,0 @@
|
||||
-- 117_fulfillment_pipeline_indexes.sql
|
||||
--
|
||||
-- 财务管道按订单项 id 增量扫描时的专用部分索引。
|
||||
-- 42 万行实测:carrier_dispatch 的「人工配置待挂载」查询原本 10.9s(扫描 12 万人工单),
|
||||
-- 加上按 id 的部分索引后可走索引顺序扫描并提前 LIMIT 结束。
|
||||
|
||||
-- carrier_dispatch:人工渠道待挂配置
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_manual_pending_config_id
|
||||
ON order_items (id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel = 'manual'
|
||||
AND fulfillment_config_id IS NULL;
|
||||
|
||||
-- carrier_dispatch:已配置但未确认履约(人工成本未记录)
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_manual_configured_id
|
||||
ON order_items (id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel = 'manual'
|
||||
AND fulfillment_config_id IS NOT NULL;
|
||||
|
||||
-- carrier_dispatch:自动发货/接单平台缺少履约载体
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_carrier_check_id
|
||||
ON order_items (id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel IN ('affiliate_dash', 'work_order');
|
||||
|
||||
-- cost_snapshot:按 id 顺序扫描需要核算成本的订单项
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_cost_ready_id
|
||||
ON order_items (id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel IN (
|
||||
'affiliate_dash', 'third_party_voucher', 'work_order', 'manual'
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
-- 118_order_items_manual_sku_index.sql
|
||||
--
|
||||
-- carrier_dispatch 按「人工 SKU 配置」反查订单项时,先按 manual_sku_key 命中再按 id 排序;
|
||||
-- 42 万行实测原查询 3.3s,补 (manual_sku_key, id) 部分索引后可走位图索引 + 小结果集排序。
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_manual_sku_key_id
|
||||
ON order_items (manual_sku_key, id)
|
||||
WHERE fulfillment_status = 'assigned'
|
||||
AND fulfillment_channel = 'manual';
|
||||
Reference in New Issue
Block a user