新增验收报酬待解冻配置并修复待解冻结算漏入队与重复释放流水

This commit is contained in:
yml2213
2026-08-22 13:38:17 +08:00
parent 7b2a5321b9
commit 7f6a291628
17 changed files with 173 additions and 115 deletions
@@ -0,0 +1,3 @@
-- 验收报酬默认进入待解冻,售后问题单成立前不能直接提现。
COMMENT ON COLUMN worker_wallets.pending_unfreeze_amount IS '待解冻金额:验收报酬与押金按配置天数解冻到账';
COMMENT ON TABLE worker_deposit_unfreezes IS '待解冻计划:验收报酬与押金到期自动转入可用余额';
@@ -18,6 +18,7 @@ import type { WorkOrderRow, WorkOrderShareRow } from './types.js'
export async function acceptWorkOrderAndSettle(input: { export async function acceptWorkOrderAndSettle(input: {
workOrderId: number workOrderId: number
unfreezeDays: number unfreezeDays: number
rewardUnfreezeEnabled: boolean
now: string now: string
/** 操作人类型:admin / worker / system */ /** 操作人类型:admin / worker / system */
actorType?: string actorType?: string
@@ -94,11 +95,13 @@ export async function acceptWorkOrderAndSettle(input: {
now: input.now, now: input.now,
}) })
const debtOffsetAmount = debtOffsets.reduce((total, item) => total + item.amount, 0) const debtOffsetAmount = debtOffsets.reduce((total, item) => total + item.amount, 0)
const shouldDelayUnfreeze = input.unfreezeDays > 0 const shouldDelayUnfreeze = input.rewardUnfreezeEnabled && input.unfreezeDays > 0
const pendingUnfreezeAmount =
(shouldDelayUnfreeze ? shareReward : 0) + (input.unfreezeDays > 0 ? releaseAmount : 0)
const nextAvailable = const nextAvailable =
Number(wallet?.available_amount || 0) + Number(wallet?.available_amount || 0) +
shareReward + (shouldDelayUnfreeze ? 0 : shareReward) +
(shouldDelayUnfreeze ? 0 : releaseAmount) - (input.unfreezeDays > 0 ? 0 : releaseAmount) -
debtOffsetAmount debtOffsetAmount
const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount) const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount)
@@ -113,25 +116,19 @@ export async function acceptWorkOrderAndSettle(input: {
updated_at = $5 updated_at = $5
WHERE worker_id = $6 WHERE worker_id = $6
`, `,
[ [nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, shareWorkerId],
nextAvailable,
nextFrozen,
shouldDelayUnfreeze ? releaseAmount : 0,
shareReward,
input.now,
shareWorkerId,
],
) )
if (releaseAmount > 0) { if (pendingUnfreezeAmount > 0) {
if (shouldDelayUnfreeze) {
await enqueueDepositUnfreezeWithClient(client, { await enqueueDepositUnfreezeWithClient(client, {
workerId: shareWorkerId, workerId: shareWorkerId,
workOrderId: input.workOrderId, workOrderId: input.workOrderId,
amount: releaseAmount, amount: pendingUnfreezeAmount,
unfreezeDays: input.unfreezeDays, unfreezeDays: input.unfreezeDays,
now: input.now, now: input.now,
}) })
}
if (releaseAmount > 0 && input.unfreezeDays > 0) {
await client.query( await client.query(
` `
INSERT INTO worker_wallet_ledgers ( INSERT INTO worker_wallet_ledgers (
@@ -153,7 +150,8 @@ export async function acceptWorkOrderAndSettle(input: {
input.now, input.now,
], ],
) )
} else { }
if (releaseAmount > 0 && input.unfreezeDays === 0) {
await client.query( await client.query(
` `
INSERT INTO worker_wallet_ledgers ( INSERT INTO worker_wallet_ledgers (
@@ -172,27 +170,33 @@ export async function acceptWorkOrderAndSettle(input: {
], ],
) )
} }
}
if (shareReward > 0) { if (shareReward > 0) {
const rewardLedgerType = shouldDelayUnfreeze
? 'deposit_pending_unfreeze'
: 'reward_settlement'
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,
related_work_order_id, note, payload_json, created_at related_work_order_id, note, payload_json, created_at
) VALUES ($1, 'sharing_reward', $2, $3, $4, $5, $6, $7::jsonb, $8) ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)
`, `,
[ [
shareWorkerId, shareWorkerId,
rewardLedgerType,
shareReward, shareReward,
nextAvailable, nextAvailable,
nextFrozen, nextFrozen,
input.workOrderId, input.workOrderId,
`拼单结算报酬 ${share.quantity}`, shouldDelayUnfreeze
? '拼单份额验收报酬待解冻'
: `拼单份额验收结算报酬 ${share.quantity}`,
JSON.stringify({ JSON.stringify({
workOrderId: input.workOrderId, workOrderId: input.workOrderId,
shareId: Number(share.id), shareId: Number(share.id),
quantity: share.quantity, quantity: share.quantity,
...(shouldDelayUnfreeze ? { unfreezeDays: input.unfreezeDays } : {}),
}), }),
input.now, input.now,
], ],
@@ -250,11 +254,13 @@ export async function acceptWorkOrderAndSettle(input: {
now: input.now, now: input.now,
}) })
const debtOffsetAmount = debtOffsets.reduce((total, item) => total + item.amount, 0) const debtOffsetAmount = debtOffsets.reduce((total, item) => total + item.amount, 0)
const shouldDelayUnfreeze = input.unfreezeDays > 0 const shouldDelayUnfreeze = input.rewardUnfreezeEnabled && input.unfreezeDays > 0
const pendingUnfreezeAmount =
(shouldDelayUnfreeze ? rewardAmount : 0) + (input.unfreezeDays > 0 ? releaseAmount : 0)
const nextAvailable = const nextAvailable =
Number(wallet?.available_amount || 0) + Number(wallet?.available_amount || 0) +
rewardAmount + (shouldDelayUnfreeze ? 0 : rewardAmount) +
(shouldDelayUnfreeze ? 0 : releaseAmount) - (input.unfreezeDays > 0 ? 0 : releaseAmount) -
debtOffsetAmount debtOffsetAmount
const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount) const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount)
@@ -269,25 +275,21 @@ export async function acceptWorkOrderAndSettle(input: {
updated_at = $5 updated_at = $5
WHERE worker_id = $6 WHERE worker_id = $6
`, `,
[ [nextAvailable, nextFrozen, pendingUnfreezeAmount, rewardAmount, input.now, workerId],
nextAvailable,
nextFrozen,
shouldDelayUnfreeze ? releaseAmount : 0,
rewardAmount,
input.now,
workerId,
],
) )
if (releaseAmount > 0) { if (pendingUnfreezeAmount > 0) {
if (shouldDelayUnfreeze) {
await enqueueDepositUnfreezeWithClient(client, { await enqueueDepositUnfreezeWithClient(client, {
workerId, workerId,
workOrderId: input.workOrderId, workOrderId: input.workOrderId,
amount: releaseAmount, amount: pendingUnfreezeAmount,
unfreezeDays: input.unfreezeDays, unfreezeDays: input.unfreezeDays,
now: input.now, now: input.now,
}) })
}
if (releaseAmount > 0) {
if (input.unfreezeDays > 0) {
await client.query( await client.query(
` `
INSERT INTO worker_wallet_ledgers ( INSERT INTO worker_wallet_ledgers (
@@ -322,14 +324,31 @@ export async function acceptWorkOrderAndSettle(input: {
} }
if (rewardAmount > 0) { if (rewardAmount > 0) {
const rewardLedgerType = shouldDelayUnfreeze
? 'deposit_pending_unfreeze'
: 'reward_settlement'
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,
related_work_order_id, note, payload_json, created_at related_work_order_id, note, payload_json, created_at
) VALUES ($1, 'reward_settlement', $2, $3, $4, $5, '验收通过结算报酬', '{}'::jsonb, $6) ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)
`, `,
[workerId, rewardAmount, nextAvailable, nextFrozen, input.workOrderId, input.now], [
workerId,
rewardLedgerType,
rewardAmount,
nextAvailable,
nextFrozen,
input.workOrderId,
shouldDelayUnfreeze ? '验收通过报酬待解冻' : '验收通过结算报酬',
JSON.stringify(
shouldDelayUnfreeze
? { workOrderId: input.workOrderId, unfreezeDays: input.unfreezeDays }
: {},
),
input.now,
],
) )
} }
for (const debtOffset of debtOffsets) { for (const debtOffset of debtOffsets) {
@@ -383,6 +402,7 @@ export async function acceptWorkOrderShareAndSettle(input: {
workOrderId: number workOrderId: number
shareId: number shareId: number
unfreezeDays: number unfreezeDays: number
rewardUnfreezeEnabled: boolean
now: string now: string
actorType?: string actorType?: string
actorName?: string actorName?: string
@@ -428,11 +448,13 @@ export async function acceptWorkOrderShareAndSettle(input: {
now: input.now, now: input.now,
}) })
const debtOffsetAmount = debtOffsets.reduce((total, item) => total + item.amount, 0) const debtOffsetAmount = debtOffsets.reduce((total, item) => total + item.amount, 0)
const shouldDelayUnfreeze = input.unfreezeDays > 0 const shouldDelayUnfreeze = input.rewardUnfreezeEnabled && input.unfreezeDays > 0
const pendingUnfreezeAmount =
(shouldDelayUnfreeze ? shareReward : 0) + (input.unfreezeDays > 0 ? releaseAmount : 0)
const nextAvailable = const nextAvailable =
Number(wallet?.available_amount || 0) + Number(wallet?.available_amount || 0) +
shareReward + (shouldDelayUnfreeze ? 0 : shareReward) +
(shouldDelayUnfreeze ? 0 : releaseAmount) - (input.unfreezeDays > 0 ? 0 : releaseAmount) -
debtOffsetAmount debtOffsetAmount
const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount) const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount)
await client.query( await client.query(
@@ -441,21 +463,14 @@ export async function acceptWorkOrderShareAndSettle(input: {
pending_unfreeze_amount = pending_unfreeze_amount + $3, pending_unfreeze_amount = pending_unfreeze_amount + $3,
total_settled_amount = total_settled_amount + $4, updated_at = $5 total_settled_amount = total_settled_amount + $4, updated_at = $5
WHERE worker_id = $6`, WHERE worker_id = $6`,
[ [nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, workerId],
nextAvailable,
nextFrozen,
shouldDelayUnfreeze ? releaseAmount : 0,
shareReward,
input.now,
workerId,
],
) )
if (releaseAmount > 0 && shouldDelayUnfreeze) { if (pendingUnfreezeAmount > 0) {
await enqueueDepositUnfreezeWithClient(client, { await enqueueDepositUnfreezeWithClient(client, {
workerId, workerId,
workOrderId: input.workOrderId, workOrderId: input.workOrderId,
amount: releaseAmount, amount: pendingUnfreezeAmount,
unfreezeDays: input.unfreezeDays, unfreezeDays: input.unfreezeDays,
now: input.now, now: input.now,
}) })
@@ -468,31 +483,43 @@ export async function acceptWorkOrderShareAndSettle(input: {
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)`, ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)`,
[ [
workerId, workerId,
shouldDelayUnfreeze ? 'deposit_pending_unfreeze' : 'deposit_release', input.unfreezeDays > 0 ? 'deposit_pending_unfreeze' : 'deposit_release',
releaseAmount, releaseAmount,
nextAvailable, nextAvailable,
nextFrozen, nextFrozen,
input.workOrderId, input.workOrderId,
shouldDelayUnfreeze ? '拼单份额验收押金待解冻' : '拼单份额验收释放押金', input.unfreezeDays > 0 ? '拼单份额验收押金待解冻' : '拼单份额验收释放押金',
JSON.stringify({ workOrderId: input.workOrderId, shareId: input.shareId }), JSON.stringify({ workOrderId: input.workOrderId, shareId: input.shareId }),
input.now, input.now,
], ],
) )
} }
if (shareReward > 0) { if (shareReward > 0) {
const rewardLedgerType = shouldDelayUnfreeze ? 'deposit_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,
related_work_order_id, note, payload_json, created_at related_work_order_id, note, payload_json, created_at
) VALUES ($1, 'sharing_reward', $2, $3, $4, $5, $6, $7::jsonb, $8)`, ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)`,
[ [
workerId, workerId,
rewardLedgerType,
shareReward, shareReward,
nextAvailable, nextAvailable,
nextFrozen, nextFrozen,
input.workOrderId, input.workOrderId,
`拼单份额验收结算报酬 ${share.quantity}`, shouldDelayUnfreeze
JSON.stringify({ workOrderId: input.workOrderId, shareId: input.shareId }), ? '拼单份额验收报酬待解冻'
: `拼单份额验收结算报酬 ${share.quantity}`,
JSON.stringify(
shouldDelayUnfreeze
? {
workOrderId: input.workOrderId,
shareId: input.shareId,
unfreezeDays: input.unfreezeDays,
}
: { workOrderId: input.workOrderId, shareId: input.shareId },
),
input.now, input.now,
], ],
) )
@@ -483,9 +483,7 @@ function buildWorkerFinanceRequestWhere({
if (createdFrom) { if (createdFrom) {
params.push(createdFrom) params.push(createdFrom)
// 纯日期字符串 cast 后即当天 00:00:00;带时间则精确到秒 // 纯日期字符串 cast 后即当天 00:00:00;带时间则精确到秒
filters.push( filters.push(`wfr.created_at >= ($${params.length}::timestamp) AT TIME ZONE 'Asia/Shanghai'`)
`wfr.created_at >= ($${params.length}::timestamp) AT TIME ZONE 'Asia/Shanghai'`,
)
} }
if (createdTo) { if (createdTo) {
params.push(createdTo) params.push(createdTo)
@@ -8,6 +8,7 @@ import {
} from '../../repositories/worker-platform/index.js' } from '../../repositories/worker-platform/index.js'
import type { JsonObject } from '../../types/json.js' import type { JsonObject } from '../../types/json.js'
import { createHttpError } from '../../utils/http.js' import { createHttpError } from '../../utils/http.js'
import { getWorkerFinanceConfig } from './worker-finance-config-service.js'
import { nowIso } from '../../utils/time.js' import { nowIso } from '../../utils/time.js'
import { resolveAdminNotificationEntity } from '../admin/admin-notification-service.js' import { resolveAdminNotificationEntity } from '../admin/admin-notification-service.js'
import { import {
@@ -21,7 +22,6 @@ import {
resolveWorkOrderOperationalStatus, resolveWorkOrderOperationalStatus,
} from './mappers.js' } from './mappers.js'
import { resolveWorkOrderRealtimeWorkerIds } from './admin-work-order-realtime-service.js' import { resolveWorkOrderRealtimeWorkerIds } from './admin-work-order-realtime-service.js'
import { getWorkerFinanceConfig } from './worker-finance-config-service.js'
import { getRequiredWorkOrder } from './worker-session-context-service.js' import { getRequiredWorkOrder } from './worker-session-context-service.js'
export async function acceptAdminWorkOrder(workOrderId: number | string, actorName = '') { export async function acceptAdminWorkOrder(workOrderId: number | string, actorName = '') {
@@ -43,7 +43,7 @@ export async function acceptAdminWorkOrder(workOrderId: number | string, actorNa
const now = nowIso() const now = nowIso()
const { order: updated, failureReason } = await acceptWorkOrderAndSettle({ const { order: updated, failureReason } = await acceptWorkOrderAndSettle({
workOrderId: workOrder.id, workOrderId: workOrder.id,
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays, ...resolveAcceptanceFinanceOptions(),
now, now,
actorType: 'admin', actorType: 'admin',
actorName, actorName,
@@ -75,7 +75,7 @@ export async function acceptAdminWorkOrderShare(
const result = await acceptWorkOrderShareAndSettle({ const result = await acceptWorkOrderShareAndSettle({
workOrderId: Number(workOrderId), workOrderId: Number(workOrderId),
shareId: Number(shareId), shareId: Number(shareId),
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays, ...resolveAcceptanceFinanceOptions(),
now: nowIso(), now: nowIso(),
actorType: 'admin', actorType: 'admin',
actorName, actorName,
@@ -104,6 +104,14 @@ export async function acceptAdminWorkOrderShare(
return { order: mapWorkOrderAdmin(result.order), share: mapWorkOrderShare(result.share) } return { order: mapWorkOrderAdmin(result.order), share: mapWorkOrderShare(result.share) }
} }
function resolveAcceptanceFinanceOptions() {
const financeConfig = getWorkerFinanceConfig()
return {
unfreezeDays: financeConfig.depositUnfreezeDays,
rewardUnfreezeEnabled: financeConfig.rewardUnfreezeEnabled,
}
}
export async function cancelAdminWorkOrderShare( export async function cancelAdminWorkOrderShare(
workOrderId: number | string, workOrderId: number | string,
shareId: number | string, shareId: number | string,
@@ -173,6 +173,7 @@ export async function submitWorkerOrderAcceptance(
const { order: acceptedOrder, failureReason } = await acceptWorkOrderAndSettle({ const { order: acceptedOrder, failureReason } = await acceptWorkOrderAndSettle({
workOrderId: workOrder.id, workOrderId: workOrder.id,
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays, unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
rewardUnfreezeEnabled: getWorkerFinanceConfig().rewardUnfreezeEnabled,
now, now,
actorType: 'worker', actorType: 'worker',
actorName: worker.display_name || worker.username || '', actorName: worker.display_name || worker.username || '',
@@ -452,6 +453,7 @@ async function submitWorkerSharingAcceptance(
workOrderId: Number(workOrder.id), workOrderId: Number(workOrder.id),
shareId: Number(result.share.id), shareId: Number(result.share.id),
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays, unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
rewardUnfreezeEnabled: getWorkerFinanceConfig().rewardUnfreezeEnabled,
now, now,
actorType: 'worker', actorType: 'worker',
actorName: worker.display_name || worker.username || '', actorName: worker.display_name || worker.username || '',
@@ -9,6 +9,7 @@ import {
export type WorkerFinanceConfig = { export type WorkerFinanceConfig = {
depositUnfreezeDays: number depositUnfreezeDays: number
rewardUnfreezeEnabled: boolean
recharge: { recharge: {
enabled: boolean enabled: boolean
channelName: string channelName: string
@@ -56,6 +57,7 @@ export function normalizeWorkerFinanceConfig(rawValue: unknown): WorkerFinanceCo
return { return {
depositUnfreezeDays: normalizeRangeInteger(source.depositUnfreezeDays, 3, 0, 30), depositUnfreezeDays: normalizeRangeInteger(source.depositUnfreezeDays, 3, 0, 30),
rewardUnfreezeEnabled: source.rewardUnfreezeEnabled !== false,
recharge: { recharge: {
enabled: typeof recharge.enabled === 'boolean' ? recharge.enabled : true, enabled: typeof recharge.enabled === 'boolean' ? recharge.enabled : true,
channelName: String(recharge.channelName || recharge.channel_name || '').trim(), channelName: String(recharge.channelName || recharge.channel_name || '').trim(),
@@ -80,6 +82,7 @@ export function normalizeWorkerFinanceConfig(rawValue: unknown): WorkerFinanceCo
export function createDefaultWorkerFinanceConfig(): WorkerFinanceConfig { export function createDefaultWorkerFinanceConfig(): WorkerFinanceConfig {
return { return {
depositUnfreezeDays: 3, depositUnfreezeDays: 3,
rewardUnfreezeEnabled: true,
recharge: { recharge: {
enabled: true, enabled: true,
channelName: '', channelName: '',
@@ -101,6 +104,7 @@ export function createDefaultWorkerFinanceConfig(): WorkerFinanceConfig {
function refreshWorkerFinanceConfig(config: WorkerFinanceConfig): WorkerFinanceConfig { function refreshWorkerFinanceConfig(config: WorkerFinanceConfig): WorkerFinanceConfig {
return { return {
depositUnfreezeDays: config.depositUnfreezeDays, depositUnfreezeDays: config.depositUnfreezeDays,
rewardUnfreezeEnabled: config.rewardUnfreezeEnabled,
recharge: { recharge: {
...config.recharge, ...config.recharge,
qrCodeImage: config.recharge.qrCodeImage qrCodeImage: config.recharge.qrCodeImage
+1 -2
View File
@@ -429,8 +429,7 @@ function patchAdminWorkOrderCaches(
matchesWorkOrderKeyword(item, keyword) && matchesWorkOrderKeyword(item, keyword) &&
(!workerId || workerId === Number(item.worker?.workerId || 0)) && (!workerId || workerId === Number(item.worker?.workerId || 0)) &&
(!targetWorkOrderId || targetWorkOrderId === Number(item.workOrderId || 0)) (!targetWorkOrderId || targetWorkOrderId === Number(item.workOrderId || 0))
const shouldInsert = const shouldInsert = event.operation === 'upsert' && page === 1 && matches && !levelFilterActive
event.operation === 'upsert' && page === 1 && matches && !levelFilterActive
const operation = event.operation === 'remove' || !matches ? 'remove' : 'upsert' const operation = event.operation === 'remove' || !matches ? 'remove' : 'upsert'
queryClient.setQueryData( queryClient.setQueryData(
queryKey, queryKey,
@@ -395,7 +395,7 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
label="追缴金额" label="追缴金额"
name="recoveryAmount" name="recoveryAmount"
rules={[{ required: true, message: '请输入追缴金额' }]} rules={[{ required: true, message: '请输入追缴金额' }]}
extra="系统依次扣减该责任打手的本单待解冻金、可用余额;不足部分转为欠款,并从后续验收报酬自动抵扣。" extra="系统依次扣减该责任打手的本单待解冻金、可用余额;不足部分转为欠款,并从后续验收报酬自动抵扣。"
> >
<InputNumber min={0} precision={2} addonAfter="元" className="full-width" /> <InputNumber min={0} precision={2} addonAfter="元" className="full-width" />
</Form.Item> </Form.Item>
@@ -41,6 +41,7 @@ import { formatAdminDateTime } from '@/utils/admin-time'
import { asRecord, formatMoney } from './shared' import { asRecord, formatMoney } from './shared'
type FinanceConfigFormValues = { type FinanceConfigFormValues = {
depositUnfreezeDays?: number depositUnfreezeDays?: number
rewardUnfreezeEnabled?: boolean
recharge?: { recharge?: {
enabled?: boolean enabled?: boolean
channelName?: string channelName?: string
@@ -158,6 +159,7 @@ export default function FinancePanel() {
try { try {
await saveAdminWorkerFinanceConfig({ await saveAdminWorkerFinanceConfig({
depositUnfreezeDays: Number(values.depositUnfreezeDays ?? 3), depositUnfreezeDays: Number(values.depositUnfreezeDays ?? 3),
rewardUnfreezeEnabled: values.rewardUnfreezeEnabled !== false,
recharge: { recharge: {
enabled: values.recharge?.enabled !== false, enabled: values.recharge?.enabled !== false,
channelName: String(values.recharge?.channelName || '').trim(), channelName: String(values.recharge?.channelName || '').trim(),
@@ -401,12 +403,20 @@ export default function FinancePanel() {
<Form.Item <Form.Item
label="押金解冻天数" label="押金解冻天数"
name="depositUnfreezeDays" name="depositUnfreezeDays"
tooltip="有押金的工单验收通过后,押金进入待解冻,N 天后自动转入可用余额;0 表示验收通过立即释放" tooltip="验收通过后,押金进入待解冻,N 天后自动转入可用余额;0 表示押金立即释放"
> >
<InputNumber min={0} max={30} step={1} addonAfter="天" /> <InputNumber min={0} max={30} step={1} addonAfter="天" />
</Form.Item> </Form.Item>
<Form.Item
label="报酬待解冻"
name="rewardUnfreezeEnabled"
valuePropName="checked"
tooltip="开启后,验收报酬和押金一起进入待解冻;期间可由售后问题单优先扣减"
>
<Switch />
</Form.Item>
<Typography.Text type="secondary"> <Typography.Text type="secondary">
"接单工单" "接单工单""售后问题单"
</Typography.Text> </Typography.Text>
</Card> </Card>
@@ -636,6 +646,7 @@ export default function FinancePanel() {
function mapFinanceConfigToFormValues(config?: WorkerFinanceConfig): FinanceConfigFormValues { function mapFinanceConfigToFormValues(config?: WorkerFinanceConfig): FinanceConfigFormValues {
return { return {
depositUnfreezeDays: Number(config?.depositUnfreezeDays ?? 3), depositUnfreezeDays: Number(config?.depositUnfreezeDays ?? 3),
rewardUnfreezeEnabled: config?.rewardUnfreezeEnabled !== false,
recharge: { recharge: {
enabled: config?.recharge.enabled !== false, enabled: config?.recharge.enabled !== false,
channelName: config?.recharge.channelName || '', channelName: config?.recharge.channelName || '',
@@ -747,7 +747,7 @@ function ScheduledJobCard({
} }
> >
<Typography.Paragraph type="secondary" style={{ marginBottom: 12 }}> <Typography.Paragraph type="secondary" style={{ marginBottom: 12 }}>
3 3
</Typography.Paragraph> </Typography.Paragraph>
<div className="platform-form-grid"> <div className="platform-form-grid">
<NumberField <NumberField
@@ -98,7 +98,11 @@ export function WorkerProfileDesktopWallet({ worker }: { worker: WorkerUser }) {
const items = [ const items = [
['可用余额', worker.wallet.availableAmount, '可用于抢单冻结或提现申请'], ['可用余额', worker.wallet.availableAmount, '可用于抢单冻结或提现申请'],
['冻结押金', worker.wallet.frozenDepositAmount, '抢单后冻结,验收后进入待解冻'], ['冻结押金', worker.wallet.frozenDepositAmount, '抢单后冻结,验收后进入待解冻'],
['待解冻押金', worker.wallet.pendingUnfreezeAmount, '验收后按配置天数解冻到账'], [
'待解冻金额',
worker.wallet.pendingUnfreezeAmount,
'验收报酬与押金按配置天数解冻到账,期间不可提现',
],
['累计结算', worker.wallet.totalSettledAmount, '已结算入账的报酬合计'], ['累计结算', worker.wallet.totalSettledAmount, '已结算入账的报酬合计'],
] as const ] as const
return ( return (
@@ -333,7 +333,7 @@ export function WorkerProfileFinanceModals({
<Alert <Alert
showIcon showIcon
type="warning" type="warning"
message={`每天最多提现 3 次(支付宝、微信共享次数);100 元及以下支持微信和支付宝,超过 100 元仅支持支付宝;有押金的工单验收通过后,押金需 ${Number(financeConfig?.depositUnfreezeDays ?? 3)} 天解冻到账后方可提现。`} message={`每天最多提现 3 次(支付宝、微信共享次数);100 元及以下支持微信和支付宝,超过 100 元仅支持支付宝;验收报酬与押金需 ${Number(financeConfig?.depositUnfreezeDays ?? 3)} 天解冻到账后方可提现。`}
style={{ marginBottom: 16 }} style={{ marginBottom: 16 }}
/> />
<Descriptions column={1} bordered size="small" style={{ marginBottom: 16 }}> <Descriptions column={1} bordered size="small" style={{ marginBottom: 16 }}>
@@ -189,7 +189,7 @@ export function WorkerProfileMobileView({
<div className="worker-profile-mobile-wallet-grid"> <div className="worker-profile-mobile-wallet-grid">
<MobileWalletItem label="冻结押金" amount={worker.wallet.frozenDepositAmount} /> <MobileWalletItem label="冻结押金" amount={worker.wallet.frozenDepositAmount} />
<MobileWalletItem label="待解冻金" amount={worker.wallet.pendingUnfreezeAmount} /> <MobileWalletItem label="待解冻金" amount={worker.wallet.pendingUnfreezeAmount} />
<MobileWalletItem label="累计结算" amount={worker.wallet.totalSettledAmount} /> <MobileWalletItem label="累计结算" amount={worker.wallet.totalSettledAmount} />
</div> </div>
</div> </div>
@@ -29,7 +29,7 @@ const LEDGER_TYPE_OPTIONS = [
{ value: 'withdraw_paid', label: '提现打款' }, { value: 'withdraw_paid', label: '提现打款' },
{ 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: 'deposit_unfreeze', label: '押金已解冻' }, { value: 'deposit_unfreeze', label: '押金已解冻' },
{ value: 'deposit_deduction', label: '扣除押金' }, { value: 'deposit_deduction', label: '扣除押金' },
{ value: 'reward_settlement', label: '结算报酬' }, { value: 'reward_settlement', label: '结算报酬' },
@@ -52,7 +52,7 @@ export function formatLedgerType(ledgerType: string) {
withdraw_paid: '提现打款', withdraw_paid: '提现打款',
deposit_freeze: '冻结押金', deposit_freeze: '冻结押金',
deposit_release: '释放押金', deposit_release: '释放押金',
deposit_pending_unfreeze: '押金待解冻', deposit_pending_unfreeze: '待解冻金额',
deposit_unfreeze: '押金已解冻', deposit_unfreeze: '押金已解冻',
deposit_deduction: '扣除押金', deposit_deduction: '扣除押金',
reward_settlement: '结算报酬', reward_settlement: '结算报酬',
@@ -408,6 +408,7 @@ export function fetchAdminWorkerFinanceConfig() {
export function saveAdminWorkerFinanceConfig(payload: { export function saveAdminWorkerFinanceConfig(payload: {
depositUnfreezeDays?: number depositUnfreezeDays?: number
rewardUnfreezeEnabled?: boolean
recharge?: { recharge?: {
enabled?: boolean enabled?: boolean
channelName?: string channelName?: string
@@ -200,6 +200,7 @@ export type WorkerAfterSalesCaseEvent = {
export type WorkerFinanceConfig = { export type WorkerFinanceConfig = {
depositUnfreezeDays: number depositUnfreezeDays: number
rewardUnfreezeEnabled: boolean
recharge: { recharge: {
enabled: boolean enabled: boolean
channelName: string channelName: string