修复押金与待解冻资金处理

This commit is contained in:
yml2213
2026-08-28 22:22:26 +08:00
parent f20ff3018f
commit 04dc363320
16 changed files with 97 additions and 23 deletions
@@ -0,0 +1,9 @@
-- 066_worker_unfreeze_fund_type.sql —— 区分待解冻押金与待解冻报酬,防止后台扣押金误扣报酬。
ALTER TABLE worker_deposit_unfreezes
ADD COLUMN IF NOT EXISTS fund_type TEXT NOT NULL DEFAULT 'legacy_combined';
CREATE INDEX IF NOT EXISTS idx_worker_deposit_unfreezes_order_fund_status
ON worker_deposit_unfreezes(work_order_id, fund_type, status);
COMMENT ON COLUMN worker_deposit_unfreezes.fund_type IS '待解冻资金类型:deposit 押金 / reward 报酬 / legacy_combined 历史合并记录';
@@ -120,6 +120,7 @@ export type WorkerDepositUnfreezeRow = {
worker_id: number worker_id: number
work_order_id: number work_order_id: number
amount: number amount: number
fund_type: 'deposit' | 'reward' | 'legacy_combined' | string
unfreeze_at: string unfreeze_at: string
status: string status: string
released_at: string | null released_at: string | null
@@ -1,7 +1,7 @@
import { query } from '../../db/client.js' import { query } from '../../db/client.js'
import type { WorkerDepositUnfreezeRow } from './types.js' import type { WorkerDepositUnfreezeRow } from './types.js'
/** 汇总仍在等待解冻的押金,供订单详情和实时通知展示。 */ /** 汇总仍在等待解冻的押金,供后台订单扣押金操作展示。 */
export async function sumPendingUnfreezeByOrderIds( export async function sumPendingUnfreezeByOrderIds(
workOrderIds: number[], workOrderIds: number[],
): Promise<Map<number, number>> { ): Promise<Map<number, number>> {
@@ -14,6 +14,7 @@ export async function sumPendingUnfreezeByOrderIds(
FROM worker_deposit_unfreezes FROM worker_deposit_unfreezes
WHERE work_order_id = ANY($1::bigint[]) WHERE work_order_id = ANY($1::bigint[])
AND status = 'pending' AND status = 'pending'
AND fund_type = 'deposit'
GROUP BY work_order_id GROUP BY work_order_id
`, `,
[uniqueIds], [uniqueIds],
@@ -50,6 +50,7 @@ export async function enqueueDepositUnfreezeWithClient(
workerId: number workerId: number
workOrderId: number workOrderId: number
amount: number amount: number
fundType: 'deposit' | 'reward'
unfreezeDays: number unfreezeDays: number
now: string now: string
}, },
@@ -61,10 +62,10 @@ export async function enqueueDepositUnfreezeWithClient(
await client.query( await client.query(
` `
INSERT INTO worker_deposit_unfreezes ( INSERT INTO worker_deposit_unfreezes (
worker_id, work_order_id, amount, unfreeze_at, status, created_at, updated_at worker_id, work_order_id, amount, fund_type, unfreeze_at, status, created_at, updated_at
) VALUES ($1, $2, $3, $4, 'pending', $5, $5) ) VALUES ($1, $2, $3, $4, $5, 'pending', $6, $6)
`, `,
[input.workerId, input.workOrderId, input.amount, unfreezeAt, input.now], [input.workerId, input.workOrderId, input.amount, input.fundType, unfreezeAt, input.now],
) )
} }
@@ -82,7 +83,7 @@ export async function deductPendingDepositUnfreeze(input: {
` `
SELECT * SELECT *
FROM worker_deposit_unfreezes FROM worker_deposit_unfreezes
WHERE work_order_id = $1 AND status = 'pending' WHERE work_order_id = $1 AND status = 'pending' AND fund_type = 'deposit'
ORDER BY unfreeze_at ASC ORDER BY unfreeze_at ASC
FOR UPDATE FOR UPDATE
`, `,
@@ -217,15 +218,17 @@ export async function releaseDepositUnfreeze({
INSERT INTO worker_wallet_ledgers ( INSERT INTO worker_wallet_ledgers (
worker_id, ledger_type, amount, balance_after, frozen_after, worker_id, ledger_type, amount, balance_after, frozen_after,
related_work_order_id, note, payload_json, created_at related_work_order_id, note, payload_json, created_at
) VALUES ($1, 'deposit_unfreeze', $2, $3, $4, $5, '押金已解冻到账', $6::jsonb, $7) ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)
`, `,
[ [
unfreeze.worker_id, unfreeze.worker_id,
unfreeze.fund_type === 'reward' ? 'reward_unfreeze' : 'deposit_unfreeze',
amount, amount,
nextAvailable, nextAvailable,
Number(wallet?.frozen_deposit_amount || 0), Number(wallet?.frozen_deposit_amount || 0),
unfreeze.work_order_id, unfreeze.work_order_id,
JSON.stringify({ unfreezeId: unfreeze.id }), unfreeze.fund_type === 'reward' ? '报酬已解冻到账' : '押金已解冻到账',
JSON.stringify({ unfreezeId: unfreeze.id, fundType: unfreeze.fund_type }),
now, now,
], ],
) )
@@ -120,11 +120,22 @@ export async function acceptWorkOrderAndSettle(input: {
[nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, shareWorkerId], [nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, shareWorkerId],
) )
if (pendingUnfreezeAmount > 0) { if (releaseAmount > 0 && input.unfreezeDays > 0) {
await enqueueDepositUnfreezeWithClient(client, { await enqueueDepositUnfreezeWithClient(client, {
workerId: shareWorkerId, workerId: shareWorkerId,
workOrderId: input.workOrderId, workOrderId: input.workOrderId,
amount: pendingUnfreezeAmount, amount: releaseAmount,
fundType: 'deposit',
unfreezeDays: input.unfreezeDays,
now: input.now,
})
}
if (shareReward > 0 && shouldDelayUnfreeze) {
await enqueueDepositUnfreezeWithClient(client, {
workerId: shareWorkerId,
workOrderId: input.workOrderId,
amount: shareReward,
fundType: 'reward',
unfreezeDays: input.unfreezeDays, unfreezeDays: input.unfreezeDays,
now: input.now, now: input.now,
}) })
@@ -174,7 +185,7 @@ export async function acceptWorkOrderAndSettle(input: {
if (shareReward > 0) { if (shareReward > 0) {
const rewardLedgerType = shouldDelayUnfreeze const rewardLedgerType = shouldDelayUnfreeze
? 'deposit_pending_unfreeze' ? 'reward_pending_unfreeze'
: 'reward_settlement' : 'reward_settlement'
await client.query( await client.query(
` `
@@ -279,11 +290,22 @@ export async function acceptWorkOrderAndSettle(input: {
[nextAvailable, nextFrozen, pendingUnfreezeAmount, rewardAmount, input.now, workerId], [nextAvailable, nextFrozen, pendingUnfreezeAmount, rewardAmount, input.now, workerId],
) )
if (pendingUnfreezeAmount > 0) { if (releaseAmount > 0 && input.unfreezeDays > 0) {
await enqueueDepositUnfreezeWithClient(client, { await enqueueDepositUnfreezeWithClient(client, {
workerId, workerId,
workOrderId: input.workOrderId, workOrderId: input.workOrderId,
amount: pendingUnfreezeAmount, amount: releaseAmount,
fundType: 'deposit',
unfreezeDays: input.unfreezeDays,
now: input.now,
})
}
if (rewardAmount > 0 && shouldDelayUnfreeze) {
await enqueueDepositUnfreezeWithClient(client, {
workerId,
workOrderId: input.workOrderId,
amount: rewardAmount,
fundType: 'reward',
unfreezeDays: input.unfreezeDays, unfreezeDays: input.unfreezeDays,
now: input.now, now: input.now,
}) })
@@ -326,7 +348,7 @@ export async function acceptWorkOrderAndSettle(input: {
if (rewardAmount > 0) { if (rewardAmount > 0) {
const rewardLedgerType = shouldDelayUnfreeze const rewardLedgerType = shouldDelayUnfreeze
? 'deposit_pending_unfreeze' ? 'reward_pending_unfreeze'
: 'reward_settlement' : 'reward_settlement'
await client.query( await client.query(
` `
@@ -467,11 +489,22 @@ export async function acceptWorkOrderShareAndSettle(input: {
[nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, workerId], [nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, workerId],
) )
if (pendingUnfreezeAmount > 0) { if (releaseAmount > 0 && input.unfreezeDays > 0) {
await enqueueDepositUnfreezeWithClient(client, { await enqueueDepositUnfreezeWithClient(client, {
workerId, workerId,
workOrderId: input.workOrderId, workOrderId: input.workOrderId,
amount: pendingUnfreezeAmount, amount: releaseAmount,
fundType: 'deposit',
unfreezeDays: input.unfreezeDays,
now: input.now,
})
}
if (shareReward > 0 && shouldDelayUnfreeze) {
await enqueueDepositUnfreezeWithClient(client, {
workerId,
workOrderId: input.workOrderId,
amount: shareReward,
fundType: 'reward',
unfreezeDays: input.unfreezeDays, unfreezeDays: input.unfreezeDays,
now: input.now, now: input.now,
}) })
@@ -496,7 +529,7 @@ export async function acceptWorkOrderShareAndSettle(input: {
) )
} }
if (shareReward > 0) { if (shareReward > 0) {
const rewardLedgerType = shouldDelayUnfreeze ? 'deposit_pending_unfreeze' : 'sharing_reward' const rewardLedgerType = shouldDelayUnfreeze ? 'reward_pending_unfreeze' : 'sharing_reward'
await client.query( await client.query(
`INSERT INTO worker_wallet_ledgers ( `INSERT INTO worker_wallet_ledgers (
worker_id, ledger_type, amount, balance_after, frozen_after, worker_id, ledger_type, amount, balance_after, frozen_after,
@@ -291,7 +291,7 @@ export async function joinWorkOrderShare(input: {
`, `,
[ [
input.workerId, input.workerId,
depositToFreeze, -depositToFreeze,
nextAvailable, nextAvailable,
nextFrozen, nextFrozen,
input.workOrderId, input.workOrderId,
@@ -72,6 +72,7 @@ function mapDepositUnfreeze(row: WorkerDepositUnfreezeRow) {
workerId: Number(row.worker_id), workerId: Number(row.worker_id),
workOrderId: Number(row.work_order_id), workOrderId: Number(row.work_order_id),
amount: Number(row.amount || 0), amount: Number(row.amount || 0),
fundType: row.fund_type || 'legacy_combined',
status: row.status, status: row.status,
unfreezeAt: row.unfreeze_at, unfreezeAt: row.unfreeze_at,
releasedAt: row.released_at || null, releasedAt: row.released_at || null,
@@ -74,6 +74,7 @@ export function normalizeWalletLedgerType(value: unknown) {
'deposit_freeze', 'deposit_freeze',
'deposit_release', 'deposit_release',
'deposit_pending_unfreeze', 'deposit_pending_unfreeze',
'reward_pending_unfreeze',
'deposit_unfreeze', 'deposit_unfreeze',
'deposit_deduction', 'deposit_deduction',
'reward_settlement', 'reward_settlement',
@@ -1935,7 +1935,7 @@ export default function WorkOrdersPanel() {
type="warning" type="warning"
message={`该工单当前待解冻押金 ${formatMoney( message={`该工单当前待解冻押金 ${formatMoney(
deductOrder.pendingUnfreezeAmount, deductOrder.pendingUnfreezeAmount,
)},扣减后剩余部分仍按原计划解冻。`} )}(不含待解冻报酬),扣减后剩余部分仍按原计划解冻。`}
style={{ marginBottom: 16 }} style={{ marginBottom: 16 }}
/> />
<Form.Item <Form.Item
@@ -28,7 +28,9 @@ const LEDGER_TYPE_LABELS: Record<string, string> = {
deposit_freeze: '冻结押金', deposit_freeze: '冻结押金',
deposit_release: '释放押金', deposit_release: '释放押金',
deposit_pending_unfreeze: '待解冻金额', deposit_pending_unfreeze: '待解冻金额',
reward_pending_unfreeze: '报酬待解冻',
deposit_unfreeze: '押金已解冻', deposit_unfreeze: '押金已解冻',
reward_unfreeze: '报酬已解冻',
deposit_deduction: '扣除押金', deposit_deduction: '扣除押金',
reward_settlement: '结算报酬', reward_settlement: '结算报酬',
sharing_reward: '拼单报酬', sharing_reward: '拼单报酬',
@@ -44,10 +46,15 @@ function formatLedgerType(ledgerType: string) {
function resolveLedgerTagColor(ledgerType: string) { function resolveLedgerTagColor(ledgerType: string) {
if ( if (
['deposit_freeze', 'deposit_pending_unfreeze', 'after_sales_debt_offset'].includes(ledgerType) [
'deposit_freeze',
'deposit_pending_unfreeze',
'reward_pending_unfreeze',
'after_sales_debt_offset',
].includes(ledgerType)
) )
return 'gold' return 'gold'
if (['deposit_release', 'deposit_unfreeze', 'after_sales_refund'].includes(ledgerType)) if (['deposit_release', 'deposit_unfreeze', 'reward_unfreeze', 'after_sales_refund'].includes(ledgerType))
return 'green' return 'green'
if ( if (
[ [
@@ -206,6 +213,18 @@ function UnfreezesSection({ workerId }: { workerId: number }) {
}) })
const data = unfreezesQuery.data?.data const data = unfreezesQuery.data?.data
const columns: TableColumnsType<WorkerDepositUnfreeze> = [ const columns: TableColumnsType<WorkerDepositUnfreeze> = [
{
title: '资金类型',
width: 110,
render: (_, row) => {
const labels: Record<string, string> = {
deposit: '押金',
reward: '报酬',
legacy_combined: '历史合并',
}
return <Tag>{labels[row.fundType] || row.fundType}</Tag>
},
},
{ {
title: '金额', title: '金额',
width: 110, width: 110,
@@ -339,7 +339,7 @@ function WorkerAfterSalesItem({
) : null} ) : null}
{Number(item.pendingDepositDeductionAmount || 0) > 0 ? ( {Number(item.pendingDepositDeductionAmount || 0) > 0 ? (
<Typography.Text type="secondary"> <Typography.Text type="secondary">
{formatMoney(item.pendingDepositDeductionAmount)} {formatMoney(item.pendingDepositDeductionAmount)}
</Typography.Text> </Typography.Text>
) : null} ) : null}
{Number(item.availableDeductionAmount || 0) > 0 ? ( {Number(item.availableDeductionAmount || 0) > 0 ? (
@@ -286,7 +286,7 @@ export function WorkerProfileDesktopAccountDetails({
)} )}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="免押额度"> <Descriptions.Item label="免押额度">
{formatMoney(worker.level?.permissions.depositFreeAmount || 0)} {formatMoney(worker.effectiveDepositFreeAmount)}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="最大同时接单"> <Descriptions.Item label="最大同时接单">
{worker.level?.permissions.maxActiveOrders || 0} {worker.level?.permissions.maxActiveOrders || 0}
@@ -359,7 +359,7 @@ function WorkerProfileMobileAccountDetails({
</Tag>, </Tag>,
], ],
['当前等级', worker.level?.name || '-'], ['当前等级', worker.level?.name || '-'],
['免押额度', formatMoney(worker.level?.permissions.depositFreeAmount || 0)], ['免押额度', formatMoney(worker.effectiveDepositFreeAmount)],
['最大同时接单', `${worker.level?.permissions.maxActiveOrders || 0}`], ['最大同时接单', `${worker.level?.permissions.maxActiveOrders || 0}`],
['邀请人', worker.inviter ? worker.inviter.displayName || worker.inviter.username : '-'], ['邀请人', worker.inviter ? worker.inviter.displayName || worker.inviter.username : '-'],
['已完成订单', `${summary?.acceptedOrderCount || 0}`], ['已完成订单', `${summary?.acceptedOrderCount || 0}`],
@@ -33,6 +33,7 @@ const LEDGER_TYPE_OPTIONS = [
{ value: 'deposit_freeze', label: '冻结押金' }, { value: 'deposit_freeze', label: '冻结押金' },
{ value: 'deposit_release', label: '释放押金' }, { value: 'deposit_release', label: '释放押金' },
{ value: 'deposit_pending_unfreeze', label: '待解冻金额' }, { value: 'deposit_pending_unfreeze', label: '待解冻金额' },
{ value: 'reward_pending_unfreeze', label: '报酬待解冻' },
{ value: 'deposit_unfreeze', label: '押金已解冻' }, { value: 'deposit_unfreeze', label: '押金已解冻' },
{ value: 'deposit_deduction', label: '扣除押金' }, { value: 'deposit_deduction', label: '扣除押金' },
{ value: 'reward_settlement', label: '结算报酬' }, { value: 'reward_settlement', label: '结算报酬' },
@@ -53,7 +53,9 @@ export function formatLedgerType(ledgerType: string) {
deposit_freeze: '冻结押金', deposit_freeze: '冻结押金',
deposit_release: '释放押金', deposit_release: '释放押金',
deposit_pending_unfreeze: '待解冻金额', deposit_pending_unfreeze: '待解冻金额',
reward_pending_unfreeze: '报酬待解冻',
deposit_unfreeze: '押金已解冻', deposit_unfreeze: '押金已解冻',
reward_unfreeze: '报酬已解冻',
deposit_deduction: '扣除押金', deposit_deduction: '扣除押金',
reward_settlement: '结算报酬', reward_settlement: '结算报酬',
sharing_reward: '拼单报酬', sharing_reward: '拼单报酬',
@@ -73,7 +75,9 @@ export function resolveLedgerTagColor(ledgerType: string) {
if (ledgerType === 'deposit_freeze') return 'gold' if (ledgerType === 'deposit_freeze') return 'gold'
if (ledgerType === 'deposit_release') return 'green' if (ledgerType === 'deposit_release') return 'green'
if (ledgerType === 'deposit_pending_unfreeze') return 'orange' if (ledgerType === 'deposit_pending_unfreeze') return 'orange'
if (ledgerType === 'reward_pending_unfreeze') return 'orange'
if (ledgerType === 'deposit_unfreeze') return 'green' if (ledgerType === 'deposit_unfreeze') return 'green'
if (ledgerType === 'reward_unfreeze') return 'green'
if (ledgerType === 'deposit_deduction') return 'red' if (ledgerType === 'deposit_deduction') return 'red'
if (ledgerType === 'reward_settlement') return 'cyan' if (ledgerType === 'reward_settlement') return 'cyan'
if (ledgerType === 'sharing_reward') return 'cyan' if (ledgerType === 'sharing_reward') return 'cyan'
@@ -98,6 +98,7 @@ export type WorkerDepositUnfreeze = {
workerId: number workerId: number
workOrderId: number workOrderId: number
amount: number amount: number
fundType: 'deposit' | 'reward' | 'legacy_combined' | string
status: string status: string
unfreezeAt: string | null unfreezeAt: string | null
releasedAt: string | null releasedAt: string | null