feat(consignment): 确认加好友后现货作废不回池,撤销履约押金可选并结算打手押金

现货口径(严):已确认加好友 = 买家好友已加上、货随时可能送出去,回池会被下一个工单
再次占用(等于二次出货)。收口在 releaseConsignmentInventoryBindingForWorkOrderWithClient:
解绑前读工单 acceptance_mode/friend_added_at,命中则 archived + allow_matching=FALSE + 留痕,
否则仍回池。所有解绑路径自动继承,发单系统接口未动。

撤销寄售履约(寄卖专属入口)支持两笔押金各自可选,默认按规则自动:
- 打手履约押金:auto / refund / forfeit
- 卖家发布押金:auto / keep_frozen / forfeit
策略抽成 domain/consignment-deposit-policy.ts 纯函数;覆盖时原因照旧必填,选项与金额写进
工单事件便于对账。

顺带修掉两个 bug:
1. 撤销寄售履约从不结算打手履约押金——只清 assigned_worker_id、只处理卖家押金,
   worker_deposit 单撤销后打手押金永久冻结。现按选项结算(退还/没收)。
2. detachConsignmentMaterialFromWorkOrderWithClient 里 $1 既作 $1::text(detachedAt)
   又赋值给 updated_at,Postgres 整体推断成 text → 42804。凡解绑现货摘寄售标记的路径
   (取消指派/撤单/退单/售后/撤销履约/人工回库)在真实库上必然 500,假 client 单测抓不到。
   已显式 ::timestamptz,并补类级静态护栏扫同类写法。

