feat(finance): 商品渠道能力、自动发货转人工与成本异常收口

- 商品可同时支持自动发货/人工,新增渠道能力与成本冲销字段
- 卡券缺商品时按内部中文 SKU 自动建 tpv: 商品并补渠道规则
- 自动发货失败转人工并冲销原渠道成本
- 配置成本后按最早缺失订单生效并立即补算、关闭异常
- tpv: 商品来源计入第三方卡券
This commit is contained in:
yml2213
2026-11-02 13:17:15 +08:00
parent 86b5347e40
commit 3c4c81003f
16 changed files with 532 additions and 34 deletions
@@ -0,0 +1,100 @@
-- 115_product_channel_capabilities.sql
--
-- 渠道能力与履约兜底:
-- 1. 商品可同时具备多个渠道能力(自动发货 + 人工兜底);
-- 2. 订单项记录原始渠道与切换原因;
-- 3. 成本快照支持冲销(reversed_at),切换渠道时不双算;
-- 4. fulfillment_source 增加 affiliate_fallback。
CREATE TABLE IF NOT EXISTS product_channel_capabilities (
product_id BIGINT NOT NULL REFERENCES product_catalog(id) ON DELETE CASCADE,
channel TEXT NOT NULL,
priority INTEGER NOT NULL DEFAULT 100,
is_fallback BOOLEAN NOT NULL DEFAULT FALSE,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
source TEXT NOT NULL DEFAULT 'auto',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (product_id, channel)
);
COMMENT ON TABLE product_channel_capabilities IS
'商品渠道能力:一个商品可同时支持自动发货、人工等;priority 越小越优先';
-- 自动发货商品:默认自动优先,人工兜底
INSERT INTO product_channel_capabilities (product_id, channel, priority, is_fallback)
SELECT p.id, 'affiliate_dash', 10, FALSE
FROM product_catalog p
WHERE p.canonical_sku LIKE 'aff:%' AND p.status = 'active'
ON CONFLICT (product_id, channel) DO NOTHING;
INSERT INTO product_channel_capabilities (product_id, channel, priority, is_fallback)
SELECT p.id, 'manual', 90, TRUE
FROM product_catalog p
WHERE p.canonical_sku LIKE 'aff:%' AND p.status = 'active'
ON CONFLICT (product_id, channel) DO NOTHING;
-- 人工商品:人工优先
INSERT INTO product_channel_capabilities (product_id, channel, priority, is_fallback)
SELECT p.id, 'manual', 10, FALSE
FROM product_catalog p
WHERE p.canonical_sku LIKE 'man:%' AND p.status = 'active'
ON CONFLICT (product_id, channel) DO NOTHING;
-- 卡券:只统计
INSERT INTO product_channel_capabilities (product_id, channel, priority, is_fallback)
SELECT p.id, 'third_party_voucher', 10, FALSE
FROM product_catalog p
WHERE p.canonical_sku LIKE 'tpv:%' AND p.status = 'active'
ON CONFLICT (product_id, channel) DO NOTHING;
-- 接单模板/工单商品
INSERT INTO product_channel_capabilities (product_id, channel, priority, is_fallback)
SELECT p.id, 'work_order', 10, FALSE
FROM product_catalog p
WHERE (p.canonical_sku LIKE 'tpl:%' OR p.canonical_sku LIKE 'wo:%') AND p.status = 'active'
ON CONFLICT (product_id, channel) DO NOTHING;
ALTER TABLE order_items
ADD COLUMN IF NOT EXISTS original_channel TEXT,
ADD COLUMN IF NOT EXISTS channel_switch_reason TEXT NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS channel_switched_at TIMESTAMPTZ;
COMMENT ON COLUMN order_items.original_channel IS
'首次分流渠道;fallback/运营改渠道后仍保留,用于审计';
COMMENT ON COLUMN order_items.channel_switch_reason IS
'渠道切换原因:affiliate_failed / manual_supplement / operator';
ALTER TABLE cost_snapshots
ADD COLUMN IF NOT EXISTS reversed_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS reversal_reason TEXT NOT NULL DEFAULT '';
DROP INDEX IF EXISTS uq_cost_snapshots_active_subject_item;
CREATE UNIQUE INDEX IF NOT EXISTS uq_cost_snapshots_active_subject_item
ON cost_snapshots (subject_type, subject_id, cost_item_id)
WHERE superseded_at IS NULL AND reversed_at IS NULL;
-- 人工成本快照同样排除已冲销行,允许冲销后重新确认。
DROP INDEX IF EXISTS uq_cost_snapshots_manual_order_item;
CREATE UNIQUE INDEX IF NOT EXISTS uq_cost_snapshots_manual_order_item
ON cost_snapshots (order_item_id)
WHERE subject_type = 'manual_case' AND superseded_at IS NULL
AND reversed_at IS NULL AND subject_id > 0;
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'chk_order_items_fulfillment_source'
) THEN
ALTER TABLE order_items DROP CONSTRAINT chk_order_items_fulfillment_source;
END IF;
ALTER TABLE order_items
ADD CONSTRAINT chk_order_items_fulfillment_source
CHECK (
fulfillment_source IN (
'', 'auto_rule', 'auto_mapping', 'carrier_evidence',
'manual_confirm', 'operator_assigned', 'cutover_archive',
'affiliate_fallback'
)
);
END $$;