激活库存表长期「只写不读」的 status 列,使其成为分配准入的硬门槛。 - inventory_reservation: 新增状态语义单一事实来源(STOCK_STATUS_*/ allocatable_filter/is_allocatable);verify_scanned 增加状态准入校验 (覆盖出库与借库两条提交路径) - outbound: _allocate_bom_requirements 过滤条件加 allocatable_filter, 非「在库」一律不进入候选集;扫码路由把 ValueError 转为 400 - outbound_service: create_outbound_batch 强制 request_id 必填。原先无单 时会跳到所谓「散单」分支,而该分支的扣减逻辑从未落地(注释点名的 _apply_reservation_override 全仓不存在),实测只写台账不扣库存, 同一批货可被反复出库;get_stock_by_barcode 接入扫码准入;维修单扫码 补排除「报废转出」;预占再平衡块补 rollback 兜底 - fill_missing_stock_status.sql: 把存量行刷回「在库」。该过滤是 Fail-Closed 的,不先洗刷会让全库无法出库 破坏性变更:不传 request_id 的出库请求现在会被拒绝。
141 lines
7.6 KiB
PL/PgSQL
141 lines
7.6 KiB
PL/PgSQL
-- =============================================================================
|
||
-- 一次性迁移:库存状态列存量洗刷(配合 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;
|