feat(borrow): 拒收原因独立成列,与转交备注彻底分离
背景
----
拒收原因此前是**拼进 remark** 的:
transfer.remark = f"{remark}\n[拒绝原因] {reason}"
前端拿到的是「3333\n[拒绝原因] 5555」这样一坨,时间线上两句挤在一起,
无法分辨哪句是发起备注、哪句是对方拒收的原因。
改动
----
· trans_borrow_transfer 新增 reject_reason text 列;
reject_transfer 改为写入该列,不再拼进 remark。
· 存量按 '[拒绝原因] ' 标记切分回填(实测仅 #22:
remark 3333 / reject_reason 5555)。
· 时间线事件带出 reject_reason,前端才能分行展示。
★ 为什么拆列而不是让前端解析字符串
1) 拼接格式是隐式契约:改分隔符或加前缀,前端解析就静默失效且难排查;
2) 用户完全可能在备注里自己打出 '[拒绝原因]' 字样,按标记切分必然误判 ——
已加测试用例锁定该场景;
3) 结构化字段才能参与查询与统计(如按拒收原因归类)。
存储层能表达的东西,不该靠字符串约定去还原。
★ 一个迁移期踩到的坑:btrim 默认只去空格、不去换行。
拼接留下的是 '3333\n',只写 btrim(x) 会残留换行;必须显式给出字符集
btrim(x, E' \t\r\n')。已修正脚本并对存量做了一次清理。
验证(7 项断言全通过)
备注不被污染、原因写独立列、无原因时为 None、
用户备注含同名标记也不误判、库存零副作用、数据零残留。
This commit is contained in:
79
db_migrations/phase4f_borrow_transfer_reject_reason.sql
Normal file
79
db_migrations/phase4f_borrow_transfer_reject_reason.sql
Normal file
@ -0,0 +1,79 @@
|
||||
-- =============================================================================
|
||||
-- 借库转交 · 拒收原因独立成列
|
||||
--
|
||||
-- 背景
|
||||
-- 拒收原因此前是**拼进 remark** 的:
|
||||
-- transfer.remark = f"{remark}\n[拒绝原因] {reason}"
|
||||
-- 结果前端拿到的是「3333\n[拒绝原因] 5555」这样一坨,无法区分
|
||||
-- 「发起时写的转交备注」与「对方拒收的原因」—— 时间线上两者挤在一起,
|
||||
-- 毫无辨识度。
|
||||
--
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 为什么改成独立列而不是让前端解析
|
||||
-- 1) 拼接格式是**隐式契约**:一旦以后有人改拼接方式(换分隔符、加前缀),
|
||||
-- 前端解析就静默失效,而且很难排查;
|
||||
-- 2) 备注内容本身可能包含 '[拒绝原因] ' 字样(用户手打),按标记切分会误判;
|
||||
-- 3) 结构化字段能直接参与查询与统计(例如「统计各类拒收原因」)。
|
||||
-- 存储层能表达的东西,不要靠字符串约定去还原。
|
||||
--
|
||||
-- 存量回填
|
||||
-- 按 '[拒绝原因] ' 标记切分现有 remark,把原因挪到新列,并清理 remark。
|
||||
-- 实测仅 1 条(#22),切分结果:remark='3333',reject_reason='5555'。
|
||||
--
|
||||
-- 幂等:带 IF NOT EXISTS 与 NULL 判断,可重复执行。
|
||||
-- 执行:docker exec -i inventory_db psql -U test -d inventory_system < 本文件
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE trans_borrow_transfer
|
||||
ADD COLUMN IF NOT EXISTS reject_reason text;
|
||||
|
||||
COMMENT ON COLUMN trans_borrow_transfer.reject_reason IS
|
||||
'拒收原因,独立成列。此前拼在 remark 里("...\n[拒绝原因] xxx"),前端无法区分转交备注与拒收原因';
|
||||
|
||||
-- ★ btrim 默认只去**空格**,不去换行 —— 拼接时留下的是 '3333\n',
|
||||
-- 只写 btrim(x) 会残留一个换行。必须显式给出要去除的字符集。
|
||||
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;
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 执行后核对
|
||||
-- =============================================================================
|
||||
\echo '--- 1) 新列已就位 ---'
|
||||
SELECT column_name, data_type FROM information_schema.columns
|
||||
WHERE table_name = 'trans_borrow_transfer' AND column_name = 'reject_reason';
|
||||
|
||||
\echo '--- 2) 备注与拒收原因已分离(#22 应为 remark=3333 / reject_reason=5555)---'
|
||||
SELECT id, status, to_user_name,
|
||||
coalesce(replace(remark, E'\n', '\\n'), '<空>') AS 转交备注,
|
||||
coalesce(reject_reason, '<无>') AS 拒收原因
|
||||
FROM trans_borrow_transfer ORDER BY id;
|
||||
|
||||
\echo '--- 3) 不应再有残留的拼接痕迹(应为 0)---'
|
||||
SELECT count(*) AS 残留拼接 FROM trans_borrow_transfer
|
||||
WHERE remark LIKE '%[拒绝原因] %';
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚段
|
||||
-- =============================================================================
|
||||
-- BEGIN;
|
||||
-- UPDATE trans_borrow_transfer
|
||||
-- SET remark = CASE WHEN remark IS NULL THEN '[拒绝原因] ' || reject_reason
|
||||
-- ELSE remark || E'\n[拒绝原因] ' || reject_reason END
|
||||
-- WHERE reject_reason IS NOT NULL;
|
||||
-- ALTER TABLE trans_borrow_transfer DROP COLUMN IF EXISTS reject_reason;
|
||||
-- COMMIT;
|
||||
@ -169,6 +169,11 @@ class TransBorrowTransfer(db.Model):
|
||||
# 责任链出现静默断点。故必须告知,且必须能标记「已告知」,
|
||||
# 否则发起方每次登录都收到同一条提醒,从提醒退化成骚扰。
|
||||
reject_seen_at = db.Column(db.DateTime)
|
||||
# ★ 拒收原因独立成列。此前拼在 remark 里("...\n[拒绝原因] xxx"),
|
||||
# 前端拿到一坨字符串无法区分「转交备注」与「拒收原因」;
|
||||
# 靠字符串约定还原结构化信息既脆弱(用户自己也可能打出该标记),
|
||||
# 又没法参与查询统计。存储层能表达的东西不靠约定去猜。
|
||||
reject_reason = db.Column(db.Text)
|
||||
|
||||
# 转出方(= 转交前的 current_holder)
|
||||
from_user_id = db.Column(db.Integer)
|
||||
@ -198,6 +203,7 @@ class TransBorrowTransfer(db.Model):
|
||||
}.get(self.status, self.status),
|
||||
# 仅供发起方「被拒绝」提醒使用,判断是否需要告知由 reject_seen_at 决定
|
||||
'reject_seen': self.reject_seen_at is not None,
|
||||
'reject_reason': self.reject_reason,
|
||||
'from_user_id': self.from_user_id,
|
||||
'from_user_name': self.from_user_name,
|
||||
'to_user_id': self.to_user_id,
|
||||
|
||||
@ -717,8 +717,11 @@ class TransService:
|
||||
raise ValueError("只有该转交的接收人本人可以拒绝")
|
||||
|
||||
transfer.status = TRANSFER_STATUS_REJECTED
|
||||
if reason:
|
||||
transfer.remark = (f"{transfer.remark}\n" if transfer.remark else "") + f"[拒绝原因] {reason}"
|
||||
# ★ 原因写独立列,不再拼进 remark:
|
||||
# 拼接会让前端拿到「3333\n[拒绝原因] 5555」这样一坨,
|
||||
# 分不清哪句是发起备注、哪句是拒收原因;而且用户自己在备注里
|
||||
# 打出同样字样时,任何按标记切分的解析都会误判。
|
||||
transfer.reject_reason = (reason or '').strip() or None
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
@ -996,6 +999,8 @@ class TransService:
|
||||
# 长得一模一样,发起方翻记录时会以为已经交接出去。
|
||||
'status': t.status,
|
||||
'status_text': t.to_dict().get('status_text'),
|
||||
# 拒收原因独立带出,前端才能与「转交备注」分行展示
|
||||
'reject_reason': t.reject_reason,
|
||||
'_seq': 1,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user