优化:寄售改名后实时快照与接单人员筛选保持一致
This commit is contained in:
@@ -3,12 +3,14 @@ import type { Response } from 'express'
|
||||
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
|
||||
|
||||
import {
|
||||
type ConsignmentInventoryRow,
|
||||
getLatestMaterialEventAtByOrderIds,
|
||||
getPendingCancelRequestOrderIds,
|
||||
getPendingFeedbackOrderIds,
|
||||
getWorkerFinanceRequestById,
|
||||
getWorkerUserById,
|
||||
getWorkOrderById,
|
||||
listConsignmentInventoriesByIds,
|
||||
listWorkerTimeoutEventsByOrderIds,
|
||||
listWorkerWorkOrderNotes,
|
||||
listWorkerWorkOrderViews,
|
||||
@@ -23,6 +25,7 @@ import {
|
||||
type RealtimeEventRow,
|
||||
} from '../../repositories/realtime-event-repo.js'
|
||||
import { getAdminDashboardSummary } from '../admin/admin-dashboard-service.js'
|
||||
import { safeParseJson } from '../admin/admin-query-utils.js'
|
||||
import { mapAdminOrderListItem } from '../admin/admin-order-read-helpers.js'
|
||||
import { mapAdminTaskListItem } from '../admin/admin-task-read-helpers.js'
|
||||
import {
|
||||
@@ -298,8 +301,27 @@ async function publishWorkOrderSnapshots(input: {
|
||||
return
|
||||
}
|
||||
|
||||
const pendingUnfreezeByOrderId = await sumPendingUnfreezeByOrderIds([workOrderId])
|
||||
const shares = await listWorkOrderShares(workOrderId)
|
||||
const material = safeParseJson(workOrder.material_json)
|
||||
const consignmentMaterial = material.consignment
|
||||
const consignmentInventoryId =
|
||||
consignmentMaterial &&
|
||||
typeof consignmentMaterial === 'object' &&
|
||||
!Array.isArray(consignmentMaterial)
|
||||
? Math.max(
|
||||
0,
|
||||
Math.floor(Number((consignmentMaterial as Record<string, unknown>).inventoryId) || 0),
|
||||
)
|
||||
: 0
|
||||
const [pendingUnfreezeByOrderId, shares, consignmentInventories] = await Promise.all([
|
||||
sumPendingUnfreezeByOrderIds([workOrderId]),
|
||||
listWorkOrderShares(workOrderId),
|
||||
consignmentInventoryId > 0
|
||||
? listConsignmentInventoriesByIds([consignmentInventoryId])
|
||||
: Promise.resolve([] as ConsignmentInventoryRow[]),
|
||||
])
|
||||
// 寄售工单的快照也要带库存实时供货人:前端会整行替换,
|
||||
// 若只给 material 里的旧快照,改名后的列表会被实时事件刷回旧名。
|
||||
const consignmentInventory = consignmentInventories[0] || null
|
||||
|
||||
// 事件驱动缓存失效:这里已经拿到的工单与拼单快照恰好给出完整的受影响打手集合
|
||||
// (当前指派 + 历史保留 + 全部拼单人 + 调用方显式指定的)。
|
||||
@@ -317,7 +339,7 @@ async function publishWorkOrderSnapshots(input: {
|
||||
.reduce((sum, share) => sum + Number(share.quantity || 0), 0)
|
||||
const pendingSubmissionCount = shares.filter((share) => share.status === 'joined').length
|
||||
const adminSnapshot = {
|
||||
...mapWorkOrderAdmin(workOrder, shares),
|
||||
...mapWorkOrderAdmin(workOrder, shares, consignmentInventory),
|
||||
pendingUnfreezeAmount: pendingUnfreezeByOrderId.get(workOrderId) || 0,
|
||||
sharingProgress: {
|
||||
joinedQuantity,
|
||||
|
||||
@@ -296,6 +296,12 @@ export function mapWorkOrderAdmin(
|
||||
const giftPhase = String(workOrder.gift_phase || '').trim()
|
||||
const resolvedGiftPhase = resolveWorkOrderGiftPhase(workOrder)
|
||||
const internalConsignment = hasInternalConsignmentFulfillment(workOrder)
|
||||
const consignmentMaterial = safeParseJson(
|
||||
safeParseJson(workOrder.material_json).consignment,
|
||||
) as JsonObject
|
||||
const consignmentWorkerIdRaw =
|
||||
consignmentInventory?.worker_id || consignmentMaterial.workerId || 0
|
||||
const consignmentWorkerId = Math.max(0, Math.floor(Number(consignmentWorkerIdRaw) || 0))
|
||||
const hallAvailable =
|
||||
workOrder.status === WORK_ORDER_STATUS.OPEN &&
|
||||
(!workOrder.sharing_enabled || activeShareQuantity < sharingTotalQuantity)
|
||||
@@ -474,6 +480,8 @@ export function mapWorkOrderAdmin(
|
||||
displayName: workOrder.worker_display_name || '',
|
||||
}
|
||||
: null,
|
||||
/** 寄售供货打手:后台「接单人员」筛选也认这个 id(内部寄售直接履约没有 assigned_worker_id)。 */
|
||||
consignmentWorkerId: consignmentWorkerId > 0 ? consignmentWorkerId : null,
|
||||
...(shares ? { sharingWorkers: mapWorkOrderSharingWorkers(shares) } : {}),
|
||||
publishedAt: workOrder.published_at,
|
||||
pinnedAt: workOrder.pinned_at,
|
||||
|
||||
@@ -765,6 +765,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(
|
||||
|
||||
Reference in New Issue
Block a user