diff --git a/apps/backend/src/services/finance/order-refund-service.ts b/apps/backend/src/services/finance/order-refund-service.ts index 27dca865..25faeb92 100644 --- a/apps/backend/src/services/finance/order-refund-service.ts +++ b/apps/backend/src/services/finance/order-refund-service.ts @@ -144,6 +144,7 @@ export async function createManualRefund( }) } await assertBasisPeriodOpen(client, order.basis_at, '退款') + await assertRefundMonthOpen(client, '退款', toChinaDate(refundedAt || now)) const itemsResult = await client.query<{ id: number @@ -396,6 +397,7 @@ export async function voidOrderRefund( }) } await assertBasisPeriodOpen(client, row.basis_at, '撤销退款') + await assertRefundMonthOpen(client, '撤销退款') await client.query( ` UPDATE order_refunds @@ -490,6 +492,25 @@ export function mapOrderRefund(row: OrderRefundRow): OrderRefund { } } +async function assertRefundMonthOpen( + client: Pick, + action: string, + date?: string, +) { + const day = date || CHINA_DATE_FORMATTER.format(new Date()) + const periodKey = day.slice(0, 7) + const period = await client.query<{ status: string }>( + `SELECT status FROM finance_periods WHERE period_key = $1 LIMIT 1`, + [periodKey], + ) + if (period.rows[0]?.status === 'closed') { + throw createHttpError(`退款所属期间 ${periodKey} 已关账,请先反关账后再${action}`, { + statusCode: 409, + errorCode: 'order_refund_period_closed', + }) + } +} + async function assertBasisPeriodOpen( client: Pick, basisAt: string, @@ -664,6 +685,19 @@ export async function approveAndRecordKuaishouRefund( } } + // 退款当天所属期间也必须开着,否则退款冲减会静默丢失。 + const refundPeriodKey = CHINA_DATE_FORMATTER.format(new Date()).slice(0, 7) + const refundPeriod = await query<{ status: string }>( + `SELECT status FROM finance_periods WHERE period_key = $1 LIMIT 1`, + [refundPeriodKey], + ) + if (refundPeriod.rows[0]?.status === 'closed') { + throw createHttpError(`退款所属期间 ${refundPeriodKey} 已关账,请先反关账后再退款`, { + statusCode: 409, + errorCode: 'order_refund_period_closed', + }) + } + // 已经登记过的平台退款必须幂等返回,不能因为剩余可退为 0 被误判为超额。 const recorded = await findRecordedPlatformRefund(orderId, platformRefundId) if (recorded) {