清理履约假草稿商品残留关联

This commit is contained in:
yml
2026-11-15 00:06:09 +08:00
parent 36f52a7f68
commit e77f96ee3c
3 changed files with 102 additions and 7 deletions
@@ -0,0 +1,68 @@
-- 167_fulfillment_invalid_draft_product_cleanup.sql
--
-- 迁移 166 已停止生成并归档接单平台的 tpl:<内部SKU> 假草稿商品。
-- 待判定履约记录仍可能保留这些商品的派生关联,需要在重试前清空,
-- 避免页面继续展示旧商品或后续成本归集到错误商品。
WITH invalid_products AS (
SELECT product.id
FROM product_catalog product
WHERE product.source_kind = 'work_order'
AND product.canonical_sku LIKE 'tpl:%'
AND COALESCE(product.attributes_json ->> 'draft', 'false') = 'true'
AND NOT EXISTS (
SELECT 1
FROM work_product_rules rule
WHERE rule.canonical_product_id = product.id
)
)
UPDATE fulfillments fulfillment
SET canonical_product_id = NULL,
updated_at = NOW()
FROM invalid_products invalid
WHERE fulfillment.canonical_product_id = invalid.id;
CREATE OR REPLACE FUNCTION assert_fulfillment_product_not_invalid_draft()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.canonical_product_id IS NULL THEN
RETURN NEW;
END IF;
IF EXISTS (
SELECT 1
FROM product_catalog product
WHERE product.id = NEW.canonical_product_id
AND product.source_kind = 'work_order'
AND product.canonical_sku LIKE 'tpl:%'
AND COALESCE(product.attributes_json ->> 'draft', 'false') = 'true'
AND NOT EXISTS (
SELECT 1
FROM work_product_rules rule
WHERE rule.canonical_product_id = product.id
)
) THEN
RAISE EXCEPTION
'fulfillment % cannot reference invalid work-order draft product %',
NEW.order_item_id,
NEW.canonical_product_id
USING ERRCODE = '23503',
HINT = 'Attach the canonical product from the matched work_product_rule instead.';
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_fulfillments_invalid_draft_product
ON fulfillments;
CREATE TRIGGER trg_fulfillments_invalid_draft_product
BEFORE INSERT OR UPDATE OF canonical_product_id
ON fulfillments
FOR EACH ROW
EXECUTE FUNCTION assert_fulfillment_product_not_invalid_draft();
COMMENT ON FUNCTION assert_fulfillment_product_not_invalid_draft() IS
'履约商品不能指向接单平台历史伪造的 tpl 草稿商品;必须使用真实接单模板商品';
@@ -281,6 +281,30 @@ test(
finalFulfillments.rows.every((row) => row.issue_code === '' && row.pending_reason === null),
)
const invalidDraftProduct = await query<{ id: number }>(
`INSERT INTO product_catalog (
canonical_sku, canonical_name, category, unit_name, source_kind, status,
attributes_json, created_at, updated_at
) VALUES (
$1, '历史假草稿商品', '接单测试', '件', 'work_order', 'archived',
'{"draft":true}'::jsonb, NOW(), NOW()
) RETURNING id`,
[`tpl:invalid-${suffix}`],
)
const invalidDraftProductId = Number(invalidDraftProduct.rows[0]!.id)
productIds.push(invalidDraftProductId)
await assert.rejects(
() =>
query(
`UPDATE fulfillments
SET canonical_product_id = $2,
updated_at = NOW()
WHERE order_item_id = $1`,
[orderItemIds[0], invalidDraftProductId],
),
/invalid work-order draft product/,
)
await assert.rejects(
() =>
query(