Files
KCGL/db_migrations/deploy_borrow_transfer_all.sql

330 lines
18 KiB
MySQL
Raw Normal View History

chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
-- =============================================================================
-- 借库转交(Borrow Transfer)完整部署脚本
--
-- 本文档把本轮全部库变更按依赖顺序合并为一个脚本,可直接在生产执行。
-- 对应开发环境已执行的 6 个脚本:phase4 / 4b / 4c / 4d / 4e / 4f。
--
-- 执行
-- docker exec -i <db容器> psql -U <用户> -d <库名> -v ON_ERROR_STOP=1 < 本文件
-- 脚本不含 psql 专有元命令,psql / DataGrip / DBeaver 均可直接整份执行。
chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
--
-- 幂等
-- 全部 DDL 带 IF NOT EXISTS,可重复执行。
-- ★ 但三个「回填」段设计为**在新代码上线前执行一次**,见各自注释中的警示。
--
-- 影响面
-- 纯增量:只加列、加表、加索引,不改任何既有列的类型或含义,
-- 不改动任何库存数量字段。全部包在一个事务里,失败自动回滚。
-- =============================================================================
BEGIN;
-- =============================================================================
-- 第 1 段:trans_borrow 补 4 列(身份锚点 + 发货操作人)
--
-- 借用人身份从「姓名」升级为「ID 锚点」:姓名无法唯一锚定一个人(重名即
-- 责任链断裂)。current_holder_* 表示「东西现在在谁手上」,转交会推进它。
--
-- dispatch_operator 补齐「谁经手发货」—— 此前 execute_dispatch 收到该参数
-- 却从未落库。
-- =============================================================================
ALTER TABLE trans_borrow
ADD COLUMN IF NOT EXISTS borrower_id integer,
ADD COLUMN IF NOT EXISTS current_holder_id integer,
ADD COLUMN IF NOT EXISTS current_holder_name varchar(100),
ADD COLUMN IF NOT EXISTS dispatch_operator varchar(100);
COMMENT ON COLUMN trans_borrow.borrower_id IS
'初始借用人ID(sys_user.id)。历史数据回填,无法唯一映射时为 NULL';
COMMENT ON COLUMN trans_borrow.current_holder_id IS
'当前实际持有人ID。转交会推进此列;已全部归还时为 NULL';
COMMENT ON COLUMN trans_borrow.current_holder_name IS
'当前持有人姓名快照。供列表展示,避免逐行回查 sys_user(N+1)';
COMMENT ON COLUMN trans_borrow.dispatch_operator IS
'执行借出(扫码发货)的库管操作人姓名。仅展示/追溯用,不参与权限判定;'
'该列上线前的历史记录为 NULL';
CREATE INDEX IF NOT EXISTS ix_trans_borrow_borrower_id
ON trans_borrow (borrower_id);
CREATE INDEX IF NOT EXISTS ix_trans_borrow_current_holder
ON trans_borrow (current_holder_id);
-- =============================================================================
-- 第 2 段:转交流水表(最终形态一次建全)
--
-- 转交粒度 = **明细行**(borrow_id):物理现场经常只转交部分工具
-- (借 2 件只把 1 件给别人),一张单下不同明细归属不同持有人是正常业务形态。
--
-- 状态机:PENDING ──accept──> ACCEPTED(主表 current_holder 正式转移)
-- └──reject──> REJECTED(主表不动,责任仍在原持有人)
--
-- 不建外键:与 trans_return / trans_defective_goods 一致 —— 库存行会被
-- 入库模块物理删除,台账表必须能独立存活。
-- =============================================================================
CREATE TABLE IF NOT EXISTS trans_borrow_transfer (
id serial PRIMARY KEY,
borrow_id integer NOT NULL, -- 转交目标明细行 trans_borrow.id
from_user_id integer, -- 发起人(= 发起时的当前持有人)
from_user_name varchar(100),
to_user_id integer, -- 接收人
to_user_name varchar(100),
transfer_qty numeric(19,4) NOT NULL DEFAULT 0, -- 该明细行的待还量
transfer_time timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
operator_name varchar(100), -- 发起人(完整 username,存档备查)
remark text, -- 转交备注(发起时填写)
borrow_no varchar(100) -- 单据归属,仅供分组展示
);
-- 补列:兼容本表已存在的场景
ALTER TABLE trans_borrow_transfer
ADD COLUMN IF NOT EXISTS borrow_no varchar(100),
ADD COLUMN IF NOT EXISTS status varchar(20) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS reject_seen_at timestamp without time zone,
ADD COLUMN IF NOT EXISTS reject_reason text;
COMMENT ON COLUMN trans_borrow_transfer.borrow_id IS
'转交目标明细行ID(trans_borrow.id)。转交粒度 = 明细行,一行最多一条待接收流水';
COMMENT ON COLUMN trans_borrow_transfer.borrow_no IS
'借用单号。仅用于单据归属与列表分组展示;覆盖范围是 borrow_id 指向的单个明细行';
COMMENT ON COLUMN trans_borrow_transfer.status IS
'PENDING 待接收 / ACCEPTED 已接收(主表已转移)/ REJECTED 已拒绝(主表未动)';
COMMENT ON COLUMN trans_borrow_transfer.reject_seen_at IS
'发起方看到「被拒绝」提醒并确认的时间。NULL 且 status=REJECTED 表示尚未告知';
COMMENT ON COLUMN trans_borrow_transfer.reject_reason IS
'拒收原因,独立成列(此前拼在 remark 里,前端无法区分转交备注与拒收原因)';
CREATE INDEX IF NOT EXISTS ix_trans_borrow_transfer_borrow ON trans_borrow_transfer (borrow_id);
CREATE INDEX IF NOT EXISTS ix_trans_borrow_transfer_time ON trans_borrow_transfer (transfer_time);
CREATE INDEX IF NOT EXISTS ix_trans_borrow_transfer_no ON trans_borrow_transfer (borrow_no);
CREATE INDEX IF NOT EXISTS ix_trans_borrow_transfer_status ON trans_borrow_transfer (status);
CREATE INDEX IF NOT EXISTS ix_trans_borrow_transfer_to_user ON trans_borrow_transfer (to_user_id, status);
CREATE INDEX IF NOT EXISTS ix_trans_borrow_transfer_borrow_status ON trans_borrow_transfer (borrow_id, status);
CREATE INDEX IF NOT EXISTS ix_trans_borrow_transfer_from_status ON trans_borrow_transfer (from_user_id, status);
-- =============================================================================
-- 第 3 段:归还流水表
--
-- 原先部分归还时,主表的 return_time / return_operator / return_signature
-- 被**逐次覆盖**,「谁在什么时候还了多少」永久丢失。本表按次记录根治。
--
-- 主表字段保持维护:returned_quantity 是累计快照(列表「未还/已还」判定
-- 依赖它),return_time 等降级为「最近一次归还」展示快照;逐次权威以本表为准。
-- =============================================================================
CREATE TABLE IF NOT EXISTS trans_borrow_return (
id serial PRIMARY KEY,
borrow_id integer NOT NULL, -- trans_borrow.id
returner_id integer, -- 实际归还人(写入前已校验 == current_holder)
return_qty numeric(19,4) NOT NULL DEFAULT 0,
return_time timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
operator_name varchar(100) -- 经手办理还库的库管
);
COMMENT ON TABLE trans_borrow_return IS
'借库归还流水:一行=一次归还,替代主表的覆盖式写入,保留分批归还历史';
COMMENT ON COLUMN trans_borrow_return.returner_id IS
'实际归还人ID。写入前已强校验其等于 trans_borrow.current_holder_id';
COMMENT ON COLUMN trans_borrow_return.operator_name IS
'执行还库操作的库管姓名(窗口经手人),与 returner_id 是两个人';
CREATE INDEX IF NOT EXISTS ix_trans_borrow_return_borrow ON trans_borrow_return (borrow_id);
CREATE INDEX IF NOT EXISTS ix_trans_borrow_return_time ON trans_borrow_return (return_time);
CREATE INDEX IF NOT EXISTS ix_trans_borrow_return_returner ON trans_borrow_return (returner_id);
-- =============================================================================
-- 第 4 段:回填 trans_borrow 的身份锚点(存量数据)
--
-- ⚠ 幂等:仅回填仍为 NULL 的行,重跑无副作用。
-- 只回填「姓名在 sys_user 中唯一命中」的行,杜绝重名错绑;命中不了就留 NULL,
-- 不猜测、不阻断 —— 前端对 NULL 有「历史数据」的降级处理。
-- =============================================================================
-- 4.1 borrower_id:全部可唯一映射的行(含已归还,作历史留痕)
UPDATE trans_borrow tb
SET borrower_id = u.id
FROM sys_user u
WHERE tb.borrower_id IS NULL
AND tb.borrower_name IS NOT NULL
AND (split_part(u.username, '/', 1) = tb.borrower_name OR u.username = tb.borrower_name)
AND (SELECT count(*) FROM sys_user u2
WHERE split_part(u2.username, '/', 1) = tb.borrower_name
OR u2.username = tb.borrower_name) = 1;
-- 4.2 current_holder:仅未归还的行(已归还 = 物品已回库,无人持有,保持 NULL)。
-- 这样「current_holder_id IS NOT NULL」本身就是「仍在某人手上」的有效信号,
-- 归还校验不会对已结清单据误触发。
UPDATE trans_borrow tb
SET current_holder_id = u.id,
current_holder_name = split_part(u.username, '/', 1)
FROM sys_user u
WHERE tb.current_holder_id IS NULL
AND tb.is_returned = FALSE
AND tb.borrower_name IS NOT NULL
AND (split_part(u.username, '/', 1) = tb.borrower_name OR u.username = tb.borrower_name)
AND (SELECT count(*) FROM sys_user u2
WHERE split_part(u2.username, '/', 1) = tb.borrower_name
OR u2.username = tb.borrower_name) = 1;
-- =============================================================================
-- 第 5 段:回填转交流水的 borrow_no 与状态(仅针对旧版遗留流水)
--
-- ⚠⚠ 本段设计为**在新代码上线前执行一次**。
-- 旧版实现是「发起即生效」(写流水的同时就改了主表 current_holder),
-- 故这些遗留流水应标为 ACCEPTED;若标 PENDING,接收人会收到一条
-- 早已生效的待办。
--
-- 安全护栏:只处理 borrow_no IS NULL 的行 —— 那是「本列刚加、由旧代码写入」
-- 的唯一特征。新代码写入的流水必然带 borrow_no,因此**重跑不会误伤**
-- 上线后产生的真实待接收记录。
--
-- ★ status 不能一律填 ACCEPTED:旧结构表里 status 是刚加的列、全是默认值
-- PENDING,若把遗留行统统标成 ACCEPTED,后面按 status='REJECTED' 做的
-- 回填就一条都匹配不到(实测踩到)。真正的信号在备注里 ——
-- 旧版拒收会把原因拼成 "[拒绝原因] xxx",据此还原真实状态。
-- =============================================================================
UPDATE trans_borrow_transfer t
SET borrow_no = tb.borrow_no,
status = CASE WHEN t.remark LIKE '%[拒绝原因] %'
THEN 'REJECTED' ELSE 'ACCEPTED' END
FROM trans_borrow tb
WHERE t.borrow_id = tb.id
AND t.borrow_no IS NULL;
-- ★ 兜底:上面按 borrow_id 关联,若某条流水的来源借用行**已被删除**,
-- JOIN 匹配不上,它会永久停在默认的 PENDING —— 在接收人那里变成一条
-- 谁也处理不掉的幽灵待办(点开发现物品早已不存在)。
-- 这里把「没有单号」的遗留行一律结掉;新代码写入的流水必然带 borrow_no,
-- 不会被误伤。
UPDATE trans_borrow_transfer
SET status = CASE WHEN remark LIKE '%[拒绝原因] %'
THEN 'REJECTED' ELSE 'ACCEPTED' END
WHERE borrow_no IS NULL
AND status = 'PENDING';
-- =============================================================================
-- 第 6 段:回填 reject_seen_at(存量已拒绝的流水标记为「已告知」)
--
-- ⚠ 本段同样设计为**上线前执行一次**。
-- 存量拒绝产生于本功能上线之前,追溯提醒只会打扰;新产生的拒绝才会触发提醒。
--
-- ⚠ 若在功能已投产后重跑:会把当时「真实的、尚未告知发起方」的拒绝记录
-- 一并标记为已告知,导致发起方收不到提醒(提醒接口再无第二次机会)。
-- 故请务必在维护窗口、新代码上线前执行。
-- =============================================================================
UPDATE trans_borrow_transfer
SET reject_seen_at = CURRENT_TIMESTAMP
WHERE status = 'REJECTED'
AND reject_seen_at IS NULL;
-- =============================================================================
-- 第 7 段:回填 reject_reason(从 remark 里拆出被拼接的拒收原因)
--
-- 旧实现把原因拼进备注:"3333\n[拒绝原因] 5555",前端无法区分两句话。
-- 按 '[拒绝原因] ' 标记切分:原因挪到新列,备注还原。
--
-- ★ btrim 默认只去**空格**、不去换行 —— 拼接留下的是 '3333\n',
-- 只写 btrim(x) 会残留一个换行,必须显式给出字符集。
-- ⚠ 与第 6 段同理:仅在上线前执行一次。
-- =============================================================================
UPDATE trans_borrow_transfer
SET reject_reason = nullif(btrim(split_part(remark, '[拒绝原因] ', 2), E' \t\r\n'), ''),
remark = nullif(
btrim(replace(remark,
'[拒绝原因] ' || split_part(remark, '[拒绝原因] ', 2),
''),
E' \t\r\n'),
'')
WHERE status = 'REJECTED'
AND remark LIKE '%[拒绝原因] %'
AND reject_reason IS NULL;
COMMIT;
-- =============================================================================
-- 第 8 段(可选,默认不执行):borrow_transfer 权限码
--
-- ⚠ 本轮最终**取消了**该权限码的使用 —— 转交的责任链规则是
-- 「只有该物品的当前持有人本人可以发起」,而持有人是普通员工,
-- 通常不持有库管权限;再加一道库管权限,能发起的人变成「持有人 ∩ 库管」,
-- 绝大多数持有人反而发不了,功能形同虚设。真正的边界是 service 层的
-- caller_user_id 强校验。
--
-- 因此默认不创建,避免在权限管理界面留下一个无代码引用的死权限。
-- 若将来要加「管理员代办」入口,取消下面注释即可(幂等,可重复执行)。
-- -----------------------------------------------------------------------------
-- BEGIN;
-- INSERT INTO sys_element (menu_code, name, code, element_type)
-- SELECT 'op_borrow', '借库转交(库管)', 'borrow_transfer', 'operation'
-- WHERE NOT EXISTS (SELECT 1 FROM sys_element WHERE code = 'borrow_transfer');
--
-- INSERT INTO sys_role_permission (role_code, target_code, type, company_name)
-- SELECT v.role_code, 'borrow_transfer', 'element', NULL
-- FROM (VALUES ('SUPER_ADMIN'), ('SUPERVISOR'), ('WAREHOUSE_MGR'), ('OUTBOUND')) AS v(role_code)
-- WHERE NOT EXISTS (
-- SELECT 1 FROM sys_role_permission r
-- WHERE r.role_code = v.role_code AND r.target_code = 'borrow_transfer' AND r.type = 'element'
-- );
-- COMMIT;
-- =============================================================================
-- 执行后核对
--
-- ★ 这里刻意**不用 psql 的 \echo**(DataGrip / DBeaver 等客户端不认元命令,
-- 会直接报语法错误)。改用 SELECT 输出一行带标签的常量,任何客户端都能跑。
chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
-- =============================================================================
SELECT '=== 1) trans_borrow 四个新列(应 4 行)===' AS "核对项";
chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
SELECT column_name, data_type FROM information_schema.columns
WHERE table_name = 'trans_borrow'
AND column_name IN ('borrower_id','current_holder_id','current_holder_name','dispatch_operator')
ORDER BY column_name;
SELECT '=== 2) 两张新表(应 2 行)===' AS "核对项";
chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
SELECT table_name FROM information_schema.tables
WHERE table_name IN ('trans_borrow_transfer','trans_borrow_return') ORDER BY table_name;
SELECT '=== 3) 索引总数(transfer 应 8 个含主键 / return 应 4 个含主键)===' AS "核对项";
chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
SELECT tablename, count(*) AS 索引数 FROM pg_indexes
WHERE tablename IN ('trans_borrow_transfer','trans_borrow_return')
GROUP BY tablename ORDER BY tablename;
SELECT '=== 4) 身份锚点回填覆盖率 ===' AS "核对项";
chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
SELECT count(*) AS 借用总行数,
count(borrower_id) AS 已锚定借用人,
count(*) FILTER (WHERE is_returned = FALSE) AS 未归还行,
count(current_holder_id) AS 已锚定持有人
FROM trans_borrow;
SELECT '=== 5) 同单多持有人单数(部分转交的正常形态,无需处理)===' AS "核对项";
chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
SELECT count(*) AS 拆分持有单数 FROM (
SELECT borrow_no FROM trans_borrow WHERE is_returned = FALSE
GROUP BY borrow_no HAVING count(DISTINCT current_holder_id) > 1
) t;
SELECT '=== 6) 遗留拼接痕迹(应为 0)===' AS "核对项";
chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
SELECT count(*) AS 残留拼接 FROM trans_borrow_transfer
WHERE remark LIKE '%[拒绝原因] %';
SELECT '=== 7) 待告知发起方的拒绝(存量已标记,应为 0)===' AS "核对项";
chore(db): 借库转交完整部署脚本(生产可执行) 把本轮 6 个迁移(phase4 / 4b / 4c / 4d / 4e / 4f)按依赖顺序合并为一份 可直接在生产执行的脚本,并在一个模拟「部署前状态」的临时库上完整验证。 内容 ---- 1. trans_borrow 补 4 列(borrower_id / current_holder_id / current_holder_name / dispatch_operator)+ 2 索引 2. trans_borrow_transfer 建表(最终形态)+ 7 索引 3. trans_borrow_return 建表 + 3 索引 4. 回填 trans_borrow 身份锚点(仅唯一命中者,重名/无法映射留 NULL) 5. 回填遗留转交流水的 borrow_no 与状态 6. 回填 reject_seen_at(存量拒收标记为已告知) 7. 从 remark 拆出被拼接的 reject_reason 8.(注释掉)borrow_transfer 权限码 —— 已无代码引用,默认不建 + 执行后核对段 + 回滚段 ★ 验证中发现并修掉两个真实缺陷(都不是「看起来能跑」能暴露的) 1) 顺序缺陷:第 5 段原先把遗留流水**一律**标成 ACCEPTED,导致第 6/7 段 按 status='REJECTED' 找行时一条都匹配不到(旧结构表里 status 是刚加的 列、全是默认 PENDING)。真正的信号在备注的 '[拒绝原因]' 标记里, 现据此还原真实状态。 2) 孤儿流水:第 5 段按 borrow_id 关联,来源借用行已被删除的流水匹配不上, 会永久停在 PENDING —— 在接收人那里变成谁也处理不掉的幽灵待办。 已加兜底把「没有单号」的遗留行一律结掉。 ★ 两处 ⚠ 警示已写入脚本:第 5/6/7 段设计为**新代码上线前执行一次**; 若在功能已投产后重跑,会把当时真实的待接收/待告知记录误标。 验证方式:建临时库复刻部署前结构(旧 trans_borrow + 旧结构转交流水表 + 重名/无法映射/已归还/孤儿等边界数据),执行脚本后核对:DDL 与索引齐全、 身份锚点按唯一性正确回填、遗留流水状态从备注还原、原因正确拆出、 孤儿流水被结掉、无报错;再执行第二遍确认幂等(结果完全一致)。
2026-09-17 10:49:34 +08:00
SELECT count(*) AS 待告知 FROM trans_borrow_transfer
WHERE status = 'REJECTED' AND reject_seen_at IS NULL;
-- =============================================================================
-- 回滚段(整段取消注释执行;⚠ 会丢弃转交/归还流水,请先备份)
-- =============================================================================
-- BEGIN;
-- DROP TABLE IF EXISTS trans_borrow_return;
-- DROP TABLE IF EXISTS trans_borrow_transfer;
-- ALTER TABLE trans_borrow
-- DROP COLUMN IF EXISTS borrower_id,
-- DROP COLUMN IF EXISTS current_holder_id,
-- DROP COLUMN IF EXISTS current_holder_name,
-- DROP COLUMN IF EXISTS dispatch_operator;
-- COMMIT;