Files
KCGL/db_migrations/phase7_outbound_reissue.sql
yueli 7071c89305 feat(outbound): 原单退回后可自动生成补发单
背景
----
原单退回只做两件事:良品加回库存 / 不良品转在管台账。但**申请人的需求并没有
被满足** —— 东西交回来了(甚至还是坏的),系统却不提醒任何人、无单据承载
「我要重新领一份」,退回与后续再出库之间也毫无关联。现场只能靠人记住再手建
一张出库申请,而那张单与原单看不出任何关系。

改动
----
· outbound_approval 新增 source_return_id(非空 = 补发单),把「退回 → 补发」
  串成闭环。
· POST /inbound/stock/return-from-outbound 新增 need_reissue / reissue_qty:
  勾选即自动生成一张**免审批**出库单(status=1,直接进入待执行),
  沿用原单出库类型,并关联回本笔退回。

★ 为什么只存退回单 ID,不加 is_reissue 布尔列
  「是不是补发」完全由来源是否存在决定,再加一列就是同一事实的两处存储,
  必然有不同步的一天。也不冗余存原出库单 ID:trans_return 已有 outbound_id。

★ 库存不足 → 整笔回滚(关键取舍)
  补发走 reserve_for_items(strict=True),与出库申请同一口径。不足时抛错,
  退回也一并回滚 —— 若只让补发静默失败,「需要补发」的意图就丢了,
  那正是本功能要解决的问题。库管看到提示后取消勾选即可只做退回过账。

★ 一个被发现的数据约束(改变了原设计)
  原打算把补发单的申请人设为「原出库单的申请人」,但 **trans_outbound 既没有
  申请人字段,也没有指回原审批单的关联**(扫码出库时只把审批单状态置为 3)。
  按 consumer_name 反查会重蹈「重名错绑」的覆辙。故申请人取**当前操作人**,
  原领用人写入备注供人工追溯。
  ⚠ 若业务要求补发单挂在原领用人名下,需要前端在退回弹窗里加一个「补发给谁」
    的人员选择 —— 请确认是否需要。

验证(打桩 JWT 直连真实接口,22 项断言全通过)
  勾选补发 → 免审批单生成、关联退回、预占库存、原领用人入备注;
  不勾选 → 不生成补发单、良品正常回库;
  ★ 库存不足 → 接口拒绝且**退回流水/补发单/退回额度全部未落库**(整笔回滚);
  补发量 > 退回量被拒;库存与数据零残留。
2026-09-17 11:56:38 +08:00

66 lines
3.1 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.

-- =============================================================================
-- 出库 · 原单退回后的「补发单」关联
--
-- 背景
-- 原单退回目前只做两件事:良品加回库存 / 不良品转在管台账。
-- 但**申请人的需求并没有被满足** —— 东西交回来了(甚至还是坏的),
-- 系统却:不提醒任何人、无单据承载「我要重新领一份」、退回与后续再出库
-- 之间毫无关联。现场只能靠人记住再手动新建一张出库申请,且那张单与原单
-- 看不出任何关系。
--
-- 本次改动
-- outbound_approval 新增 source_return_id非空即表示这是一张由「原单退回」
-- 自动生成的**补发单**,从而把「退回 → 补发」串成闭环,出库记录里也能
-- 一眼看出哪张单是补发的。
--
-- ---------------------------------------------------------------------------
-- ★ 为什么不加 is_reissue 布尔列
-- 「是不是补发」完全由「来源退回单是否存在」决定,再加一个布尔列就是
-- 同一事实的两处存储,必然有不同步的一天(改了 A 忘了 B
-- 单列既表达事实,又能直接 join 回原单。
--
-- ★ 为什么只存退回单 ID不另存原出库单 ID
-- trans_return 本身已有 outbound_id顺着 source_return_id 一跳即可拿到
-- 原出库明细。冗余存储只会带来「两张单对不上」的风险。
--
-- 幂等:带 IF NOT EXISTS可重复执行。不含 psql 元命令DataGrip 可直接执行。
-- =============================================================================
BEGIN;
ALTER TABLE outbound_approval
ADD COLUMN IF NOT EXISTS source_return_id integer;
COMMENT ON COLUMN outbound_approval.source_return_id IS
'补发单来源trans_return.id。非空即表示本单是由「原单退回」自动生成的补发单';
-- 支撑「这批补发是从哪张退回单来的」与列表标识
CREATE INDEX IF NOT EXISTS ix_outbound_approval_source_return
ON outbound_approval (source_return_id);
COMMIT;
-- =============================================================================
-- 执行后核对
-- =============================================================================
SELECT '=== 1) 新列已就位 ===' AS "核对项";
SELECT column_name, data_type FROM information_schema.columns
WHERE table_name = 'outbound_approval' AND column_name = 'source_return_id';
SELECT '=== 2) 索引已就位 ===' AS "核对项";
SELECT indexname FROM pg_indexes
WHERE tablename = 'outbound_approval' AND indexname = 'ix_outbound_approval_source_return';
SELECT '=== 3) 存量补发单(新功能上线前应为 0===' AS "核对项";
SELECT count(*) AS FROM outbound_approval WHERE source_return_id IS NOT NULL;
-- =============================================================================
-- 回滚段
-- =============================================================================
-- BEGIN;
-- DROP INDEX IF EXISTS ix_outbound_approval_source_return;
-- ALTER TABLE outbound_approval DROP COLUMN IF EXISTS source_return_id;
-- COMMIT;