fix(worker-platform): 优化工单号精准检索与跨状态流转展示,修复状态更新后工单消失问题

1. 后端查询重构:新增 isWorkOrderNoLookupKeyword 工单号前缀与忽略大小写精准索引匹配,优化后端 work-order-query-repo 的工单号检索路径;
2. 前端单号智能穿透:管理员输入 WO... 等精准单号查询时,自动放宽细粒度排他状态/阶段过滤,避免加好友/验收等状态流转后工单因旧状态筛选脱靶消失;
3. 搜索栏交互升级:输入框清空即时重置关键词与页码,支持回车/按钮/清除全方位无缝触发;
4. 详情抽屉与多面板联动:增加 ordersQuery 数据更新对 detailOrder 的自动同步,并将 refreshAll 广播范围覆盖至寄售仓库与统计指标。
This commit is contained in:
yml2213
2026-09-18 19:36:17 +08:00
parent 68cd309bdd
commit d6523635ea
12 changed files with 526 additions and 93 deletions
@@ -41,6 +41,8 @@ export type ConsignmentInventoryRow = {
gift_evidence_json?: string | Record<string, unknown> | null
gift_work_order_no?: string | null
gift_work_order_status?: string | null
gift_phase?: string | null
gift_friend_added_at?: string | null
bound_work_order_material_json?: string | Record<string, unknown> | null
bound_work_order_requirement_json?: string | Record<string, unknown> | null
worker_id?: string | number | null
@@ -197,6 +199,8 @@ export async function listConsignmentInventories(filters: {
wo.acceptance_json AS gift_evidence_json,
wo.work_order_no AS gift_work_order_no,
wo.status AS gift_work_order_status,
wo.gift_phase AS gift_phase,
wo.friend_added_at AS gift_friend_added_at,
wo.material_json AS bound_work_order_material_json,
wo.requirement_json AS bound_work_order_requirement_json
FROM consignment_inventories ci
@@ -314,6 +318,8 @@ export async function getConsignmentInventoryById(
wo.acceptance_json AS gift_evidence_json,
wo.work_order_no AS gift_work_order_no,
wo.status AS gift_work_order_status,
wo.gift_phase AS gift_phase,
wo.friend_added_at AS gift_friend_added_at,
wo.material_json AS bound_work_order_material_json,
wo.requirement_json AS bound_work_order_requirement_json
FROM consignment_inventories ci
@@ -3,9 +3,14 @@ import type { ListInput, WorkOrderRow, WorkOrderStatistics } from './types.js'
import type { PoolClient } from 'pg'
import {
normalizeWorkOrderAcceptanceMode,
WORK_ORDER_ACCEPTANCE_MODE,
WORK_ORDER_GIFT_PHASE,
} from '../../domain/work-order-acceptance-mode.js'
import { isPlatformOrderIdLookupKeyword } from '../../utils/platform-order-id.js'
import {
isPlatformOrderIdLookupKeyword,
isWorkOrderNoLookupKeyword,
} from '../../utils/platform-order-id.js'
export const WORK_ORDER_SELECT = `
SELECT
wo.*,
@@ -843,6 +848,12 @@ export function buildWorkOrderWhere({
filters.push(
`(wo.platform_order_id = ${paramIndex} OR wo.platform_order_id LIKE ${paramIndex} || '%')`,
)
} else if (isWorkOrderNoLookupKeyword(normalizedKeyword)) {
params.push(normalizedKeyword)
const paramIndex = `$${params.length}`
filters.push(
`(wo.work_order_no = ${paramIndex} OR wo.work_order_no ILIKE ${paramIndex} || '%' OR LOWER(wo.work_order_no) = LOWER(${paramIndex}))`,
)
} else {
params.push(`%${normalizedKeyword}%`)
filters.push(`${workOrderSearchExpression()} LIKE LOWER($${params.length})`)
@@ -887,11 +898,37 @@ export function buildWorkOrderWhere({
)
}
if (acceptanceMode) {
params.push(normalizeWorkOrderAcceptanceMode(acceptanceMode))
filters.push(`wo.acceptance_mode = $${params.length}`)
const rawMode = String(acceptanceMode).trim()
if (rawMode === 'internal_consignment') {
filters.push(`(
wo.material_json->'consignment'->>'inventoryId' IS NOT NULL
AND (wo.material_json->'consignment'->>'fulfillmentType' IS NULL OR wo.material_json->'consignment'->>'fulfillmentType' != 'worker_deposit')
AND (wo.material_json->'consignment'->>'workerId' IS NULL OR wo.material_json->'consignment'->>'workerId' = '0')
AND (wo.assigned_worker_id IS NULL OR wo.assigned_worker_id = 0)
)`)
} else if (rawMode === WORK_ORDER_ACCEPTANCE_MODE.FRIEND_GIFT) {
filters.push(`(
wo.acceptance_mode = 'friend_gift'
AND NOT (
wo.material_json->'consignment'->>'inventoryId' IS NOT NULL
AND (wo.material_json->'consignment'->>'fulfillmentType' IS NULL OR wo.material_json->'consignment'->>'fulfillmentType' != 'worker_deposit')
AND (wo.material_json->'consignment'->>'workerId' IS NULL OR wo.material_json->'consignment'->>'workerId' = '0')
AND (wo.assigned_worker_id IS NULL OR wo.assigned_worker_id = 0)
)
)`)
} else if (rawMode === WORK_ORDER_ACCEPTANCE_MODE.STANDARD) {
filters.push(`(
wo.acceptance_mode = 'standard'
AND wo.material_json->'consignment'->>'inventoryId' IS NULL
)`)
} else {
params.push(normalizeWorkOrderAcceptanceMode(acceptanceMode))
filters.push(`wo.acceptance_mode = $${params.length}`)
}
}
if (giftPhase) {
const normalizedGiftPhase = String(giftPhase).trim()
const activeGiftStatusSql = `wo.status NOT IN ('accepted', 'cancelled')`
if (normalizedGiftPhase === WORK_ORDER_GIFT_PHASE.GIFT_READY) {
// gift_ready 是冷却到点后的实时阶段,由时间条件计算而不是落库状态。
filters.push(`(
@@ -899,10 +936,18 @@ export function buildWorkOrderWhere({
AND wo.gift_phase = 'friend_countdown'
AND wo.gift_available_at IS NOT NULL
AND wo.gift_available_at <= NOW()
AND ${activeGiftStatusSql}
)`)
} else if (normalizedGiftPhase === WORK_ORDER_GIFT_PHASE.FRIEND_COUNTDOWN) {
filters.push(`(
wo.acceptance_mode = 'friend_gift'
AND wo.gift_phase = 'friend_countdown'
AND (wo.gift_available_at IS NULL OR wo.gift_available_at > NOW())
AND ${activeGiftStatusSql}
)`)
} else if (Object.values(WORK_ORDER_GIFT_PHASE).includes(normalizedGiftPhase as never)) {
params.push(normalizedGiftPhase)
const giftPhaseFilter = `wo.acceptance_mode = 'friend_gift' AND wo.gift_phase = $${params.length}`
const giftPhaseFilter = `wo.acceptance_mode = 'friend_gift' AND wo.gift_phase = $${params.length} AND ${activeGiftStatusSql}`
// 待打手提交资料仅代表已接单后的赠送流程;待抢单/未分配模板单不应混入该筛选。
filters.push(
normalizedGiftPhase === WORK_ORDER_GIFT_PHASE.MATERIAL_REQUIRED
@@ -110,6 +110,8 @@ function mapConsignmentEvidence(source: {
gift_evidence_json?: string | Record<string, unknown> | null
gift_work_order_no?: string | null
gift_work_order_status?: string | null
gift_phase?: string | null
gift_friend_added_at?: string | null
}) {
const acceptance = toRecord(source.gift_evidence_json)
const files = Array.isArray(acceptance?.files) ? (acceptance.files as unknown[]) : []
@@ -146,6 +148,8 @@ function mapConsignmentEvidence(source: {
giftEvidenceSubmittedAt: String(acceptance?.submittedAt || ''),
giftWorkOrderNo: source.gift_work_order_no || '',
giftWorkOrderStatus: workOrderStatus,
giftPhase: source.gift_phase || '',
giftFriendAddedAt: source.gift_friend_added_at || '',
/** none=还没交 / pending=交了待客服验收 / accepted=客服已验收 / submitted=其他在途状态 */
giftEvidenceReviewStatus: reviewStatus,
}
@@ -1635,6 +1635,25 @@ test('好友赠送待打手提交资料筛选排除待抢单订单', () => {
assert.match(whereClause, /wo\.status IN \('in_progress', 'problem'\)/)
})
test('工单筛选支持内部寄售匹配与打手好友赠送隔离,且赠送冷却中排除已验收已取消工单', () => {
const internalQuery = buildWorkOrderWhere({ acceptanceMode: 'internal_consignment' })
assert.match(internalQuery.whereClause, /wo\.material_json->'consignment'->>'inventoryId' IS NOT NULL/)
assert.match(internalQuery.whereClause, /wo\.assigned_worker_id IS NULL OR wo\.assigned_worker_id = 0/)
const friendGiftQuery = buildWorkOrderWhere({ acceptanceMode: 'friend_gift' })
assert.match(friendGiftQuery.whereClause, /wo\.acceptance_mode = 'friend_gift'/)
assert.match(friendGiftQuery.whereClause, /NOT \(/)
const countdownQuery = buildWorkOrderWhere({ giftPhase: 'friend_countdown' })
assert.match(countdownQuery.whereClause, /wo\.gift_phase = 'friend_countdown'/)
assert.match(countdownQuery.whereClause, /wo\.gift_available_at > NOW\(\)/)
assert.match(countdownQuery.whereClause, /wo\.status NOT IN \('accepted', 'cancelled'\)/)
const readyQuery = buildWorkOrderWhere({ giftPhase: 'gift_ready' })
assert.match(readyQuery.whereClause, /wo\.gift_available_at <= NOW\(\)/)
assert.match(readyQuery.whereClause, /wo\.status NOT IN \('accepted', 'cancelled'\)/)
})
test('好友赠送冷却中才允许客服手动结束冷却', () => {
const cooling = mapWorkOrderAdmin(
buildWorkOrderRow({
@@ -1,7 +1,10 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { isPlatformOrderIdLookupKeyword } from './platform-order-id.js'
import {
isPlatformOrderIdLookupKeyword,
isWorkOrderNoLookupKeyword,
} from './platform-order-id.js'
test('识别纯数字平台订单号', () => {
assert.equal(isPlatformOrderIdLookupKeyword('20247230034021572'), true)
@@ -14,9 +17,19 @@ test('识别拼多多带连字符的 order_sn', () => {
assert.equal(isPlatformOrderIdLookupKeyword('260912-123456789012345'), true)
})
test('识别统一内部工单号 (WO...)', () => {
assert.equal(isWorkOrderNoLookupKeyword('WOb320ee0b39b9'), true)
assert.equal(isWorkOrderNoLookupKeyword('wob320ee0b39b9'), true)
assert.equal(isWorkOrderNoLookupKeyword('WO26091216358821'), true)
assert.equal(isWorkOrderNoLookupKeyword('WO12'), false)
assert.equal(isWorkOrderNoLookupKeyword('20247230034021572'), false)
})
test('拒绝普通关键词', () => {
assert.equal(isPlatformOrderIdLookupKeyword('皮肤店'), false)
assert.equal(isPlatformOrderIdLookupKeyword('安卓怒嚎3钻'), false)
assert.equal(isPlatformOrderIdLookupKeyword('DT881aa66294cc'), false)
assert.equal(isPlatformOrderIdLookupKeyword(''), false)
assert.equal(isWorkOrderNoLookupKeyword('皮肤店'), false)
})
@@ -6,3 +6,12 @@ export function isPlatformOrderIdLookupKeyword(value: unknown): boolean {
const trimmed = String(value || '').trim()
return /^\d{6,}(-\d{6,})?$/.test(trimmed)
}
/**
* 识别内部统一工单号 (以 WO 开头且至少 4 位字符,如 WOb320ee0b39b9、WO26091216358821)。
*/
export function isWorkOrderNoLookupKeyword(value: unknown): boolean {
const trimmed = String(value || '').trim()
return /^WO[0-9a-zA-Z]{4,}$/i.test(trimmed)
}