diff --git a/apps/backend/src/db/migrations/066_worker_unfreeze_fund_type.sql b/apps/backend/src/db/migrations/066_worker_unfreeze_fund_type.sql new file mode 100644 index 00000000..881dce4b --- /dev/null +++ b/apps/backend/src/db/migrations/066_worker_unfreeze_fund_type.sql @@ -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 历史合并记录'; diff --git a/apps/backend/src/repositories/worker-platform/types.ts b/apps/backend/src/repositories/worker-platform/types.ts index 2a482967..c15a8ea6 100644 --- a/apps/backend/src/repositories/worker-platform/types.ts +++ b/apps/backend/src/repositories/worker-platform/types.ts @@ -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 diff --git a/apps/backend/src/repositories/worker-platform/work-order-deposit-query-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-deposit-query-repo.ts index ff77182c..218ea6c4 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-deposit-query-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-deposit-query-repo.ts @@ -1,7 +1,7 @@ import { query } from '../../db/client.js' import type { WorkerDepositUnfreezeRow } from './types.js' -/** 汇总仍在等待解冻的押金,供订单详情和实时通知展示。 */ +/** 汇总仍在等待解冻的押金,供后台订单扣押金操作展示。 */ export async function sumPendingUnfreezeByOrderIds( workOrderIds: number[], ): Promise> { @@ -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], diff --git a/apps/backend/src/repositories/worker-platform/work-order-deposit-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-deposit-repo.ts index 4cab964b..79144bb5 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-deposit-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-deposit-repo.ts @@ -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, ], ) diff --git a/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts index 49eefb61..75219e77 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts @@ -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, diff --git a/apps/backend/src/repositories/worker-platform/work-order-share-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-share-repo.ts index 7764a780..0f06d21a 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-share-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-share-repo.ts @@ -291,7 +291,7 @@ export async function joinWorkOrderShare(input: { `, [ input.workerId, - depositToFreeze, + -depositToFreeze, nextAvailable, nextFrozen, input.workOrderId, diff --git a/apps/backend/src/services/worker-platform/admin-worker-detail-service.ts b/apps/backend/src/services/worker-platform/admin-worker-detail-service.ts index 46fb1353..560a21f7 100644 --- a/apps/backend/src/services/worker-platform/admin-worker-detail-service.ts +++ b/apps/backend/src/services/worker-platform/admin-worker-detail-service.ts @@ -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, diff --git a/apps/backend/src/services/worker-platform/worker-platform-normalizers.ts b/apps/backend/src/services/worker-platform/worker-platform-normalizers.ts index b12cad8c..51a16205 100644 --- a/apps/backend/src/services/worker-platform/worker-platform-normalizers.ts +++ b/apps/backend/src/services/worker-platform/worker-platform-normalizers.ts @@ -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', diff --git a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx index 00a53789..d31bd9b9 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx @@ -1935,7 +1935,7 @@ export default function WorkOrdersPanel() { type="warning" message={`该工单当前待解冻押金 ${formatMoney( deductOrder.pendingUnfreezeAmount, - )},扣减后剩余部分仍按原计划解冻。`} + )}(不含待解冻报酬),扣减后剩余部分仍按原计划解冻。`} style={{ marginBottom: 16 }} /> = { 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 = [ + { + title: '资金类型', + width: 110, + render: (_, row) => { + const labels: Record = { + deposit: '押金', + reward: '报酬', + legacy_combined: '历史合并', + } + return {labels[row.fundType] || row.fundType} + }, + }, { title: '金额', width: 110, diff --git a/apps/frontend/src/pages/worker/WorkerAfterSalesPage.tsx b/apps/frontend/src/pages/worker/WorkerAfterSalesPage.tsx index 004dfb60..8f787763 100644 --- a/apps/frontend/src/pages/worker/WorkerAfterSalesPage.tsx +++ b/apps/frontend/src/pages/worker/WorkerAfterSalesPage.tsx @@ -339,7 +339,7 @@ function WorkerAfterSalesItem({ ) : null} {Number(item.pendingDepositDeductionAmount || 0) > 0 ? ( - 押金抵扣 {formatMoney(item.pendingDepositDeductionAmount)} + 待解冻资金抵扣 {formatMoney(item.pendingDepositDeductionAmount)} ) : null} {Number(item.availableDeductionAmount || 0) > 0 ? ( diff --git a/apps/frontend/src/pages/worker/WorkerProfileDesktopSections.tsx b/apps/frontend/src/pages/worker/WorkerProfileDesktopSections.tsx index 8417398b..21404edc 100644 --- a/apps/frontend/src/pages/worker/WorkerProfileDesktopSections.tsx +++ b/apps/frontend/src/pages/worker/WorkerProfileDesktopSections.tsx @@ -286,7 +286,7 @@ export function WorkerProfileDesktopAccountDetails({ )} - {formatMoney(worker.level?.permissions.depositFreeAmount || 0)} + {formatMoney(worker.effectiveDepositFreeAmount)} {worker.level?.permissions.maxActiveOrders || 0} 单 diff --git a/apps/frontend/src/pages/worker/WorkerProfileMobileView.tsx b/apps/frontend/src/pages/worker/WorkerProfileMobileView.tsx index 0b10b17f..97ef9886 100644 --- a/apps/frontend/src/pages/worker/WorkerProfileMobileView.tsx +++ b/apps/frontend/src/pages/worker/WorkerProfileMobileView.tsx @@ -359,7 +359,7 @@ function WorkerProfileMobileAccountDetails({ , ], ['当前等级', 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} 单`], diff --git a/apps/frontend/src/pages/worker/WorkerProfileRecords.tsx b/apps/frontend/src/pages/worker/WorkerProfileRecords.tsx index 284f2f38..4061ef4f 100644 --- a/apps/frontend/src/pages/worker/WorkerProfileRecords.tsx +++ b/apps/frontend/src/pages/worker/WorkerProfileRecords.tsx @@ -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: '结算报酬' }, diff --git a/apps/frontend/src/pages/worker/worker-profile-view-utils.tsx b/apps/frontend/src/pages/worker/worker-profile-view-utils.tsx index bf500eaa..e8864b5a 100644 --- a/apps/frontend/src/pages/worker/worker-profile-view-utils.tsx +++ b/apps/frontend/src/pages/worker/worker-profile-view-utils.tsx @@ -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' diff --git a/apps/frontend/src/types/worker-platform.ts b/apps/frontend/src/types/worker-platform.ts index 8971e76d..d4ffc11d 100644 --- a/apps/frontend/src/types/worker-platform.ts +++ b/apps/frontend/src/types/worker-platform.ts @@ -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