diff --git a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts index 1b7eade4..ed973ee1 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts @@ -846,13 +846,13 @@ export function buildWorkOrderWhere({ params.push(normalizedKeyword) const paramIndex = `$${params.length}` filters.push( - `(wo.platform_order_id = ${paramIndex} OR wo.platform_order_id LIKE ${paramIndex} || '%')`, + `(wo.platform_order_id = ${paramIndex} OR wo.platform_order_id LIKE ${paramIndex} || '%' OR wo.work_order_no ILIKE '%' || ${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}))`, + `(wo.work_order_no = ${paramIndex} OR wo.work_order_no ILIKE ${paramIndex} || '%' OR wo.work_order_no ILIKE '%' || ${paramIndex} || '%' OR LOWER(wo.work_order_no) = LOWER(${paramIndex}))`, ) } else { params.push(`%${normalizedKeyword}%`) diff --git a/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts b/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts index 5fabe350..e9ac02a2 100644 --- a/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts +++ b/apps/backend/src/services/worker-platform/consignment-fulfillment-service.ts @@ -250,7 +250,11 @@ export async function listInventoryMatchableOrders( } const { items } = await listWorkOrders({ - statuses: [WORK_ORDER_STATUS.UNASSIGNED, WORK_ORDER_STATUS.PENDING_MATERIAL], + statuses: [ + WORK_ORDER_STATUS.UNASSIGNED, + WORK_ORDER_STATUS.PENDING_MATERIAL, + WORK_ORDER_STATUS.OPEN, + ], keyword: String(input.keyword || '').trim(), pageSize: Math.min(Math.max(Number(input.pageSize || 50), 1), 100), sort: 'id_desc', @@ -358,11 +362,13 @@ export async function fulfillWorkOrderWithConsignment(input: { throw createHttpError('工单不存在', { statusCode: 404, errorCode: 'work_order_not_found' }) } - // 仅未分配或待补资料工单支持选用库存履约 + // 未分配、待补资料或大厅待接单工单支持选用库存履约 if ( - ![WORK_ORDER_STATUS.UNASSIGNED, WORK_ORDER_STATUS.PENDING_MATERIAL].includes( - workOrder.status as any, - ) + ![ + WORK_ORDER_STATUS.UNASSIGNED, + WORK_ORDER_STATUS.PENDING_MATERIAL, + WORK_ORDER_STATUS.OPEN, + ].includes(workOrder.status as any) ) { throw createHttpError('当前工单状态不允许直接使用内部库存履约', { statusCode: 409, diff --git a/apps/frontend/src/components/admin/ConsignmentManualMatchModal.tsx b/apps/frontend/src/components/admin/ConsignmentManualMatchModal.tsx index 5b3fac3c..6a9b615e 100644 --- a/apps/frontend/src/components/admin/ConsignmentManualMatchModal.tsx +++ b/apps/frontend/src/components/admin/ConsignmentManualMatchModal.tsx @@ -141,6 +141,10 @@ export default function ConsignmentManualMatchModal({ item.inventory.system, item.inventory.serverZone, item.inventory.series, + item.inventory.workerName, + item.inventory.buyerName, + item.inventory.sellerGameId, + item.inventory.id, ]), [options, keyword], ) @@ -148,10 +152,12 @@ export default function ConsignmentManualMatchModal({ () => filterManualMatchOptions(orders, keyword, (item) => [ item.workOrderNo, + item.workOrderId, item.productName, item.requiredSpec.system, item.requiredSpec.serverZone, item.requiredSpec.series, + item.status, ]), [orders, keyword], ) @@ -179,8 +185,8 @@ export default function ConsignmentManualMatchModal({ workOrderId: selected.workOrderId, inventoryId: selected.inventoryId, workerId: selected.workerId, - allowPartial: selected.matchType !== 'exact', - allowSpecMismatch: selected.matchType === 'conflict', + allowPartial: selected.matchType !== 'exact' || confirmedPartial, + allowSpecMismatch: selected.matchType === 'conflict' || confirmedMismatch, matchNote: matchNote.trim(), }) @@ -259,8 +265,8 @@ export default function ConsignmentManualMatchModal({ } value={keyword} @@ -296,6 +302,9 @@ export default function ConsignmentManualMatchModal({ > + + {option.inventory.sourceSubmissionNo || option.inventory.inventoryNo} + {option.inventory.itemName} {meta.label} @@ -369,9 +378,16 @@ export default function ConsignmentManualMatchModal({ - #{order.workOrderId} - {order.productName} + {order.workOrderNo || `#${order.workOrderId}`} + {order.productName} {meta.label} + {order.status === 'open' ? ( + 大厅待抢 + ) : order.status === 'unassigned' ? ( + 待指派 + ) : order.status === 'pending_material' ? ( + 待补资料 + ) : null} 要求:{order.requiredSpec.system || '未指定'}/ @@ -398,7 +414,7 @@ export default function ConsignmentManualMatchModal({ onClick={() => { setSelected({ workOrderId: order.workOrderId, - workOrderLabel: `#${order.workOrderId} ${order.productName}`, + workOrderLabel: `${order.workOrderNo || `#${order.workOrderId}`} · ${order.productName}`, inventoryId: inventory?.inventoryId || 0, inventoryLabel, matchType: order.matchType, diff --git a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx index 65eb8eec..23c6d42c 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx @@ -1644,7 +1644,7 @@ export default function WorkOrdersPanel() { onClick={() => setManualMatchOrder(row)} title="从全部在库现货里人工指定一件(支持规格冲突强制匹配,需填原因)" > - 手动匹配现货 + 手动匹配寄售 ) : null} {row.capabilities?.canCancel && (canReturnToHall || canCancelOrder) ? ( diff --git a/apps/frontend/src/utils/consignment-manual-match.ts b/apps/frontend/src/utils/consignment-manual-match.ts index 9b1975b8..3ed89086 100644 --- a/apps/frontend/src/utils/consignment-manual-match.ts +++ b/apps/frontend/src/utils/consignment-manual-match.ts @@ -74,9 +74,9 @@ export function filterManualMatchOptions( /** * 哪些订单状态可以手动指定现货。 * - * 与后端 `fulfillWorkOrderWithConsignment` 的前置校验保持一致(只允许 unassigned / - * pending_material):入口先按同一口径收敛,避免"按钮点了才报错"。 + * 与后端 `fulfillWorkOrderWithConsignment` 的前置校验保持一致(允许 unassigned / + * pending_material / open):入口先按同一口径收敛,避免"按钮点了才报错"。 */ export function isManuallyMatchableOrderStatus(status: string): boolean { - return status === 'unassigned' || status === 'pending_material' + return status === 'unassigned' || status === 'pending_material' || status === 'open' } diff --git a/apps/frontend/tests/consignment-manual-match.test.ts b/apps/frontend/tests/consignment-manual-match.test.ts index da667db4..b40c327c 100644 --- a/apps/frontend/tests/consignment-manual-match.test.ts +++ b/apps/frontend/tests/consignment-manual-match.test.ts @@ -60,11 +60,12 @@ test('关键词过滤:现货能按寄卖单号/物品名搜到,大小写与 assert.equal(filterManualMatchOptions(list, '不存在', pick).length, 0) }) -test('只有待指派/待补资料的订单能手动指定现货(与后端前置校验同口径)', async () => { +test('待指派/待补资料/大厅待抢的订单均能手动指定现货(与后端前置校验同口径)', async () => { const { isManuallyMatchableOrderStatus } = await import('../src/utils/consignment-manual-match.ts') assert.equal(isManuallyMatchableOrderStatus('unassigned'), true) assert.equal(isManuallyMatchableOrderStatus('pending_material'), true) + assert.equal(isManuallyMatchableOrderStatus('open'), true) for (const status of ['in_progress', 'pending_acceptance', 'accepted', 'cancelled', 'disputed']) { assert.equal(isManuallyMatchableOrderStatus(status), false, `${status} 不该给手动匹配入口`) }