售后处置为补救完成或问题不成立时自动退还原追缴金额
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user