-- ============================================================================= -- 一次性迁移:库存状态列存量洗刷(配合 status 硬隔离上线) -- -- 背景 -- 三张库存表(stock_buy / stock_semi / stock_product)的 status 列自建表起 -- 就是「只写不读」的:入库时写一次 '在库',此后全仓再无任何代码更新它, -- 分配器也从不看它。现分配器已改为「仅 status = '在库' 才可被分配」 -- (见 app/services/inventory_reservation.py::allocatable_filter)。 -- -- 该过滤是 Fail-Closed 的:status 为 NULL、空串或任何非 '在库' 值的行 -- 都会被排除在候选集之外。因此上线该过滤**之前**必须先跑本脚本,把 -- 本该可用的存量行刷回 '在库',否则它们将集体无法出库。 -- -- 洗刷范围(刻意收敛,只动 status 一列) -- (status IS NULL OR status <> '在库') -- AND (stock_quantity > 0 OR available_quantity > 0) -- -- · 为什么带 stock_quantity 条件,而不只是 available_quantity: -- 借出未还的行 available_quantity 可能为 0 而 stock_quantity > 0。 -- 归还时 process_return() 会给该行加回 available_quantity,届时若 -- status 仍为 NULL,这行就永远出不去了。此处一并覆盖,堵住这个未来缺口。 -- -- · 为什么排除「无实物的行」(stock_quantity / available_quantity 均为空或 0): -- 这类行多为历史脏数据(实测 stock_buy id=807 是整行全空的幽灵记录)。 -- 把它们刷成 '在库' 等于凭空声明一批并不存在的合格库存,是有害的。 -- 它们本来就不会被分配(分配器要求 available_quantity > 0),保持原样无害。 -- 可用文末「0-b)」的查询把它们捞出来人工核对。 -- -- ● 为什么**不**刷 quality_status / inspection_status(对原始需求的偏离,务必知悉) -- 1) stock_buy **没有** quality_status 列,只有 inspection_status。 -- 统一写 `SET quality_status='合格'` 会直接报 column does not exist, -- 脚本根本跑不完。 -- 2) 更要命的是语义:stock_semi 有 24 行 quality_status='待检'、 -- stock_buy 有 2042 行 inspection_status='未检'(其中 1766 行可用量 > 0)。 -- 把它们刷成 '合格' 属于**伪造质检结论** —— 等于把从未检验过的货 -- 当成合格品放行,与本次改造「不让坏件流出」的目的正好相反。 -- 因此本脚本只洗 status。如确有补空需求,见文末「3) 可选:质量列补空」, -- 那里只补 NULL,绝不覆盖任何已有取值。 -- -- 幂等性 -- WHERE 条件保证可重复执行:第二次执行影响 0 行。 -- -- 执行 -- docker exec -i inventory_db psql -U test -d inventory_system < 本文件 -- ============================================================================= BEGIN; -- --------------------------------------------------------------------------- -- 0) 执行前预览:将被洗刷的行数(建议先单独跑这一段确认无误再跑 1) -- -- ★ 用 COALESCE 而非裸比较:库存表允许数量列为 NULL,而 `NULL > 0` 求值为 -- NULL(非真),裸写会让「数量为 NULL」的脏行既不进洗刷、也不进 0-b 诊断, -- 在两次查询里凭空消失。COALESCE 让两个条件互为补集,口径严密。 -- --------------------------------------------------------------------------- SELECT 'stock_buy' AS tbl, count(*) AS will_update FROM stock_buy WHERE (status IS NULL OR status <> '在库') AND (COALESCE(stock_quantity, 0) > 0 OR COALESCE(available_quantity, 0) > 0) UNION ALL SELECT 'stock_semi', count(*) FROM stock_semi WHERE (status IS NULL OR status <> '在库') AND (COALESCE(stock_quantity, 0) > 0 OR COALESCE(available_quantity, 0) > 0) UNION ALL SELECT 'stock_product', count(*) FROM stock_product WHERE (status IS NULL OR status <> '在库') AND (COALESCE(stock_quantity, 0) > 0 OR COALESCE(available_quantity, 0) > 0); -- --------------------------------------------------------------------------- -- 0-b) 被跳过的行(无实物但状态异常,多为历史脏数据)—— 仅诊断,不修改 -- 与 0) 严格互补,两者行数之和 = 该表 status 非「在库」的总行数 -- --------------------------------------------------------------------------- SELECT 'stock_buy' AS tbl, id, sku, barcode, status, stock_quantity, available_quantity FROM stock_buy WHERE (status IS NULL OR status <> '在库') AND COALESCE(stock_quantity, 0) <= 0 AND COALESCE(available_quantity, 0) <= 0 UNION ALL SELECT 'stock_semi', id, sku, barcode, status, stock_quantity, available_quantity FROM stock_semi WHERE (status IS NULL OR status <> '在库') AND COALESCE(stock_quantity, 0) <= 0 AND COALESCE(available_quantity, 0) <= 0 UNION ALL SELECT 'stock_product', id, sku, barcode, status, stock_quantity, available_quantity FROM stock_product WHERE (status IS NULL OR status <> '在库') AND COALESCE(stock_quantity, 0) <= 0 AND COALESCE(available_quantity, 0) <= 0; -- --------------------------------------------------------------------------- -- 1) 洗刷 status(三表口径一致;stock_buy 无 quality_status,故不涉及) -- --------------------------------------------------------------------------- UPDATE stock_buy SET status = '在库' WHERE (status IS NULL OR status <> '在库') AND (COALESCE(stock_quantity, 0) > 0 OR COALESCE(available_quantity, 0) > 0); UPDATE stock_semi SET status = '在库' WHERE (status IS NULL OR status <> '在库') AND (COALESCE(stock_quantity, 0) > 0 OR COALESCE(available_quantity, 0) > 0); UPDATE stock_product SET status = '在库' WHERE (status IS NULL OR status <> '在库') AND (COALESCE(stock_quantity, 0) > 0 OR COALESCE(available_quantity, 0) > 0); -- --------------------------------------------------------------------------- -- 2) 执行后核对:三行结果应全部为 0 -- (统计口径与 1) 的 WHERE 完全一致,故「有实物且状态非在库」的行应为 0) -- --------------------------------------------------------------------------- SELECT 'stock_buy' AS tbl, count(*) AS remaining FROM stock_buy WHERE (status IS NULL OR status <> '在库') AND (COALESCE(stock_quantity, 0) > 0 OR COALESCE(available_quantity, 0) > 0) UNION ALL SELECT 'stock_semi', count(*) FROM stock_semi WHERE (status IS NULL OR status <> '在库') AND (COALESCE(stock_quantity, 0) > 0 OR COALESCE(available_quantity, 0) > 0) UNION ALL SELECT 'stock_product', count(*) FROM stock_product WHERE (status IS NULL OR status <> '在库') AND (COALESCE(stock_quantity, 0) > 0 OR COALESCE(available_quantity, 0) > 0); -- --------------------------------------------------------------------------- -- 3) 可选:质量列补空 —— 默认**注释掉**,需要时人工确认后再放开 -- -- ★ 只补 NULL,不覆盖任何已有取值。 -- '待检' / '未检' 是真实业务状态,绝不能被本脚本抹成 '合格'。 -- 实测当前三表均无 NULL 的质量列(stock_buy.inspection_status 有 2042 行为 -- '未检',是有值状态,不在补空范围内),故本段在当前数据上是空操作。 -- --------------------------------------------------------------------------- -- UPDATE stock_semi SET quality_status = '合格' WHERE quality_status IS NULL; -- UPDATE stock_product SET quality_status = '合格' WHERE quality_status IS NULL; -- UPDATE stock_buy SET inspection_status = '未检' WHERE inspection_status IS NULL; -- ↑ 采购件补的是「未检」而非「合格」:默认未检验是保守且诚实的取值。 COMMIT;