售后处置为补救完成或问题不成立时自动退还原追缴金额

This commit is contained in:
yml2213
2026-08-22 13:54:58 +08:00
parent 9beb501fa6
commit 6ac77c0124
12 changed files with 170 additions and 10 deletions
@@ -0,0 +1,7 @@
-- 售后处置为补救完成/问题不成立时,退还此前对同一责任单元已追缴的金额。
ALTER TABLE worker_after_sales_cases
ADD COLUMN IF NOT EXISTS refunded_amount INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS refunded_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS refunded_by TEXT NOT NULL DEFAULT '';
COMMENT ON COLUMN worker_after_sales_cases.refunded_amount IS '已退还的追缴金额;补救完成/问题不成立时按原追缴额全额退还';
@@ -271,6 +271,8 @@ export async function resolveAfterSalesCase(input: {
now: string
}): Promise<{
case: WorkerAfterSalesCaseRow | null
refundedRecoveries: Array<{ caseId: number; caseNo: string; amount: number }>
refundedTotalAmount: number
failureReason: 'not_found' | 'not_resolvable' | null
}> {
return withTransaction(async (client) => {
@@ -279,9 +281,20 @@ export async function resolveAfterSalesCase(input: {
[input.caseId],
)
const current = currentResult.rows[0] || null
if (!current) return { case: null, failureReason: 'not_found' }
if (!current)
return {
case: null,
refundedRecoveries: [],
refundedTotalAmount: 0,
failureReason: 'not_found',
}
if (!['awaiting_worker_response', 'under_review'].includes(current.status)) {
return { case: current, failureReason: 'not_resolvable' }
return {
case: current,
refundedRecoveries: [],
refundedTotalAmount: 0,
failureReason: 'not_resolvable',
}
}
let pendingDepositDeductionAmount = 0
@@ -350,7 +363,107 @@ export async function resolveAfterSalesCase(input: {
now: input.now,
})
}
return { case: updated, failureReason: null }
// 补救完成 / 问题不成立意味着责任解除:同一责任单元此前已追缴且未退还的金额应退还打手。
const refundedRecoveries: Array<{ caseId: number; caseNo: string; amount: number }> = []
if (updated && input.action !== 'upheld') {
const priorCases = await client.query<WorkerAfterSalesCaseRow>(
`
SELECT * FROM worker_after_sales_cases
WHERE work_order_id = $1
AND worker_id = $2
AND COALESCE(work_order_share_id, 0) = COALESCE($3, 0)
AND id != $4
AND resolution_action = 'upheld'
AND recovery_amount > 0
AND refunded_amount = 0
ORDER BY id ASC
FOR UPDATE
`,
[
Number(updated.work_order_id),
Number(updated.worker_id),
updated.work_order_share_id ? Number(updated.work_order_share_id) : null,
Number(updated.id),
],
)
for (const priorCase of priorCases.rows) {
const refundAmount = Number(priorCase.recovery_amount || 0)
if (refundAmount <= 0) continue
await client.query(
`
UPDATE worker_after_sales_debts
SET outstanding_amount = 0, status = 'settled', settled_at = $1, updated_at = $1
WHERE case_id = $2 AND status = 'open'
`,
[input.now, Number(priorCase.id)],
)
await ensureWorkerWalletWithClient(client, Number(priorCase.worker_id), input.now)
const wallet = await getWorkerWalletWithClient(client, Number(priorCase.worker_id))
const nextAvailable = Number(wallet?.available_amount || 0) + refundAmount
await client.query(
`UPDATE worker_wallets SET available_amount = $1, updated_at = $2 WHERE worker_id = $3`,
[nextAvailable, input.now, Number(priorCase.worker_id)],
)
await client.query(
`
INSERT INTO worker_wallet_ledgers (
worker_id, ledger_type, amount, balance_after, frozen_after,
related_work_order_id, note, payload_json, created_at
) VALUES ($1, 'after_sales_refund', $2, $3, $4, $5, '售后追缴退还', $6::jsonb, $7)
`,
[
Number(priorCase.worker_id),
refundAmount,
nextAvailable,
Number(wallet?.frozen_deposit_amount || 0),
Number(priorCase.work_order_id),
JSON.stringify({
caseId: Number(priorCase.id),
triggerCaseId: Number(updated.id),
triggerAction: input.action,
}),
input.now,
],
)
await client.query(
`
UPDATE worker_after_sales_cases
SET refunded_amount = $1,
refunded_at = $2,
refunded_by = $3,
debt_amount = 0,
status = CASE WHEN status = 'recovery_pending' THEN 'closed' ELSE status END,
updated_at = $2
WHERE id = $4
`,
[refundAmount, input.now, input.resolvedBy, Number(priorCase.id)],
)
await createAfterSalesCaseEventWithClient(client, {
caseId: Number(priorCase.id),
actorType: 'admin',
actorId: input.resolvedBy,
eventType: 'recovery_refunded',
payloadJson: JSON.stringify({
amount: refundAmount,
triggerCaseId: Number(updated.id),
triggerAction: input.action,
}),
now: input.now,
})
refundedRecoveries.push({
caseId: Number(priorCase.id),
caseNo: String(priorCase.case_no || ''),
amount: refundAmount,
})
}
}
return {
case: updated,
refundedRecoveries,
refundedTotalAmount: refundedRecoveries.reduce((total, item) => total + item.amount, 0),
failureReason: null,
}
})
}
@@ -159,6 +159,9 @@ export type WorkerAfterSalesCaseRow = {
pending_deposit_deduction_amount: number
available_deduction_amount: number
debt_amount: number
refunded_amount?: number
refunded_at?: string | null
refunded_by?: string
created_by: string
resolved_by: string
resolved_at: string | null
@@ -212,6 +212,8 @@ export async function resolveAdminAfterSalesCase(
action,
recoveryAmount: Number(result.case.recovery_amount || 0),
debtAmount: Number(result.case.debt_amount || 0),
refundedTotalAmount: result.refundedTotalAmount,
refundedRecoveries: result.refundedRecoveries,
}),
now: nowIso(),
})
@@ -221,7 +223,11 @@ export async function resolveAdminAfterSalesCase(
workerIds: workerId > 0 ? [workerId] : [],
})
if (workerId > 0) publishWorkerWalletRealtimeChange(workerId)
return { case: mapWorkerAfterSalesCase(result.case) }
return {
case: mapWorkerAfterSalesCase(result.case),
refundedTotalAmount: result.refundedTotalAmount,
refundedRecoveries: result.refundedRecoveries,
}
}
export async function getWorkerAfterSalesCaseSummary(session: WorkerSession) {
@@ -198,6 +198,9 @@ export function mapWorkerAfterSalesCase(afterSalesCase: {
pending_deposit_deduction_amount: number
available_deduction_amount: number
debt_amount: number
refunded_amount?: number
refunded_at?: string | null
refunded_by?: string
created_by: string
resolved_by: string
resolved_at: string | null
@@ -245,6 +248,9 @@ export function mapWorkerAfterSalesCase(afterSalesCase: {
pendingDepositDeductionAmount: Number(afterSalesCase.pending_deposit_deduction_amount || 0),
availableDeductionAmount: Number(afterSalesCase.available_deduction_amount || 0),
debtAmount: Number(afterSalesCase.debt_amount || 0),
refundedAmount: Number(afterSalesCase.refunded_amount || 0),
refundedAt: afterSalesCase.refunded_at || null,
refundedBy: String(afterSalesCase.refunded_by || ''),
createdBy: String(afterSalesCase.created_by || ''),
resolvedBy: String(afterSalesCase.resolved_by || ''),
resolvedAt: afterSalesCase.resolved_at,
@@ -79,6 +79,7 @@ export function normalizeWalletLedgerType(value: unknown) {
'after_sales_deposit_deduction',
'after_sales_balance_deduction',
'after_sales_debt_offset',
'after_sales_refund',
].includes(ledgerType)
) {
return ledgerType