fix(worker-platform): 按工单号搜不到彻底收口 —— 显式单号检索不再被任何排他筛选吞掉
## 修的是什么
线上反复复发的「后台接单平台按工单号搜不到、某些状态尤其明显」,根因不是一处,
而是同一类问题在 5 个地方各留了一半(历次修复都只补了一处,所以总复发):
1. 状态筛选走运营态派生:拼单母单一旦有人参与,open → in_progress /
pending_acceptance,于是「单号 + 待接单」永远为空(没人参与时反而搜得到),
这正是「有人接单后就搜不到」;非拼单单子被接单后同理。
2. 「单号 + 接单人员 / 分类 / 打手等级 / 创建时间」照样过滤掉目标单
(接单人员是 INNER JOIN 可见集合,不在集合里整行消失)。
d6523635 只在面板查询按钮里清了 状态/赠送阶段/验收模式,这三个没清。
3. 待办通知跳转(buildNotificationActionPath 带 ?workOrderId=)设下的
「定位工单」是 wo.id = X 排他条件,不手点「取消定位」就一直生效,
之后搜任何别的单号都是空 —— 这条路径历次修复从未涉及。
4. AdminLayout 把服务端 operation='remove' 直接判成 mismatch,
绕过了「显式单号不删行」的护栏。
5. 打手端「我的订单」默认页签固定「代练中」,搜索不联动页签、
实时补丁又拿状态当可证伪条件删行 → 打手端同类问题(此前只修了关键词匹配)。
## 修法(收口到单一真源,不再按按钮打补丁)
后端:
- 新增 isExplicitOrderNoLookupKeyword()(工单号 / 平台单号 / SUB·INV·AS·DT);
- 后台列表筛选抽成纯函数 resolveAdminWorkOrderListFilters():显式单号检索时
不下推 状态/VIP待补图/验收模式/赠送阶段/接单人员/分类/打手等级/创建时间/定位工单;
- 打手端 resolveWorkerMyOrderStatusFilter():只放掉状态与 VIP 待补图,
**严格保留 workerSharingId 可见范围**(否则打手能搜到别人的单)。
前端:
- admin-realtime-work-orders.ts:显式单号检索直接判 unknown(含服务端 remove),
remove 的短路移交给判定函数;
- WorkOrdersPanel:单号检索时清空全部排他筛选并提示「已按单号精准检索,自动忽略…」,
通知定位工单时也清掉其它筛选,避免残留;
- WorkerOrdersPage:单号检索自动切「全部订单」;WorkerLayout 走新的
resolveWorkerMyOrderRealtimeVerdict(),与后台同策。
刻意不改(避免越权/召回不可用单):抢单大厅(候选必须是 open)、
寄卖手动匹配候选(只做未接单整单)。
## 验证
后端 npm run check 全绿:682 pass / 3 skip(新增 6 项);
前端 npm run check 全绿:138 pass(新增 6 项,含「单号检索 + 实时事件不得删行」,
以及「服务端 remove 不得绕过单号护栏」的回归);format/lint/typecheck/build 通过。
无数据库迁移。
This commit is contained in:
@@ -410,6 +410,30 @@ test('模糊关键词不享受单号护栏:状态可证伪时仍按原设计
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 服务端 operation='remove' 曾经直接短路成 mismatch,绕过了上面的单号护栏,
|
||||
* 等于「显式单号检索不删行」这条规则在最需要它的路径上失效。现在交给判定函数统一处理。
|
||||
*/
|
||||
test('服务端 remove 事件不得绕过显式单号护栏;模糊关键词仍可被移除', () => {
|
||||
const explicit = parseAdminWorkOrderListFilters(
|
||||
buildQueryKey({ keyword: 'WO26101213031939', statuses: 'open' }),
|
||||
)!
|
||||
assert.equal(
|
||||
evaluateAdminWorkOrderRealtimeMatch(buildSnapshot({ status: 'accepted' }), explicit, {
|
||||
operation: 'remove',
|
||||
}),
|
||||
'unknown',
|
||||
'按单号搜出来的行,即便服务端发 remove 也不能删(工单并未删除)',
|
||||
)
|
||||
|
||||
const fuzzy = parseAdminWorkOrderListFilters(buildQueryKey({ keyword: '暗潮' }))!
|
||||
assert.equal(
|
||||
evaluateAdminWorkOrderRealtimeMatch(buildSnapshot(), fuzzy, { operation: 'remove' }),
|
||||
'mismatch',
|
||||
'普通关键词仍要保留「服务端说没了就移除」的语义',
|
||||
)
|
||||
})
|
||||
|
||||
test('isExplicitOrderNoSearch 只认单号形态,普通关键词不误判', () => {
|
||||
for (const orderNo of ['WO26100217194691', 'OD26100217', '2627502003065030', 'wo12345678']) {
|
||||
assert.equal(isExplicitOrderNoSearch(orderNo), true, `${orderNo} 应识别为单号`)
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
matchesWorkerWorkOrderKeyword,
|
||||
patchWorkerWorkOrderInfiniteEnvelope,
|
||||
patchWorkerWorkOrderListEnvelope,
|
||||
resolveWorkerMyOrderRealtimeVerdict,
|
||||
} from '../src/utils/worker-realtime-work-orders.ts'
|
||||
|
||||
function buildRow(overrides: Record<string, unknown> = {}) {
|
||||
@@ -116,3 +117,74 @@ test('无限下拉:只有第 1 页允许插入,其余页只就地更新', ()
|
||||
assert.equal(itemsOf(patched.pages[0]).length, 3, '第 1 页应插入')
|
||||
assert.equal(itemsOf(patched.pages[1]).length, 2, '第 2 页不得插入')
|
||||
})
|
||||
|
||||
/**
|
||||
* 「我的订单」状态页签的实时删行判定。
|
||||
*
|
||||
* 生产现象(与后台同源):按工单号搜出来的那一单,状态一流转(代练中 → 待验收 / 已验收)
|
||||
* 就被实时补丁按"状态不匹配"删掉,列表变空、必须刷新整页。
|
||||
* 页签只是展示偏好:后端按单号查时已经忽略状态,前端不能再拿状态删行。
|
||||
*/
|
||||
test('显式单号检索:状态页签不匹配也不得删行(状态流转后仍要能搜到)', () => {
|
||||
const row = buildRow({ status: 'in_progress' })
|
||||
const verdict = resolveWorkerMyOrderRealtimeVerdict({
|
||||
item: buildRow({ status: 'pending_acceptance' }),
|
||||
statusFilter: 'in_progress',
|
||||
keyword: 'WO26093017345168',
|
||||
operation: 'upsert',
|
||||
page: 1,
|
||||
explicitOrderNo: true,
|
||||
})
|
||||
assert.equal(verdict.operation, 'upsert', '单号检索下状态不能证伪')
|
||||
assert.equal(verdict.shouldInsert, true, '命中的单号仍可插入第 1 页')
|
||||
|
||||
const updated = patchWorkerWorkOrderListEnvelope(
|
||||
buildEnvelope([row]),
|
||||
{ operation: verdict.operation, data: buildRow({ status: 'pending_acceptance' }) },
|
||||
verdict.shouldInsert,
|
||||
)
|
||||
assert.equal(itemsOf(updated).length, 1, '行不能被删掉')
|
||||
assert.equal(
|
||||
(itemsOf(updated)[0] as { status?: string }).status,
|
||||
'pending_acceptance',
|
||||
'应就地更新成最新状态',
|
||||
)
|
||||
})
|
||||
|
||||
test('模糊关键词:状态页签不匹配仍按原设计移除行', () => {
|
||||
const verdict = resolveWorkerMyOrderRealtimeVerdict({
|
||||
item: buildRow({ status: 'accepted' }),
|
||||
statusFilter: 'in_progress',
|
||||
keyword: '暗潮',
|
||||
operation: 'upsert',
|
||||
page: 1,
|
||||
explicitOrderNo: false,
|
||||
})
|
||||
assert.equal(verdict.operation, 'remove')
|
||||
assert.equal(verdict.shouldInsert, false)
|
||||
})
|
||||
|
||||
test('服务端 remove(打手已失去该单)仍然生效,单号护栏不误挡', () => {
|
||||
const verdict = resolveWorkerMyOrderRealtimeVerdict({
|
||||
item: buildRow({ status: 'in_progress' }),
|
||||
statusFilter: '',
|
||||
keyword: 'WO26093017345168',
|
||||
operation: 'remove',
|
||||
page: 1,
|
||||
explicitOrderNo: true,
|
||||
})
|
||||
assert.equal(verdict.operation, 'remove', '服务端明确说单子已不可见时必须移除')
|
||||
})
|
||||
|
||||
test('非第 1 页不得插入,只做就地更新', () => {
|
||||
const verdict = resolveWorkerMyOrderRealtimeVerdict({
|
||||
item: buildRow({ workOrderId: 8888 }),
|
||||
statusFilter: '',
|
||||
keyword: 'WO26093017345168',
|
||||
operation: 'upsert',
|
||||
page: 2,
|
||||
explicitOrderNo: true,
|
||||
})
|
||||
assert.equal(verdict.operation, 'upsert')
|
||||
assert.equal(verdict.shouldInsert, false)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user