fix(worker): 删除依赖残留字段 task_id 的死 JOIN,店铺名兜底改读 material_json
核查结论:work_orders.task_id 是残留字段,不是"数据没同步上来"。
1. 唯一写入点 createWorkOrder 写的是 input.taskId || null,而两个调用方
一个硬编码 taskId: null、一个根本不传;updateWorkOrder 的白名单里也没有它;
git 全历史检索 `taskId:` 只出现过 null 一种取值。
2. 即便强写也无从关联:工单关联 100,231 个订单、履约任务关联 87,603 个订单,
交集为 0;按 order_item_id 比对 100,229 条同样 0 命中。而两者时间范围
(2026-07~10)与 orders.id 区间(1~280k)都重叠 —— 说明是按规则表分流的
两条并行履约通道,一单只走一条。
3. 依赖它的 idx_work_orders_task_unique 为空(8KB)。
改动:
- 三处 `LEFT JOIN fulfillment_tasks ft ON ft.id = wo.task_id`
(取消申请 / 售后案 / 工单反馈)恒为空,删除;
- 店铺名兜底 `ft.shop_name/shop_id` 改为读工单自己的
material_json->'source'->>'shopName'/'shopId';
- 取消申请里的 `ft.platform_order_id` 一并删除(实测 needs_ft_pid_fallback = 0,纯 no-op);
- 迁移 091 给列与索引打上注释,避免后续再被当成数据问题重复排查。
生产逐行回归(旧逻辑 vs 新逻辑取差集):
取消申请 715/715 行、103 条店铺名从空变有值、回归 0;
售后案 21/21 行、1 条、回归 0;工单反馈 348/348 行、15 条、回归 0;
合计 119 条修复、0 条回归、行数与 platform_order_id 零变化。
验证:新增源码级守卫(禁止再 JOIN fulfillment_tasks、兜底必须读 material_json)。
This commit is contained in:
@@ -2620,6 +2620,52 @@ test('筛选统计必须有短缓存 + 并发合并,避免成本随工单量
|
||||
}
|
||||
})
|
||||
|
||||
test('残留字段 work_orders.task_id 不得再被当成关联键 JOIN(迁移 091)', async () => {
|
||||
const { readFile } = await import('node:fs/promises')
|
||||
|
||||
// 背景:task_id 无任何代码写入(git 全历史只出现过 null),且工单与履约任务的订单
|
||||
// 集合零交集(order_id / order_item_id 比对均为 0,2026-10-03 生产核查)。
|
||||
// 三处 LEFT JOIN fulfillment_tasks 永远匹配不上,店铺名兜底已改为读 material_json。
|
||||
for (const name of [
|
||||
'worker-cancel-request-repo.ts',
|
||||
'after-sales-repo.ts',
|
||||
'worker-order-feedback-repo.ts',
|
||||
]) {
|
||||
const source = await readFile(
|
||||
new URL(`../../repositories/worker-platform/${name}`, import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
// 只断言不存在 JOIN(注释里可以解释这段历史),并确认兜底已切到 material_json
|
||||
assert.ok(
|
||||
!/\bJOIN\s+fulfillment_tasks\b/i.test(source),
|
||||
`${name} 不应再 JOIN fulfillment_tasks:wo.task_id 是永不写入的残留字段,该连接恒为空`,
|
||||
)
|
||||
assert.ok(
|
||||
source.includes("material_json->'source'->>'shopName'"),
|
||||
`${name} 的店铺名兜底必须读工单自己的 material_json 快照`,
|
||||
)
|
||||
}
|
||||
|
||||
// 写入点必须保留说明,避免下一个人又把 task_id 当成"数据没同步上来"
|
||||
const managementRepo = await readFile(
|
||||
new URL('../../repositories/worker-platform/work-order-management-repo.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(
|
||||
managementRepo.includes('残留字段'),
|
||||
'createWorkOrder 的 task_id 绑定处必须保留残留字段说明',
|
||||
)
|
||||
|
||||
const migration = await readFile(
|
||||
new URL('../../db/migrations/091_annotate_vestigial_work_order_task_id.sql', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(
|
||||
migration.includes('COMMENT ON COLUMN work_orders.task_id'),
|
||||
'必须给 work_orders.task_id 留下数据库注释',
|
||||
)
|
||||
})
|
||||
|
||||
test('打手端「我的订单」概览必须有覆盖索引 + 短缓存(数据库累计耗时第一的热路径)', async () => {
|
||||
const { readFile } = await import('node:fs/promises')
|
||||
// 覆盖索引:不改 SQL,靠 index-only scan 让规划器放弃全表顺序扫
|
||||
|
||||
Reference in New Issue
Block a user