新增验收报酬待解冻配置并修复待解冻结算漏入队与重复释放流水
This commit is contained in:
@@ -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: {
|
||||
workOrderId: number
|
||||
unfreezeDays: number
|
||||
rewardUnfreezeEnabled: boolean
|
||||
now: string
|
||||
/** 操作人类型:admin / worker / system */
|
||||
actorType?: string
|
||||
@@ -94,11 +95,13 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
now: input.now,
|
||||
})
|
||||
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 =
|
||||
Number(wallet?.available_amount || 0) +
|
||||
shareReward +
|
||||
(shouldDelayUnfreeze ? 0 : releaseAmount) -
|
||||
(shouldDelayUnfreeze ? 0 : shareReward) +
|
||||
(input.unfreezeDays > 0 ? 0 : releaseAmount) -
|
||||
debtOffsetAmount
|
||||
const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount)
|
||||
|
||||
@@ -113,86 +116,87 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
updated_at = $5
|
||||
WHERE worker_id = $6
|
||||
`,
|
||||
[
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
shouldDelayUnfreeze ? releaseAmount : 0,
|
||||
shareReward,
|
||||
input.now,
|
||||
shareWorkerId,
|
||||
],
|
||||
[nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, shareWorkerId],
|
||||
)
|
||||
|
||||
if (releaseAmount > 0) {
|
||||
if (shouldDelayUnfreeze) {
|
||||
await enqueueDepositUnfreezeWithClient(client, {
|
||||
workerId: shareWorkerId,
|
||||
workOrderId: input.workOrderId,
|
||||
amount: releaseAmount,
|
||||
unfreezeDays: input.unfreezeDays,
|
||||
now: input.now,
|
||||
})
|
||||
await client.query(
|
||||
`
|
||||
if (pendingUnfreezeAmount > 0) {
|
||||
await enqueueDepositUnfreezeWithClient(client, {
|
||||
workerId: shareWorkerId,
|
||||
workOrderId: input.workOrderId,
|
||||
amount: pendingUnfreezeAmount,
|
||||
unfreezeDays: input.unfreezeDays,
|
||||
now: input.now,
|
||||
})
|
||||
}
|
||||
if (releaseAmount > 0 && input.unfreezeDays > 0) {
|
||||
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, 'deposit_pending_unfreeze', $2, $3, $4, $5, '拼单验收押金待解冻', $6::jsonb, $7)
|
||||
`,
|
||||
[
|
||||
shareWorkerId,
|
||||
releaseAmount,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
JSON.stringify({
|
||||
workOrderId: input.workOrderId,
|
||||
shareId: Number(share.id),
|
||||
unfreezeDays: input.unfreezeDays,
|
||||
}),
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
} else {
|
||||
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, 'deposit_release', $2, $3, $4, $5, '拼单验收释放押金', $6::jsonb, $7)
|
||||
`,
|
||||
[
|
||||
shareWorkerId,
|
||||
releaseAmount,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
JSON.stringify({ workOrderId: input.workOrderId, shareId: Number(share.id) }),
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
}
|
||||
[
|
||||
shareWorkerId,
|
||||
releaseAmount,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
JSON.stringify({
|
||||
workOrderId: input.workOrderId,
|
||||
shareId: Number(share.id),
|
||||
unfreezeDays: input.unfreezeDays,
|
||||
}),
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
if (shareReward > 0) {
|
||||
if (releaseAmount > 0 && input.unfreezeDays === 0) {
|
||||
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, 'sharing_reward', $2, $3, $4, $5, $6, $7::jsonb, $8)
|
||||
) VALUES ($1, 'deposit_release', $2, $3, $4, $5, '拼单验收释放押金', $6::jsonb, $7)
|
||||
`,
|
||||
[
|
||||
shareWorkerId,
|
||||
releaseAmount,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
JSON.stringify({ workOrderId: input.workOrderId, shareId: Number(share.id) }),
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
if (shareReward > 0) {
|
||||
const rewardLedgerType = shouldDelayUnfreeze
|
||||
? 'deposit_pending_unfreeze'
|
||||
: 'reward_settlement'
|
||||
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, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)
|
||||
`,
|
||||
[
|
||||
shareWorkerId,
|
||||
rewardLedgerType,
|
||||
shareReward,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
`拼单结算报酬 ${share.quantity} 份`,
|
||||
shouldDelayUnfreeze
|
||||
? '拼单份额验收报酬待解冻'
|
||||
: `拼单份额验收结算报酬 ${share.quantity} 份`,
|
||||
JSON.stringify({
|
||||
workOrderId: input.workOrderId,
|
||||
shareId: Number(share.id),
|
||||
quantity: share.quantity,
|
||||
...(shouldDelayUnfreeze ? { unfreezeDays: input.unfreezeDays } : {}),
|
||||
}),
|
||||
input.now,
|
||||
],
|
||||
@@ -250,11 +254,13 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
now: input.now,
|
||||
})
|
||||
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 =
|
||||
Number(wallet?.available_amount || 0) +
|
||||
rewardAmount +
|
||||
(shouldDelayUnfreeze ? 0 : releaseAmount) -
|
||||
(shouldDelayUnfreeze ? 0 : rewardAmount) +
|
||||
(input.unfreezeDays > 0 ? 0 : releaseAmount) -
|
||||
debtOffsetAmount
|
||||
const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount)
|
||||
|
||||
@@ -269,25 +275,21 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
updated_at = $5
|
||||
WHERE worker_id = $6
|
||||
`,
|
||||
[
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
shouldDelayUnfreeze ? releaseAmount : 0,
|
||||
rewardAmount,
|
||||
input.now,
|
||||
workerId,
|
||||
],
|
||||
[nextAvailable, nextFrozen, pendingUnfreezeAmount, rewardAmount, input.now, workerId],
|
||||
)
|
||||
|
||||
if (pendingUnfreezeAmount > 0) {
|
||||
await enqueueDepositUnfreezeWithClient(client, {
|
||||
workerId,
|
||||
workOrderId: input.workOrderId,
|
||||
amount: pendingUnfreezeAmount,
|
||||
unfreezeDays: input.unfreezeDays,
|
||||
now: input.now,
|
||||
})
|
||||
}
|
||||
|
||||
if (releaseAmount > 0) {
|
||||
if (shouldDelayUnfreeze) {
|
||||
await enqueueDepositUnfreezeWithClient(client, {
|
||||
workerId,
|
||||
workOrderId: input.workOrderId,
|
||||
amount: releaseAmount,
|
||||
unfreezeDays: input.unfreezeDays,
|
||||
now: input.now,
|
||||
})
|
||||
if (input.unfreezeDays > 0) {
|
||||
await client.query(
|
||||
`
|
||||
INSERT INTO worker_wallet_ledgers (
|
||||
@@ -322,14 +324,31 @@ export async function acceptWorkOrderAndSettle(input: {
|
||||
}
|
||||
|
||||
if (rewardAmount > 0) {
|
||||
const rewardLedgerType = shouldDelayUnfreeze
|
||||
? 'deposit_pending_unfreeze'
|
||||
: 'reward_settlement'
|
||||
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, '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) {
|
||||
@@ -383,6 +402,7 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
workOrderId: number
|
||||
shareId: number
|
||||
unfreezeDays: number
|
||||
rewardUnfreezeEnabled: boolean
|
||||
now: string
|
||||
actorType?: string
|
||||
actorName?: string
|
||||
@@ -428,11 +448,13 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
now: input.now,
|
||||
})
|
||||
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 =
|
||||
Number(wallet?.available_amount || 0) +
|
||||
shareReward +
|
||||
(shouldDelayUnfreeze ? 0 : releaseAmount) -
|
||||
(shouldDelayUnfreeze ? 0 : shareReward) +
|
||||
(input.unfreezeDays > 0 ? 0 : releaseAmount) -
|
||||
debtOffsetAmount
|
||||
const nextFrozen = Math.max(0, Number(wallet?.frozen_deposit_amount || 0) - releaseAmount)
|
||||
await client.query(
|
||||
@@ -441,21 +463,14 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
pending_unfreeze_amount = pending_unfreeze_amount + $3,
|
||||
total_settled_amount = total_settled_amount + $4, updated_at = $5
|
||||
WHERE worker_id = $6`,
|
||||
[
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
shouldDelayUnfreeze ? releaseAmount : 0,
|
||||
shareReward,
|
||||
input.now,
|
||||
workerId,
|
||||
],
|
||||
[nextAvailable, nextFrozen, pendingUnfreezeAmount, shareReward, input.now, workerId],
|
||||
)
|
||||
|
||||
if (releaseAmount > 0 && shouldDelayUnfreeze) {
|
||||
if (pendingUnfreezeAmount > 0) {
|
||||
await enqueueDepositUnfreezeWithClient(client, {
|
||||
workerId,
|
||||
workOrderId: input.workOrderId,
|
||||
amount: releaseAmount,
|
||||
amount: pendingUnfreezeAmount,
|
||||
unfreezeDays: input.unfreezeDays,
|
||||
now: input.now,
|
||||
})
|
||||
@@ -468,31 +483,43 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)`,
|
||||
[
|
||||
workerId,
|
||||
shouldDelayUnfreeze ? 'deposit_pending_unfreeze' : 'deposit_release',
|
||||
input.unfreezeDays > 0 ? 'deposit_pending_unfreeze' : 'deposit_release',
|
||||
releaseAmount,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
shouldDelayUnfreeze ? '拼单份额验收押金待解冻' : '拼单份额验收释放押金',
|
||||
input.unfreezeDays > 0 ? '拼单份额验收押金待解冻' : '拼单份额验收释放押金',
|
||||
JSON.stringify({ workOrderId: input.workOrderId, shareId: input.shareId }),
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
}
|
||||
if (shareReward > 0) {
|
||||
const rewardLedgerType = shouldDelayUnfreeze ? 'deposit_pending_unfreeze' : 'sharing_reward'
|
||||
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, 'sharing_reward', $2, $3, $4, $5, $6, $7::jsonb, $8)`,
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)`,
|
||||
[
|
||||
workerId,
|
||||
rewardLedgerType,
|
||||
shareReward,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
`拼单份额验收结算报酬 ${share.quantity} 份`,
|
||||
JSON.stringify({ workOrderId: input.workOrderId, shareId: input.shareId }),
|
||||
shouldDelayUnfreeze
|
||||
? '拼单份额验收报酬待解冻'
|
||||
: `拼单份额验收结算报酬 ${share.quantity} 份`,
|
||||
JSON.stringify(
|
||||
shouldDelayUnfreeze
|
||||
? {
|
||||
workOrderId: input.workOrderId,
|
||||
shareId: input.shareId,
|
||||
unfreezeDays: input.unfreezeDays,
|
||||
}
|
||||
: { workOrderId: input.workOrderId, shareId: input.shareId },
|
||||
),
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -483,9 +483,7 @@ function buildWorkerFinanceRequestWhere({
|
||||
if (createdFrom) {
|
||||
params.push(createdFrom)
|
||||
// 纯日期字符串 cast 后即当天 00:00:00;带时间则精确到秒
|
||||
filters.push(
|
||||
`wfr.created_at >= ($${params.length}::timestamp) AT TIME ZONE 'Asia/Shanghai'`,
|
||||
)
|
||||
filters.push(`wfr.created_at >= ($${params.length}::timestamp) AT TIME ZONE 'Asia/Shanghai'`)
|
||||
}
|
||||
if (createdTo) {
|
||||
params.push(createdTo)
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { getWorkerFinanceConfig } from './worker-finance-config-service.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { resolveAdminNotificationEntity } from '../admin/admin-notification-service.js'
|
||||
import {
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
resolveWorkOrderOperationalStatus,
|
||||
} from './mappers.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'
|
||||
|
||||
export async function acceptAdminWorkOrder(workOrderId: number | string, actorName = '') {
|
||||
@@ -43,7 +43,7 @@ export async function acceptAdminWorkOrder(workOrderId: number | string, actorNa
|
||||
const now = nowIso()
|
||||
const { order: updated, failureReason } = await acceptWorkOrderAndSettle({
|
||||
workOrderId: workOrder.id,
|
||||
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
|
||||
...resolveAcceptanceFinanceOptions(),
|
||||
now,
|
||||
actorType: 'admin',
|
||||
actorName,
|
||||
@@ -75,7 +75,7 @@ export async function acceptAdminWorkOrderShare(
|
||||
const result = await acceptWorkOrderShareAndSettle({
|
||||
workOrderId: Number(workOrderId),
|
||||
shareId: Number(shareId),
|
||||
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
|
||||
...resolveAcceptanceFinanceOptions(),
|
||||
now: nowIso(),
|
||||
actorType: 'admin',
|
||||
actorName,
|
||||
@@ -104,6 +104,14 @@ export async function acceptAdminWorkOrderShare(
|
||||
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(
|
||||
workOrderId: number | string,
|
||||
shareId: number | string,
|
||||
|
||||
@@ -173,6 +173,7 @@ export async function submitWorkerOrderAcceptance(
|
||||
const { order: acceptedOrder, failureReason } = await acceptWorkOrderAndSettle({
|
||||
workOrderId: workOrder.id,
|
||||
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
|
||||
rewardUnfreezeEnabled: getWorkerFinanceConfig().rewardUnfreezeEnabled,
|
||||
now,
|
||||
actorType: 'worker',
|
||||
actorName: worker.display_name || worker.username || '',
|
||||
@@ -452,6 +453,7 @@ async function submitWorkerSharingAcceptance(
|
||||
workOrderId: Number(workOrder.id),
|
||||
shareId: Number(result.share.id),
|
||||
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
|
||||
rewardUnfreezeEnabled: getWorkerFinanceConfig().rewardUnfreezeEnabled,
|
||||
now,
|
||||
actorType: 'worker',
|
||||
actorName: worker.display_name || worker.username || '',
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
|
||||
export type WorkerFinanceConfig = {
|
||||
depositUnfreezeDays: number
|
||||
rewardUnfreezeEnabled: boolean
|
||||
recharge: {
|
||||
enabled: boolean
|
||||
channelName: string
|
||||
@@ -56,6 +57,7 @@ export function normalizeWorkerFinanceConfig(rawValue: unknown): WorkerFinanceCo
|
||||
|
||||
return {
|
||||
depositUnfreezeDays: normalizeRangeInteger(source.depositUnfreezeDays, 3, 0, 30),
|
||||
rewardUnfreezeEnabled: source.rewardUnfreezeEnabled !== false,
|
||||
recharge: {
|
||||
enabled: typeof recharge.enabled === 'boolean' ? recharge.enabled : true,
|
||||
channelName: String(recharge.channelName || recharge.channel_name || '').trim(),
|
||||
@@ -80,6 +82,7 @@ export function normalizeWorkerFinanceConfig(rawValue: unknown): WorkerFinanceCo
|
||||
export function createDefaultWorkerFinanceConfig(): WorkerFinanceConfig {
|
||||
return {
|
||||
depositUnfreezeDays: 3,
|
||||
rewardUnfreezeEnabled: true,
|
||||
recharge: {
|
||||
enabled: true,
|
||||
channelName: '',
|
||||
@@ -101,6 +104,7 @@ export function createDefaultWorkerFinanceConfig(): WorkerFinanceConfig {
|
||||
function refreshWorkerFinanceConfig(config: WorkerFinanceConfig): WorkerFinanceConfig {
|
||||
return {
|
||||
depositUnfreezeDays: config.depositUnfreezeDays,
|
||||
rewardUnfreezeEnabled: config.rewardUnfreezeEnabled,
|
||||
recharge: {
|
||||
...config.recharge,
|
||||
qrCodeImage: config.recharge.qrCodeImage
|
||||
|
||||
Reference in New Issue
Block a user