From c5356a27ed87b09fba707a1ae658729880421884 Mon Sep 17 00:00:00 2001 From: yueli Date: Wed, 23 Sep 2026 17:16:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(scrap):=20=E8=A1=A5=E4=B8=8A=E9=81=97?= =?UTF-8?q?=E6=BC=8F=E7=9A=84=20phase14=20=E8=BF=81=E7=A7=BB=EF=BC=88?= =?UTF-8?q?=E4=B8=8D=E8=89=AF=E5=93=81=E6=8A=A5=E5=BA=9F=E7=9A=84=E5=BA=93?= =?UTF-8?q?=E4=BD=8D/=E6=89=B9=E6=AC=A1=E5=BF=AB=E7=85=A7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:前面几个报废提交里,inventory-backend/app/models/transaction.py 已经 引用了 db_migrations/phase14_defective_goods_location_snapshot.sql(两处注释 指向它),但**该文件本身没被提交** —— 代码与迁移对不上,别人拉下来跑不起来。 内容:为 trans_defective_goods 增加 warehouse_location / batch_number 两列, 并从源库存行回填存量。 为什么需要这两列:报废台账页与报废申请单明细上,凡是不良品来源的行,库位与 批次此前一律显示 "-" —— 因为应用层硬编码了空串,理由是「源库存行可能已被 物理删除」。该担忧对 material_name/spec_model 成立(所以它们有冗余快照), 但库位/批次当时**没有**快照列,一律置空等于放弃了「源行还在」这个绝大多数 情形。补上快照后,退回那一刻 stock_row 已 with_for_update() 锁在手里, 库位/批次随手可取,落库后即使源行日后被删除,报表依然有值。 口径:存「原库位」(这批货最初在哪),与三张库存表来源的报废记录一致; 不良品区的位置本系统未记录。batch_number 对 stock_product 来源存的是 serial_number(成品表无 batch_number 列),与 scrap.py 的 _from_stock 对齐。 安全性:纯追加式 DDL,无 NOT NULL、无 DEFAULT,幂等可重复执行。 已在本机执行验证:存量 1 行全部回填,源行已删取不到 0 行。 --- ...se14_defective_goods_location_snapshot.sql | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 db_migrations/phase14_defective_goods_location_snapshot.sql diff --git a/db_migrations/phase14_defective_goods_location_snapshot.sql b/db_migrations/phase14_defective_goods_location_snapshot.sql new file mode 100644 index 0000000..4b431b2 --- /dev/null +++ b/db_migrations/phase14_defective_goods_location_snapshot.sql @@ -0,0 +1,108 @@ +-- ============================================================================= +-- 不良品在管台账:补「原库位 / 批次(序列号)」快照列 +-- +-- 背景 +-- trans_defective_goods 只冗余了 material_name / spec_model,却没有存库位与 +-- 批次。于是报废台账页、报废申请单明细上,凡是不良品来源的行,库位与 +-- 批次一律显示 "-" —— 而它们的源库存行往往**还在**,本该取得到。 +-- +-- 应用层原先的处置是在 _resolve_materials 与 DefectiveScrapAdapter.snapshot +-- 里直接硬编码空串,注释理由是「源库存行可能已被物理删除」。这个担忧对 +-- material_name/spec_model 成立(所以它们有冗余快照),但库位/批次**没有** +-- 快照列,一律置空等于放弃了「源行还在」这个绝大多数情形。 +-- +-- 本次改动(A+B) +-- A. 应用层改为「快照优先 + 回查源库存行兜底」(本次只加列,逻辑在代码侧) +-- B. 本文件补两列,把「源行可能被删」这个担忧真正解决掉:退回那一刻 +-- stock_row 已 with_for_update() 锁在手里,库位/批次随手可取, +-- 落库后即使源行日后被物理删除,报表依然有值。 +-- +-- 口径(2026-09-23 与业务确认) +-- 存的是「**原库位**」—— 这批货最初在哪,而不是坏件当前所在的不良品区。 +-- 与三张库存表来源的报废记录口径一致。不良品区的位置本系统未记录。 +-- +-- ★ batch_number 列对 stock_product 来源存的是 serial_number: +-- 成品表没有 batch_number 列(见 information_schema),取值口径沿用 +-- 应用层的 COALESCE(batch_number, serial_number),与 _from_stock 一致。 +-- 故本列语义为「批次或序列号」,与前端列名「批次/序列号」对齐。 +-- +-- 安全性:纯追加式 DDL,无 NOT NULL、无 DEFAULT,既有行取值 NULL。 +-- 幂等:IF NOT EXISTS + 带 WHERE 的 UPDATE,可重复执行。 +-- +-- 执行 +-- docker exec -i inventory_db psql -U test -d inventory_system < 本文件 +-- ============================================================================= + +BEGIN; + +-- --------------------------------------------------------------------------- +-- 1) 快照列(长度与三张库存表对齐,均为 varchar(100)) +-- --------------------------------------------------------------------------- +ALTER TABLE trans_defective_goods + ADD COLUMN IF NOT EXISTS warehouse_location varchar(100), + ADD COLUMN IF NOT EXISTS batch_number varchar(100); + +COMMENT ON COLUMN trans_defective_goods.warehouse_location IS + '原库位快照(退回时取自源库存行)。源行被物理删除时此处仍有值,故报表优先用它'; +COMMENT ON COLUMN trans_defective_goods.batch_number IS + '批次/序列号快照(退回时取自源库存行的 batch_number,成品表缺失时取 serial_number)'; + +-- --------------------------------------------------------------------------- +-- 2) 存量回填:按 source_table + stock_id 回查源库存行 +-- 三张表分别 UPDATE(多态关联,无法用一条语句表达)。 +-- WHERE warehouse_location IS NULL 保证幂等,且不覆盖已回填过的值。 +-- +-- 回填不到的(源行已被物理删除)保持 NULL —— 刻意不写成空串, +-- 这样下面的核对能数出「真正取不到」的行数,与「已确认无库位」区分开。 +-- --------------------------------------------------------------------------- +UPDATE trans_defective_goods g + SET warehouse_location = COALESCE(b.warehouse_location, ''), + batch_number = COALESCE(NULLIF(b.batch_number, ''), b.serial_number, '') + FROM stock_buy b + WHERE g.source_table = 'stock_buy' + AND g.stock_id = b.id + AND g.warehouse_location IS NULL; + +UPDATE trans_defective_goods g + SET warehouse_location = COALESCE(s.warehouse_location, ''), + batch_number = COALESCE(NULLIF(s.batch_number, ''), s.serial_number, '') + FROM stock_semi s + WHERE g.source_table = 'stock_semi' + AND g.stock_id = s.id + AND g.warehouse_location IS NULL; + +-- ★ 成品表无 batch_number 列,只取 serial_number +UPDATE trans_defective_goods g + SET warehouse_location = COALESCE(p.warehouse_location, ''), + batch_number = COALESCE(p.serial_number, '') + FROM stock_product p + WHERE g.source_table = 'stock_product' + AND g.stock_id = p.id + AND g.warehouse_location IS NULL; + +COMMIT; + + +-- ============================================================================= +-- 执行后核对 +-- ============================================================================= +SELECT '=== 1) 回填结果:已回填 / 源行已删取不到 / 无需回填 ===' AS "核对项"; +SELECT count(*) FILTER (WHERE warehouse_location IS NOT NULL) AS 已回填, + count(*) FILTER (WHERE warehouse_location IS NULL) AS 源行已删_取不到, + count(*) AS 台账总行数 + FROM trans_defective_goods; + +SELECT '=== 2) ★ 源行已删的明细(取不到库位属预期,应逐条确认是否为真)===' AS "核对项"; +SELECT id, source_table, stock_id, sku, status + FROM trans_defective_goods + WHERE warehouse_location IS NULL + ORDER BY id; + + +-- ============================================================================= +-- 回滚段(仅撤销本迁移;回填值与「原本就是空」无法区分) +-- ============================================================================= +-- BEGIN; +-- ALTER TABLE trans_defective_goods DROP COLUMN IF EXISTS warehouse_location; +-- ALTER TABLE trans_defective_goods DROP COLUMN IF EXISTS batch_number; +-- COMMIT;