fix(worker): 售后追缴自动驳回提现并扣冻结资金,修复欠款抵扣 500

- 修复欠款抵扣 SQL 参数类型冲突(debt_amount = $1 与 $1 = 0 推断不一致),
  此前手动偿还、验收报酬抵扣、充值/押金解冻/寄卖自动抵扣都会 500
- 追缴顺序改为 待解冻押金 → 提现冻结 → 可用余额 → 欠款:
  驳回待审核提现并直接扣走冻结资金,扣不完的部分退回可用余额
- 迁移 109:售后单记录 withdraw_deduction_amount,补 pending 提现部分索引
- 新增流水类型 after_sales_withdraw_deduction,前后端展示与标签同步
- 提现驳回退回余额后补调欠款自动抵扣,与充值/结算/解冻/寄卖路径对齐
- 补追缴分配单测与 SQL 参数类型静态门禁
This commit is contained in:
yml2213
2026-10-29 19:05:37 +08:00
parent fba0a13d44
commit c7d650b482
18 changed files with 372 additions and 10 deletions
@@ -371,6 +371,9 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
{item.resolutionAction === 'upheld' ? (
<Typography.Text type={item.debtAmount > 0 ? 'danger' : undefined}>
追缴 {formatMoney(item.recoveryAmount)},待还 {formatMoney(item.debtAmount)}
{Number(item.withdrawDeductionAmount || 0) > 0
? `(含提现冻结 ${formatMoney(item.withdrawDeductionAmount)},提现已驳回)`
: ''}
</Typography.Text>
) : null}
{item.refundedAmount > 0 ? (
@@ -518,7 +521,7 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
label="追缴金额"
name="recoveryAmount"
rules={[{ required: true, message: '请输入追缴金额' }]}
extra="系统依次扣减该责任打手的本单待解冻金额、可用余额;不足部分转为欠款,可从后续验收报酬、充值入账自动抵扣,也可在此手动偿还。"
extra="系统依次扣减该责任打手的本单待解冻金额、提现冻结中的金额(会自动驳回其待审核提现)、可用余额;不足部分转为欠款,可从后续验收报酬、充值入账自动抵扣,也可在此手动偿还。"
>
<InputNumber min={0} precision={2} addonAfter="元" className="full-width" />
</Form.Item>
@@ -36,6 +36,7 @@ const LEDGER_TYPE_LABELS: Record<string, string> = {
reward_settlement: '结算报酬',
sharing_reward: '拼单报酬',
after_sales_deposit_deduction: '售后扣减押金',
after_sales_withdraw_deduction: '售后扣减提现',
after_sales_balance_deduction: '售后扣减余额',
after_sales_debt_offset: '抵扣售后欠款',
after_sales_refund: '售后追缴退还',
@@ -65,6 +66,7 @@ function resolveLedgerTagColor(ledgerType: string) {
[
'deposit_deduction',
'after_sales_deposit_deduction',
'after_sales_withdraw_deduction',
'after_sales_balance_deduction',
].includes(ledgerType)
)
@@ -420,6 +420,7 @@ function WorkerAfterSalesItem({
const hasRecoveryDetail =
Number(item.recoveryAmount || 0) > 0 ||
Number(item.pendingDepositDeductionAmount || 0) > 0 ||
Number(item.withdrawDeductionAmount || 0) > 0 ||
Number(item.availableDeductionAmount || 0) > 0 ||
Number(item.debtAmount || 0) > 0 ||
Number(item.refundedAmount || 0) > 0
@@ -720,6 +721,15 @@ function WorkerAfterSalesItem({
</span>
</span>
)}
{Number(item.withdrawDeductionAmount || 0) > 0 && (
<span className="financial-chip is-available">
<span className="chip-name">提现冻结抵扣</span>
<span className="chip-amount">
{formatMoney(item.withdrawDeductionAmount)}
</span>
<span className="chip-hint">(提现申请已驳回)</span>
</span>
)}
{Number(item.availableDeductionAmount || 0) > 0 && (
<span className="financial-chip is-available">
<span className="chip-name">余额抵扣</span>
@@ -40,6 +40,7 @@ const LEDGER_TYPE_OPTIONS = [
{ value: 'reward_settlement', label: '结算报酬' },
{ value: 'sharing_reward', label: '拼单报酬' },
{ value: 'after_sales_deposit_deduction', label: '售后扣减押金' },
{ value: 'after_sales_withdraw_deduction', label: '售后扣减提现' },
{ value: 'after_sales_balance_deduction', label: '售后扣减余额' },
{ value: 'after_sales_debt_offset', label: '抵扣售后欠款' },
{ value: 'after_sales_refund', label: '售后追缴退还' },
@@ -60,6 +60,7 @@ export function formatLedgerType(ledgerType: string) {
reward_settlement: '结算报酬',
sharing_reward: '拼单报酬',
after_sales_deposit_deduction: '售后扣减押金',
after_sales_withdraw_deduction: '售后扣减提现',
after_sales_balance_deduction: '售后扣减余额',
after_sales_debt_offset: '抵扣售后欠款',
after_sales_refund: '售后追缴退还',
@@ -82,6 +83,7 @@ export function resolveLedgerTagColor(ledgerType: string) {
if (ledgerType === 'reward_settlement') return 'cyan'
if (ledgerType === 'sharing_reward') return 'cyan'
if (ledgerType === 'after_sales_deposit_deduction') return 'red'
if (ledgerType === 'after_sales_withdraw_deduction') return 'red'
if (ledgerType === 'after_sales_balance_deduction') return 'red'
if (ledgerType === 'after_sales_debt_offset') return 'orange'
if (ledgerType === 'after_sales_refund') return 'green'
@@ -247,6 +247,7 @@ export type WorkerAfterSalesCase = {
resolutionNote: string
recoveryAmount: number
pendingDepositDeductionAmount: number
withdrawDeductionAmount: number
availableDeductionAmount: number
debtAmount: number
refundedAmount: number