fix(consignment): 拼单份额验收收官补寄卖结算钩子 + 手动匹配弹窗三处复核遗留
- acceptWorkOrderShareAndSettle 最后一个份额把母单转 accepted 的分支, 此前不释放发布押金也不结算供货货款(两钩子只在统一验收里): 现在同口径补上;供货货款传 assignedWorkerId=0(拼单母单无单一接单人, 份额打手已各自按 share_reward 结算,不会重复付款) - 手动匹配弹窗:race_condition/unavailable/spec_mismatch/partial_requires_confirm 四个错误码清空已选并自动重拉列表,数据过期不再死循环撞错 - 手动匹配弹窗库存侧:keyword+pageSize100 透传后端(原来 keyword 是死参数、 只拉 50 条),300ms 防抖后端搜索;无供货打手时禁用指派选项并在提交前 再拦一道(原来必 400 死路);选中订单回显 workerId/workerName - 凭证核销反馈抽共享 utils/voucher-consume,WorkOrdersPanel 与弹窗共用, 口径不再漂移(弹窗顺带获得"已提前核销 N 张"提示) - docs/寄卖系统优化方案.md 记录批次 V 后端 477(+4)/ 前端 50(+1)测试全绿,typecheck / lint / format / check:sql 通过
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
resolveManualMatchRequirement,
|
||||
type ManualMatchType,
|
||||
} from '@/utils/consignment-manual-match'
|
||||
import { notifyVoucherConsume } from '@/utils/voucher-consume'
|
||||
|
||||
const { Text, Paragraph } = Typography
|
||||
|
||||
@@ -74,6 +75,10 @@ export default function ConsignmentManualMatchModal({
|
||||
const [options, setOptions] = useState<ConsignmentMatchOptionView[]>([])
|
||||
const [orders, setOrders] = useState<ConsignmentMatchableOrder[]>([])
|
||||
const [inventoryLabel, setInventoryLabel] = useState('')
|
||||
const [inventoryWorker, setInventoryWorker] = useState<{
|
||||
workerId: number | null
|
||||
workerName: string
|
||||
} | null>(null)
|
||||
const [selected, setSelected] = useState<Selected | null>(null)
|
||||
const [deliveryKind, setDeliveryKind] = useState<'internal_stock' | 'assign_worker'>(
|
||||
'internal_stock',
|
||||
@@ -98,13 +103,22 @@ export default function ConsignmentManualMatchModal({
|
||||
setLoading(true)
|
||||
try {
|
||||
if (inventory) {
|
||||
const res = await fetchInventoryMatchableOrders(inventory.inventoryId)
|
||||
// 关键字与分页透传后端:库存侧默认只拉 50 条时,第 51 单以后永远搜不到
|
||||
const res = await fetchInventoryMatchableOrders(inventory.inventoryId, {
|
||||
keyword: keyword.trim() || undefined,
|
||||
pageSize: 100,
|
||||
})
|
||||
setOrders(res.data?.orders || [])
|
||||
setInventoryLabel(
|
||||
res.data?.inventory
|
||||
? `${res.data.inventory.itemName}(${res.data.inventory.sourceSubmissionNo || res.data.inventory.inventoryNo})`
|
||||
: inventory.label,
|
||||
)
|
||||
// 指派线需要现货的供货打手:库存侧入口从响应里记下来,选中订单时带上
|
||||
setInventoryWorker({
|
||||
workerId: res.data?.inventory?.workerId ?? null,
|
||||
workerName: res.data?.inventory?.workerName || '',
|
||||
})
|
||||
} else if (workOrder) {
|
||||
const res = await fetchWorkOrderConsignmentMatchOptions(workOrder.workOrderId)
|
||||
setOptions(res.data?.options || [])
|
||||
@@ -116,6 +130,16 @@ export default function ConsignmentManualMatchModal({
|
||||
}
|
||||
}
|
||||
|
||||
// 库存侧:关键字防抖透传后端搜索(本地过滤只作用在已拉取的 100 条内)
|
||||
useEffect(() => {
|
||||
if (!open || !inventory) return
|
||||
const timer = setTimeout(() => {
|
||||
void load()
|
||||
}, 300)
|
||||
return () => clearTimeout(timer)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [keyword])
|
||||
|
||||
const filteredOptions = useMemo(
|
||||
() =>
|
||||
filterManualMatchOptions(options, keyword, (item) => [
|
||||
@@ -152,6 +176,10 @@ export default function ConsignmentManualMatchModal({
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!selected) return
|
||||
if (deliveryKind === 'assign_worker' && !selected.workerId) {
|
||||
message.error('该现货未关联供货打手,不能走指派履约;请改用内部现货履约')
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const payload = {
|
||||
@@ -169,15 +197,10 @@ export default function ConsignmentManualMatchModal({
|
||||
})
|
||||
: await fulfillWorkOrderWithConsignment(payload)
|
||||
|
||||
// 核销失败时共享工具会弹警告(状态已变更但凭证没消耗),此时不能再弹"成功"掩盖
|
||||
const voucher = (res.data as any)?.voucherConsume
|
||||
if (voucher && voucher.ok === false) {
|
||||
message.warning({
|
||||
content: `状态已变更,但电子凭证核销失败:${
|
||||
voucher.errorMessage || '未知错误'
|
||||
}。请在快手后台人工核销`,
|
||||
duration: 8,
|
||||
})
|
||||
} else {
|
||||
notifyVoucherConsume(voucher)
|
||||
if (!voucher || voucher.ok) {
|
||||
message.success(
|
||||
deliveryKind === 'assign_worker'
|
||||
? '已手动匹配:指派供货打手履约,押金已冻结'
|
||||
@@ -188,6 +211,27 @@ export default function ConsignmentManualMatchModal({
|
||||
resetState()
|
||||
onClose()
|
||||
} catch (err: any) {
|
||||
// 列表数据过期(并发占用 / 打开弹窗后订单规格被改):刷新列表按最新判定重来,
|
||||
// 否则运营会对着已失效的候选反复重试、循环撞同一个错
|
||||
const code = String(err?.errorCode || '')
|
||||
if (
|
||||
code === 'consignment_inventory_race_condition' ||
|
||||
code === 'consignment_inventory_unavailable'
|
||||
) {
|
||||
message.error('该现货刚被其他操作占用或已下架,已刷新列表,请重新选择')
|
||||
setSelected(null)
|
||||
void load()
|
||||
return
|
||||
}
|
||||
if (
|
||||
code === 'consignment_spec_mismatch' ||
|
||||
code === 'consignment_spec_partial_requires_confirm'
|
||||
) {
|
||||
message.error('订单或现货的规格已变化(列表数据过期),已刷新,请按最新判定重新确认')
|
||||
setSelected(null)
|
||||
void load()
|
||||
return
|
||||
}
|
||||
message.error(err.message || '手动匹配失败')
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
@@ -312,6 +356,10 @@ export default function ConsignmentManualMatchModal({
|
||||
setConfirmedPartial(false)
|
||||
setConfirmedMismatch(false)
|
||||
setMatchNote('')
|
||||
// 选了无打手的现货时指派不可用,兜底切回内部履约
|
||||
if (!option.inventory.workerId && deliveryKind === 'assign_worker') {
|
||||
setDeliveryKind('internal_stock')
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isSelected ? '已选' : '选它'}
|
||||
@@ -377,10 +425,17 @@ export default function ConsignmentManualMatchModal({
|
||||
inventoryLabel,
|
||||
matchType: order.matchType,
|
||||
conflicts: order.conflicts,
|
||||
// 指派线要用现货的供货打手:库存侧入口从接口响应的 inventory 里带过来
|
||||
workerId: inventoryWorker?.workerId ?? null,
|
||||
workerName: inventoryWorker?.workerName || '',
|
||||
})
|
||||
setConfirmedPartial(false)
|
||||
setConfirmedMismatch(false)
|
||||
setMatchNote('')
|
||||
// 选了无打手的现货时指派不可用,兜底切回内部履约
|
||||
if (!inventoryWorker?.workerId && deliveryKind === 'assign_worker') {
|
||||
setDeliveryKind('internal_stock')
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isSelected ? '已选' : '选它'}
|
||||
@@ -421,8 +476,12 @@ export default function ConsignmentManualMatchModal({
|
||||
},
|
||||
{
|
||||
value: 'assign_worker',
|
||||
// 没关联打手的现货走指派必 400 consignment_worker_required,直接禁用并说明,别给死路
|
||||
disabled: !selected.workerId,
|
||||
label: `指派供货打手履约(冻结押金、工单进行中)${
|
||||
selected.workerName ? ` · 打手:${selected.workerName}` : ''
|
||||
selected.workerName
|
||||
? ` · 打手:${selected.workerName}`
|
||||
: ' · 该现货未关联打手,不可指派'
|
||||
}`,
|
||||
},
|
||||
]}
|
||||
|
||||
Reference in New Issue
Block a user