merge: 合并 main 到 dev(含订单来源快照与寄售改名同步)

# Conflicts:
#	README.md
#	apps/backend/src/db/migrate.ts
This commit is contained in:
2026-11-12 15:35:42 +08:00
38 changed files with 860 additions and 289 deletions
@@ -782,6 +782,8 @@ export type WorkOrder = {
username: string
displayName: string
}
/** 寄售供货打手 id:后台「接单人员」筛选同时认这个 id。 */
consignmentWorkerId?: number | null
/** 拼单订单的当前参与打手,普通整单不返回该字段。 */
sharingWorkers?: Array<{
workerId: number
@@ -155,6 +155,18 @@ export function isExplicitOrderNoSearch(text: unknown): boolean {
)
}
/**
* 快照里是否带寄售履约标记。
*
* 寄售单的供货打手不在 worker / sharingWorkers 上,而在库存行上;
* 旧快照没有 consignmentWorkerId 时无法判断它是否命中「接单人员」筛选。
*/
function hasConsignmentMaterial(snapshot: Record<string, unknown>): boolean {
const material = isRecord(snapshot.material) ? snapshot.material : null
const consignment = material && isRecord(material.consignment) ? material.consignment : null
return Boolean(consignment && (consignment.inventoryId || consignment.inventoryNo))
}
export function evaluateAdminWorkOrderRealtimeMatch(
snapshot: unknown,
filters: AdminWorkOrderListFilters,
@@ -190,7 +202,16 @@ export function evaluateAdminWorkOrderRealtimeMatch(
toNumber((worker as { workerId?: unknown })?.workerId),
)
: []
if (assignedWorkerId !== filters.workerId && !sharingWorkerIds.includes(filters.workerId)) {
// 后台「接单人员」筛选同时认寄售供货人;实时快照带 consignmentWorkerId。
const consignmentWorkerId = toNumber(snapshot.consignmentWorkerId)
const matchesTargetWorker =
assignedWorkerId === filters.workerId ||
sharingWorkerIds.includes(filters.workerId) ||
consignmentWorkerId === filters.workerId
if (!matchesTargetWorker) {
// 寄售单的供货人只存在库存行上:旧快照 / 未加载库存时无法证伪,
// 直接删行会把「接单人员 + 内部寄售匹配」列表里的寄售单清空。
if (consignmentWorkerId <= 0 && hasConsignmentMaterial(snapshot)) return 'unknown'
return 'mismatch'
}
}
@@ -332,11 +332,27 @@ test('打手 / 分类 / 工单号 / 创建日期筛选的证伪能力', () => {
}),
)!
// 指派给别的打手 → mismatch
// 指派给别的打手 → mismatch(consignmentWorkerId 明确指向别人,才能证伪)
assert.equal(
evaluateAdminWorkOrderRealtimeMatch(buildSnapshot({ worker: { workerId: 8 } }), filters),
evaluateAdminWorkOrderRealtimeMatch(
buildSnapshot({ worker: { workerId: 8 }, consignmentWorkerId: 8 }),
filters,
),
'mismatch',
)
// 寄售供货人是目标打手 → 命中(内部寄售直接履约没有 assigned_worker_id)
assert.equal(
evaluateAdminWorkOrderRealtimeMatch(
buildSnapshot({ consignmentWorkerId: 7, categoryId: 12 }),
filters,
),
'match',
)
// 旧快照没有 consignmentWorkerId、但有寄售标记 → 无法证伪,只能原地更新,不能删行
assert.equal(
evaluateAdminWorkOrderRealtimeMatch(buildSnapshot({ categoryId: 12 }), filters),
'unknown',
)
// 拼单参与者是目标打手 → 命中(后端 workerId 筛选也认拼单参与者)
assert.equal(
evaluateAdminWorkOrderRealtimeMatch(