验证:真实库(全回滚)——已确认加好友解绑 → archived/allow_matching=false;
未确认 → 仍 in_stock;打手押金冻结 3000 → 退还 3000(重复结算 0)→ 再冻 2500 → 没收 2500
(可用余额不变、冻结归零、流水 deposit_deduction)。后端 432 例(新增 5 例)全绿。
This commit is contained in:
yml2213
2026-09-17 10:21:38 +08:00
parent a47c243421
commit d158cc91bc
12 changed files with 577 additions and 29 deletions
@@ -315,12 +315,18 @@ export default function WorkOrdersPanel() {
order: WorkOrder | null
action: 'reopen' | 'cancel_order' | 'archive_inventory'
reason: string
/** 打手履约押金处置:默认按规则自动,覆盖后必须填原因 */
workerDeposit: 'auto' | 'refund' | 'forfeit'
/** 卖家发布押金处置:默认按规则自动 */
consignmentDeposit: 'auto' | 'keep_frozen' | 'forfeit'
submitting: boolean
}>({
open: false,
order: null,
action: 'reopen',
reason: '',
workerDeposit: 'auto',
consignmentDeposit: 'auto',
submitting: false,
})
@@ -820,6 +826,8 @@ export default function WorkOrdersPanel() {
order: row,
action: 'reopen',
reason: '',
workerDeposit: 'auto',
consignmentDeposit: 'auto',
submitting: false,
})
}
@@ -836,13 +844,17 @@ export default function WorkOrdersPanel() {
workOrderId: revokeConsignmentModal.order.workOrderId,
action: revokeConsignmentModal.action,
reason: revokeConsignmentModal.reason,
workerDeposit: revokeConsignmentModal.workerDeposit,
consignmentDeposit: revokeConsignmentModal.consignmentDeposit,
})
message.success('寄售履约已撤销!物品已退单回库,工单已更新')
message.success('寄售履约已撤销,现货与押金已按所选方式处理')
setRevokeConsignmentModal({
open: false,
order: null,
action: 'reopen',
reason: '',
workerDeposit: 'auto',
consignmentDeposit: 'auto',
submitting: false,
})
await refreshAll()
@@ -3078,6 +3090,8 @@ export default function WorkOrdersPanel() {
order: null,
action: 'reopen',
reason: '',
workerDeposit: 'auto',
consignmentDeposit: 'auto',
submitting: false,
})
}}
@@ -3090,8 +3104,16 @@ export default function WorkOrdersPanel() {
<Alert
showIcon
type="warning"
message="撤销后,关联寄售物品将退单回库;物品的【允许匹配】开关保持原设置不变"
description="只适用于【进行中 / 待验收 / 问题单】。已验收订单无法撤销(现货已交付、押金已结清),买家退款或纠纷请走售后流程。"
message={
revokeConsignmentModal.order?.gift?.friendAddedAt
? '该单已确认加好友:撤销时现货一律作废下架,不会退回在库池'
: '撤销后,关联寄售物品将退单回库;物品的【允许匹配】开关保持原设置不变'
}
description={
revokeConsignmentModal.order?.gift?.friendAddedAt
? '买家好友已经加上,货随时可能送出去,回池会被下一个工单再次占用(等于二次出货)。押金默认按「已确认加好友不退」处理,可在下方按需覆盖。'
: '只适用于【进行中 / 待验收 / 问题单】。已验收订单无法撤销(现货已交付、押金已结清),买家退款或纠纷请走售后流程。'
}
style={{ marginBottom: 16 }}
/>
@@ -3132,6 +3154,59 @@ export default function WorkOrdersPanel() {
</Radio.Group>
</Form.Item>
<Form.Item label="打手履约押金">
<Radio.Group
value={revokeConsignmentModal.workerDeposit}
onChange={(e) =>
setRevokeConsignmentModal((prev) => ({ ...prev, workerDeposit: e.target.value }))
}
>
<Space direction="vertical">
<Radio value="auto">
按规则自动
<span style={{ fontSize: 12, color: '#8c8c8c' }}>
(已确认加好友则扣除,否则全额退还)
</span>
</Radio>
<Radio value="refund">全额退还打手</Radio>
<Radio value="forfeit">全额扣除(押金不退)</Radio>
</Space>
</Radio.Group>
{!revokeConsignmentModal.order?.worker ? (
<div style={{ fontSize: 12, color: '#8c8c8c', marginTop: 4 }}>
内部现货单没有接单打手,无需处理履约押金。
</div>
) : null}
</Form.Item>
<Form.Item label="卖家发布押金">
<Radio.Group
value={revokeConsignmentModal.consignmentDeposit}
onChange={(e) =>
setRevokeConsignmentModal((prev) => ({
...prev,
consignmentDeposit: e.target.value,
}))
}
>
<Space direction="vertical">
<Radio value="auto">
按规则自动
<span style={{ fontSize: 12, color: '#8c8c8c' }}>
(已确认加好友则没收;否则作废退还、回库保持冻结)
</span>
</Radio>
<Radio value="keep_frozen">
保留冻结
<span style={{ fontSize: 12, color: '#8c8c8c' }}>
(不退还也不没收,钱一直冻着,留给后续人工处理)
</span>
</Radio>
<Radio value="forfeit">扣除没收(卖家押金不退)</Radio>
</Space>
</Radio.Group>
</Form.Item>
<Form.Item label="撤单原因说明" required>
<Input.TextArea
rows={3}
@@ -146,6 +146,10 @@ export function revokeWorkOrderConsignmentFulfillment(payload: {
workOrderId: number | string
action?: 'reopen' | 'cancel_order' | 'archive_inventory'
reason?: string
/** 打手履约押金处置:auto 按规则自动 / refund 全额退还 / forfeit 全额扣除 */
workerDeposit?: 'auto' | 'refund' | 'forfeit'
/** 卖家发布押金处置:auto 按规则自动 / keep_frozen 保留冻结 / forfeit 扣除没收 */
consignmentDeposit?: 'auto' | 'keep_frozen' | 'forfeit'
}) {
return apiPost<{ workOrder: unknown; inventory?: ConsignmentInventory | null }>(
'/api/v1/admin/worker-platform/consignment/revoke',