v3.5.3版本更新1
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-08-20 18:54:15 +08:00
parent ee493823bf
commit fc2e5b65de
65 changed files with 2297 additions and 875 deletions
@@ -8,7 +8,9 @@ import {
} from '@dukang/shared-types';
import {
calcLogisticsFeeByBottles,
calcRedeemSettleAmount,
pickPayoutsForWithdrawAmount,
resolveSettlementRate,
sumUnbilledPayoutAmount,
validateStoreWithdraw,
type LogisticsPricingRule,
@@ -96,6 +98,12 @@ export class SettlementService {
settlementRate: number,
tx?: Prisma.TransactionClient,
) {
const redeemAmountNum = Number(redeemAmount);
const payoutAmountNum = Number(payoutAmount);
const settlementRateNum = resolveSettlementRate(settlementRate);
if (!(redeemAmountNum > 0) || !(payoutAmountNum > 0)) {
throw new BadRequestException('结算金额无效,无法入账');
}
const expectedPayAt = new Date();
expectedPayAt.setDate(expectedPayAt.getDate() + 1);
const client = tx ?? this.prisma;
@@ -103,9 +111,9 @@ export class SettlementService {
data: {
redeemRecordId,
storeId,
redeemAmount,
payoutAmount,
settlementRate,
redeemAmount: redeemAmountNum,
payoutAmount: payoutAmountNum,
settlementRate: settlementRateNum,
status: 'PENDING',
expectedPayAt,
},
@@ -152,17 +160,65 @@ export class SettlementService {
}
private async listAvailableUnbilledPayouts(storeId: bigint) {
await this.backfillMissingStorePayouts(storeId);
return this.prisma.storePayout.findMany({
where: {
storeId,
status: 'PENDING',
storeBillId: null,
withdrawItem: null,
withdrawItem: { is: null },
},
orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],
});
}
/**
* 补写历史核销缺失的 store_payout(含测试流水)。
* 线上曾出现核销成功但未入账、可提余额为 0 的情况。
*/
async backfillMissingStorePayouts(storeId: bigint) {
const store = await this.prisma.store.findUnique({
where: { id: storeId },
select: { settlementRate: true },
});
if (!store) return 0;
const orphans = await this.prisma.redeemRecord.findMany({
where: {
storeId,
payout: { is: null },
},
select: { id: true, amount: true, settleAmount: true },
take: 200,
});
if (!orphans.length) return 0;
const settlementRate = resolveSettlementRate(store.settlementRate);
let created = 0;
for (const row of orphans) {
const redeemAmount = Number(row.amount);
let payoutAmount = Number(row.settleAmount);
if (!(redeemAmount > 0)) continue;
if (!(payoutAmount > 0)) {
payoutAmount = calcRedeemSettleAmount(redeemAmount, settlementRate);
}
if (!(payoutAmount > 0)) continue;
try {
await this.createStorePayout(
row.id,
storeId,
redeemAmount,
payoutAmount,
settlementRate,
);
created += 1;
} catch {
// 并发补写时可能已存在 payout
}
}
return created;
}
private async todayWithdrawAppliedAmount(storeId: bigint, now = new Date()) {
const start = startOfDay(now);
const end = new Date(start);
@@ -291,7 +347,7 @@ export class SettlementService {
storeId,
status: 'PENDING',
storeBillId: null,
withdrawItem: null,
withdrawItem: { is: null },
},
select: { id: true, payoutAmount: true },
});
@@ -782,7 +838,7 @@ export class SettlementService {
storeBillId: null,
createdAt: { gte: start, lt: end },
// 排除已锁定在待审提现单中的明细,避免出账与提现双占
withdrawItem: null,
withdrawItem: { is: null },
status: 'PENDING',
},
include: { store: { select: { id: true, settlementRate: true } } },
@@ -1322,12 +1378,9 @@ export class SettlementService {
cityId: primary.cityId,
payStatus: 'PAID',
paidAt: { gte: periodStart, lte: periodEnd },
isTest: false,
},
});
const orderCommission = primary.isTest
? 0
: orders.reduce((sum, o) => {
const orderCommission = orders.reduce((sum, o) => {
if (o.partnerAccountIdAtPay) {
if (o.partnerAccountIdAtPay !== primary.id) return sum;
const rate = o.orderCommissionRateAtPay != null ? Number(o.orderCommissionRateAtPay) : 0;
@@ -1341,14 +1394,12 @@ export class SettlementService {
select: { id: true },
});
const storeIds = stores.map((s) => s.id);
const redeems =
primary.isTest || !storeIds.length
? []
: await this.prisma.redeemRecord.findMany({
const redeems = !storeIds.length
? []
: await this.prisma.redeemRecord.findMany({
where: {
storeId: { in: storeIds },
createdAt: { gte: periodStart, lte: periodEnd },
isTest: false,
},
});
const redeemCommission = redeems.reduce(
@@ -1593,7 +1644,6 @@ export class SettlementService {
payStatus: 'PAID',
deliveryType: { in: ['LOCAL', 'CROSS_CITY'] },
completedAt: { gte: start, lt: end },
isTest: false,
},
orderBy: { completedAt: 'asc' },
});
@@ -1944,7 +1994,6 @@ export class SettlementService {
quantity: true,
deliveryType: true,
payStatus: true,
isTest: true,
},
},
},
@@ -1953,7 +2002,6 @@ export class SettlementService {
const eligible = deliveries.filter(
(d) =>
!d.order.isTest &&
d.order.payStatus === 'PAID' &&
(d.order.deliveryType === 'LOCAL' || d.order.deliveryType === 'CROSS_CITY'),
);