Files
KCGL/db_migrations/phase3_defective_qty_tracking.sql
yueli 69c38a1bf7 feat(return): 逆向物流数据模型与迁移
新增原单退回与不良品在管的持久化结构。

- TransOutbound 增 returned_quantity(numeric(19,4),非 float):该值参与
  「return_qty <= quantity - returned_quantity」判等,浮点误差会让反复部分
  退回后出现「已退满却判定未退满」的错判
- 新增 TransReturn:退回流水,每次退回写一条而非覆盖式更新。刻意与
  trans_borrow 划清界限——后者部分归还时会覆盖 return_time/operator,
  导致归还历史永久丢失
- 新增 TransDefectiveGoods:不良品在管台账。坏件全程不入库存表,因为
  status 是行级属性而质量是件级属性,把坏件加回原行只能整行打不良
  (实测 stock_buy 单行最大 4789 件、中位 8 件,整行打不良会凭空损失良品)
- 状态机:待处理 → 处理中 → {已回库|已报废|已闭环}。终态由累计去向推导
  而非「最后一次动作」——一批坏件可能既回库过又报废过,按最后动作定状态
  会产生误导
- restocked_qty/scrapped_qty 两列:二期用 quantity-remaining_qty 反推回库量,
  三期加入报废出口后该反推失效
- 审计白名单与模型预加载同步登记(监听器绑定 18 → 20 个模型)

迁移脚本均为纯追加式 DDL,含预检、回滚段与执行后核对。首个脚本用
COALESCE 包裹数量列——库存表允许数量为 NULL,而「NULL 大于 0」求值为
NULL 而非真,裸写会让脏行在预览与诊断两次查询里凭空消失。
2026-09-16 15:45:22 +08:00

85 lines
4.3 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =============================================================================
-- 三期迁移:坏件处置量跟踪(配合三期的「坏件报废」闭环)
--
-- 背景
-- 二期只支持坏件的「回库」一种出口,故用 `quantity - remaining_qty` 就能
-- 反推已回库量。三期补上「报废」出口后,这个反推就不再成立 ——
-- 它会把报废掉的数量误算成已回库量。
--
-- 因此拆出两个独立累计列restocked_qty累计回库与 scrapped_qty累计报废
--
-- ★ 不变式(全表恒成立,测试中逐行校验):
-- restocked_qty + scrapped_qty + remaining_qty = quantity
-- 暂不加 DB 层 CHECK 约束:本系统库存数量普遍以 Python float 累加后再
-- 落 numeric(19,4),极端小数下可能出现 4 位以外的舍入抖动,硬约束会把
-- 一次合法操作变成 500。改为在接口层维护并由测试守住不变式。
--
-- 状态值归一
-- 二期把「部分处置」命名为 '部分回库'。三期报废也会产生部分状态,旧名不再
-- 准确,统一改为 '处理中'。新状态机(取值见 app/models/transaction.py
-- 待处理 → 处理中 → ┬ 已回库(全部回库)
-- ├ 已报废(全部报废)
-- └ 已闭环(回库与报废混合)
--
-- 安全性:纯追加式 DDL新列带 DEFAULT 0既有行自动补 0。
-- 幂等:全部 IF NOT EXISTS / 带条件的 UPDATE可重复执行。
--
-- 执行
-- docker exec -i inventory_db psql -U test -d inventory_system < 本文件
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- 1) 处置量跟踪列
-- ---------------------------------------------------------------------------
ALTER TABLE trans_defective_goods
ADD COLUMN IF NOT EXISTS restocked_qty numeric(19,4) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS scrapped_qty numeric(19,4) NOT NULL DEFAULT 0;
COMMENT ON COLUMN trans_defective_goods.restocked_qty IS '累计已回库数量';
COMMENT ON COLUMN trans_defective_goods.scrapped_qty IS '累计已报废数量';
-- ---------------------------------------------------------------------------
-- 2) 存量回填
-- 迁移前只有「回库」一种出口,故「已减少的在管量」全部归入 restocked_qty。
-- 本表在写此迁移时为空,此段为幂等防御,防止在其它环境(已有数据)执行时
-- 出现 restocked/scrapped 全 0 而 remaining 已减少的不一致状态。
-- ---------------------------------------------------------------------------
UPDATE trans_defective_goods
SET restocked_qty = GREATEST(COALESCE(quantity, 0) - COALESCE(remaining_qty, 0), 0)
WHERE restocked_qty = 0
AND scrapped_qty = 0
AND COALESCE(quantity, 0) > COALESCE(remaining_qty, 0);
-- ---------------------------------------------------------------------------
-- 3) 状态值归一:'部分回库' → '处理中'
-- ---------------------------------------------------------------------------
UPDATE trans_defective_goods SET status = '处理中' WHERE status = '部分回库';
-- ---------------------------------------------------------------------------
-- 4) 执行后核对:不一致行数必须为 0
-- ---------------------------------------------------------------------------
\echo '--- 处置量不变式核对inconsistent 应为 0---'
SELECT count(*) AS total,
count(*) FILTER (
WHERE COALESCE(restocked_qty,0) + COALESCE(scrapped_qty,0)
+ COALESCE(remaining_qty,0) <> COALESCE(quantity,0)
) AS inconsistent
FROM trans_defective_goods;
\echo '--- 状态取值分布 ---'
SELECT COALESCE(status, '<NULL>') AS status, count(*) FROM trans_defective_goods GROUP BY 1;
COMMIT;
-- =============================================================================
-- 回滚段(仅撤销三期变更;不动二期表结构)
-- =============================================================================
-- BEGIN;
-- ALTER TABLE trans_defective_goods DROP COLUMN IF EXISTS restocked_qty;
-- ALTER TABLE trans_defective_goods DROP COLUMN IF EXISTS scrapped_qty;
-- UPDATE trans_defective_goods SET status = '部分回库' WHERE status = '处理中';
-- COMMIT;