修复押金与待解冻资金处理
This commit is contained in:
@@ -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
|
||||
work_order_id: number
|
||||
amount: number
|
||||
fund_type: 'deposit' | 'reward' | 'legacy_combined' | string
|
||||
unfreeze_at: string
|
||||
status: string
|
||||
released_at: string | null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { query } from '../../db/client.js'
|
||||
import type { WorkerDepositUnfreezeRow } from './types.js'
|
||||
|
||||
/** 汇总仍在等待解冻的押金,供订单详情和实时通知展示。 */
|
||||
/** 汇总仍在等待解冻的押金,供后台订单扣押金操作展示。 */
|
||||
export async function sumPendingUnfreezeByOrderIds(
|
||||
workOrderIds: number[],
|
||||
): Promise<Map<number, number>> {
|
||||
@@ -14,6 +14,7 @@ export async function sumPendingUnfreezeByOrderIds(
|
||||
FROM worker_deposit_unfreezes
|
||||
WHERE work_order_id = ANY($1::bigint[])
|
||||
AND status = 'pending'
|
||||
AND fund_type = 'deposit'
|
||||
GROUP BY work_order_id
|
||||
`,
|
||||
[uniqueIds],
|
||||
|
||||
@@ -50,6 +50,7 @@ export async function enqueueDepositUnfreezeWithClient(
|
||||
workerId: number
|
||||
workOrderId: number
|
||||
amount: number
|
||||
fundType: 'deposit' | 'reward'
|
||||
unfreezeDays: number
|
||||
now: string
|
||||
},
|
||||
@@ -61,10 +62,10 @@ export async function enqueueDepositUnfreezeWithClient(
|
||||
await client.query(
|
||||
`
|
||||
INSERT INTO worker_deposit_unfreezes (
|
||||
worker_id, work_order_id, amount, unfreeze_at, status, created_at, updated_at
|
||||
) VALUES ($1, $2, $3, $4, 'pending', $5, $5)
|
||||
worker_id, work_order_id, amount, fund_type, unfreeze_at, status, created_at, updated_at
|
||||
) 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 *
|
||||
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
|
||||
FOR UPDATE
|
||||
`,
|
||||
@@ -217,15 +218,17 @@ export async function releaseDepositUnfreeze({
|
||||
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, 'deposit_unfreeze', $2, $3, $4, $5, '押金已解冻到账', $6::jsonb, $7)
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)
|
||||
`,
|
||||
[
|
||||
unfreeze.worker_id,
|
||||
unfreeze.fund_type === 'reward' ? 'reward_unfreeze' : 'deposit_unfreeze',
|
||||
amount,
|
||||
nextAvailable,
|
||||
Number(wallet?.frozen_deposit_amount || 0),
|
||||
unfreeze.work_order_id,
|
||||
JSON.stringify({ unfreezeId: unfreeze.id }),
|
||||
unfreeze.fund_type === 'reward' ? '报酬已解冻到账' : '押金已解冻到账',
|
||||
JSON.stringify({ unfreezeId: unfreeze.id, fundType: unfreeze.fund_type }),
|
||||
now,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -120,11 +120,22 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
[nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, shareWorkerId],
|
||||
)
|
||||
|
||||
if (pendingUnfreezeAmount > 0) {
|
||||
if (releaseAmount > 0 && input.unfreezeDays > 0) {
|
||||
await enqueueDepositUnfreezeWithClient(client, {
|
||||
workerId: shareWorkerId,
|
||||
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,
|
||||
now: input.now,
|
||||
})
|
||||
@@ -174,7 +185,7 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
|
||||
if (shareReward > 0) {
|
||||
const rewardLedgerType = shouldDelayUnfreeze
|
||||
? 'deposit_pending_unfreeze'
|
||||
? 'reward_pending_unfreeze'
|
||||
: 'reward_settlement'
|
||||
await client.query(
|
||||
`
|
||||
@@ -279,11 +290,22 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
[nextAvailable, nextFrozen, pendingUnfreezeAmount, rewardAmount, input.now, workerId],
|
||||
)
|
||||
|
||||
if (pendingUnfreezeAmount > 0) {
|
||||
if (releaseAmount > 0 && input.unfreezeDays > 0) {
|
||||
await enqueueDepositUnfreezeWithClient(client, {
|
||||
workerId,
|
||||
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,
|
||||
now: input.now,
|
||||
})
|
||||
@@ -326,7 +348,7 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
|
||||
if (rewardAmount > 0) {
|
||||
const rewardLedgerType = shouldDelayUnfreeze
|
||||
? 'deposit_pending_unfreeze'
|
||||
? 'reward_pending_unfreeze'
|
||||
: 'reward_settlement'
|
||||
await client.query(
|
||||
`
|
||||
@@ -467,11 +489,22 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
[nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, workerId],
|
||||
)
|
||||
|
||||
if (pendingUnfreezeAmount > 0) {
|
||||
if (releaseAmount > 0 && input.unfreezeDays > 0) {
|
||||
await enqueueDepositUnfreezeWithClient(client, {
|
||||
workerId,
|
||||
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,
|
||||
now: input.now,
|
||||
})
|
||||
@@ -496,7 +529,7 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
)
|
||||
}
|
||||
if (shareReward > 0) {
|
||||
const rewardLedgerType = shouldDelayUnfreeze ? 'deposit_pending_unfreeze' : 'sharing_reward'
|
||||
const rewardLedgerType = shouldDelayUnfreeze ? 'reward_pending_unfreeze' : 'sharing_reward'
|
||||
await client.query(
|
||||
`INSERT INTO worker_wallet_ledgers (
|
||||
worker_id, ledger_type, amount, balance_after, frozen_after,
|
||||
|
||||
@@ -291,7 +291,7 @@ export async function joinWorkOrderShare(input: {
|
||||
`,
|
||||
[
|
||||
input.workerId,
|
||||
depositToFreeze,
|
||||
-depositToFreeze,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
|
||||
@@ -72,6 +72,7 @@ function mapDepositUnfreeze(row: WorkerDepositUnfreezeRow) {
|
||||
workerId: Number(row.worker_id),
|
||||
workOrderId: Number(row.work_order_id),
|
||||
amount: Number(row.amount || 0),
|
||||
fundType: row.fund_type || 'legacy_combined',
|
||||
status: row.status,
|
||||
unfreezeAt: row.unfreeze_at,
|
||||
releasedAt: row.released_at || null,
|
||||
|
||||
@@ -74,6 +74,7 @@ export function normalizeWalletLedgerType(value: unknown) {
|
||||
'deposit_freeze',
|
||||
'deposit_release',
|
||||
'deposit_pending_unfreeze',
|
||||
'reward_pending_unfreeze',
|
||||
'deposit_unfreeze',
|
||||
'deposit_deduction',
|
||||
'reward_settlement',
|
||||
|
||||
@@ -1935,7 +1935,7 @@ export default function WorkOrdersPanel() {
|
||||
type="warning"
|
||||
message={`该工单当前待解冻押金 ${formatMoney(
|
||||
deductOrder.pendingUnfreezeAmount,
|
||||
)},扣减后剩余部分仍按原计划解冻。`}
|
||||
)}(不含待解冻报酬),扣减后剩余部分仍按原计划解冻。`}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form.Item
|
||||
|
||||
@@ -28,7 +28,9 @@ const LEDGER_TYPE_LABELS: Record<string, string> = {
|
||||
deposit_freeze: '冻结押金',
|
||||
deposit_release: '释放押金',
|
||||
deposit_pending_unfreeze: '待解冻金额',
|
||||
reward_pending_unfreeze: '报酬待解冻',
|
||||
deposit_unfreeze: '押金已解冻',
|
||||
reward_unfreeze: '报酬已解冻',
|
||||
deposit_deduction: '扣除押金',
|
||||
reward_settlement: '结算报酬',
|
||||
sharing_reward: '拼单报酬',
|
||||
@@ -44,10 +46,15 @@ function formatLedgerType(ledgerType: string) {
|
||||
|
||||
function resolveLedgerTagColor(ledgerType: string) {
|
||||
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'
|
||||
if (['deposit_release', 'deposit_unfreeze', 'after_sales_refund'].includes(ledgerType))
|
||||
if (['deposit_release', 'deposit_unfreeze', 'reward_unfreeze', 'after_sales_refund'].includes(ledgerType))
|
||||
return 'green'
|
||||
if (
|
||||
[
|
||||
@@ -206,6 +213,18 @@ function UnfreezesSection({ workerId }: { workerId: number }) {
|
||||
})
|
||||
const data = unfreezesQuery.data?.data
|
||||
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: '金额',
|
||||
width: 110,
|
||||
|
||||
@@ -339,7 +339,7 @@ function WorkerAfterSalesItem({
|
||||
) : null}
|
||||
{Number(item.pendingDepositDeductionAmount || 0) > 0 ? (
|
||||
<Typography.Text type="secondary">
|
||||
押金抵扣 {formatMoney(item.pendingDepositDeductionAmount)}
|
||||
待解冻资金抵扣 {formatMoney(item.pendingDepositDeductionAmount)}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{Number(item.availableDeductionAmount || 0) > 0 ? (
|
||||
|
||||
@@ -286,7 +286,7 @@ export function WorkerProfileDesktopAccountDetails({
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="免押额度">
|
||||
{formatMoney(worker.level?.permissions.depositFreeAmount || 0)}
|
||||
{formatMoney(worker.effectiveDepositFreeAmount)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最大同时接单">
|
||||
{worker.level?.permissions.maxActiveOrders || 0} 单
|
||||
|
||||
@@ -359,7 +359,7 @@ function WorkerProfileMobileAccountDetails({
|
||||
</Tag>,
|
||||
],
|
||||
['当前等级', worker.level?.name || '-'],
|
||||
['免押额度', formatMoney(worker.level?.permissions.depositFreeAmount || 0)],
|
||||
['免押额度', formatMoney(worker.effectiveDepositFreeAmount)],
|
||||
['最大同时接单', `${worker.level?.permissions.maxActiveOrders || 0} 单`],
|
||||
['邀请人', worker.inviter ? worker.inviter.displayName || worker.inviter.username : '-'],
|
||||
['已完成订单', `${summary?.acceptedOrderCount || 0} 单`],
|
||||
|
||||
@@ -33,6 +33,7 @@ const LEDGER_TYPE_OPTIONS = [
|
||||
{ value: 'deposit_freeze', label: '冻结押金' },
|
||||
{ value: 'deposit_release', label: '释放押金' },
|
||||
{ value: 'deposit_pending_unfreeze', label: '待解冻金额' },
|
||||
{ value: 'reward_pending_unfreeze', label: '报酬待解冻' },
|
||||
{ value: 'deposit_unfreeze', label: '押金已解冻' },
|
||||
{ value: 'deposit_deduction', label: '扣除押金' },
|
||||
{ value: 'reward_settlement', label: '结算报酬' },
|
||||
|
||||
@@ -53,7 +53,9 @@ export function formatLedgerType(ledgerType: string) {
|
||||
deposit_freeze: '冻结押金',
|
||||
deposit_release: '释放押金',
|
||||
deposit_pending_unfreeze: '待解冻金额',
|
||||
reward_pending_unfreeze: '报酬待解冻',
|
||||
deposit_unfreeze: '押金已解冻',
|
||||
reward_unfreeze: '报酬已解冻',
|
||||
deposit_deduction: '扣除押金',
|
||||
reward_settlement: '结算报酬',
|
||||
sharing_reward: '拼单报酬',
|
||||
@@ -73,7 +75,9 @@ export function resolveLedgerTagColor(ledgerType: string) {
|
||||
if (ledgerType === 'deposit_freeze') return 'gold'
|
||||
if (ledgerType === 'deposit_release') return 'green'
|
||||
if (ledgerType === 'deposit_pending_unfreeze') return 'orange'
|
||||
if (ledgerType === 'reward_pending_unfreeze') return 'orange'
|
||||
if (ledgerType === 'deposit_unfreeze') return 'green'
|
||||
if (ledgerType === 'reward_unfreeze') return 'green'
|
||||
if (ledgerType === 'deposit_deduction') return 'red'
|
||||
if (ledgerType === 'reward_settlement') return 'cyan'
|
||||
if (ledgerType === 'sharing_reward') return 'cyan'
|
||||
|
||||
@@ -98,6 +98,7 @@ export type WorkerDepositUnfreeze = {
|
||||
workerId: number
|
||||
workOrderId: number
|
||||
amount: number
|
||||
fundType: 'deposit' | 'reward' | 'legacy_combined' | string
|
||||
status: string
|
||||
unfreezeAt: string | null
|
||||
releasedAt: string | null
|
||||
|
||||
Reference in New Issue
Block a user