V4.0.9版本更新合伙人H5端调整
This commit is contained in:
@@ -18,15 +18,20 @@ import {
|
||||
parseShanghaiYmd,
|
||||
pickPayoutsForWithdrawAmount,
|
||||
previousShanghaiMonth,
|
||||
previousShanghaiWeek,
|
||||
resolveSettlementRate,
|
||||
shanghaiBillPeriodYmds,
|
||||
shanghaiMonthLastInstant,
|
||||
shanghaiMonthRange,
|
||||
shanghaiPeriodYmds,
|
||||
shanghaiPeriodYmdsForWeek,
|
||||
shanghaiT1DayWindow,
|
||||
shanghaiWeekRange,
|
||||
shanghaiWineryPeriodWindow,
|
||||
shanghaiYearMonth,
|
||||
shanghaiYmd,
|
||||
startOfShanghaiDay,
|
||||
nextPartnerBillIssueAt,
|
||||
sumUnbilledPayoutAmount,
|
||||
validateStoreWithdraw,
|
||||
wineryIssueDateFromCompletedAt,
|
||||
@@ -80,7 +85,39 @@ function paymentProofUrlsInput(urls?: string[]): Prisma.InputJsonValue | typeof
|
||||
}
|
||||
|
||||
function withShanghaiPeriod<T extends { periodStart: Date; periodEnd: Date }>(row: T) {
|
||||
return { ...row, ...shanghaiPeriodYmds(row.periodStart) };
|
||||
return { ...row, ...shanghaiBillPeriodYmds(row.periodStart, row.periodEnd) };
|
||||
}
|
||||
|
||||
function resolvePartnerWeekPeriod(weekStartYmd: string): {
|
||||
periodStart: Date;
|
||||
periodEnd: Date;
|
||||
endExclusive: Date;
|
||||
} {
|
||||
const ymd = String(weekStartYmd || '').trim();
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(ymd)) {
|
||||
throw new BadRequestException('请提供周起始日 weekStartYmd(YYYY-MM-DD)');
|
||||
}
|
||||
const start = parseShanghaiYmd(ymd);
|
||||
const week = shanghaiWeekRange(start);
|
||||
if (shanghaiYmd(week.start) !== ymd) {
|
||||
throw new BadRequestException('账期起始日须为周一');
|
||||
}
|
||||
return {
|
||||
periodStart: week.start,
|
||||
periodEnd: new Date(week.endExclusive.getTime() - 1),
|
||||
endExclusive: week.endExclusive,
|
||||
};
|
||||
}
|
||||
|
||||
function partnerSettlementCycleDto(anchor = new Date()) {
|
||||
const { start } = shanghaiWeekRange(anchor);
|
||||
const ymds = shanghaiPeriodYmdsForWeek(start);
|
||||
return {
|
||||
periodStart: ymds.periodStart,
|
||||
periodEnd: ymds.periodEnd,
|
||||
nextIssueAt: nextPartnerBillIssueAt(anchor).toISOString(),
|
||||
cycleLabel: '每周一 08:00 出账',
|
||||
};
|
||||
}
|
||||
|
||||
function toPartnerBillItemDto(row: {
|
||||
@@ -1619,6 +1656,7 @@ export class SettlementService implements OnModuleInit {
|
||||
where: {
|
||||
partnerAccountId: primary.id,
|
||||
status: { in: ['AWAITING_CONFIRM', 'UNPAID', 'PAID', 'REJECTED'] },
|
||||
totalAmount: { gt: 0 },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
@@ -1630,6 +1668,58 @@ export class SettlementService implements OnModuleInit {
|
||||
return serializeBigInt(bills.map(withShanghaiPeriod));
|
||||
}
|
||||
|
||||
getPartnerSettlementCycle(anchor = new Date()) {
|
||||
return partnerSettlementCycleDto(anchor);
|
||||
}
|
||||
|
||||
async getPartnerSettlementPreview(partnerAccountId: bigint) {
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const { start } = shanghaiWeekRange();
|
||||
const now = new Date();
|
||||
const periodEnd = now.getTime() < start.getTime() ? start : now;
|
||||
const redeemCommissionRate = Number(primary.redeemCommissionRate ?? 0.03);
|
||||
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: {
|
||||
partnerAccountIdAtPay: primary.id,
|
||||
payStatus: 'PAID',
|
||||
paidAt: { gte: start, lte: periodEnd },
|
||||
},
|
||||
select: { payAmount: true, orderCommissionRateAtPay: true },
|
||||
});
|
||||
const orderCommission = round2(
|
||||
orders.reduce((sum, o) => {
|
||||
const rate = o.orderCommissionRateAtPay != null ? Number(o.orderCommissionRateAtPay) : 0;
|
||||
return sum + Number(o.payAmount) * rate;
|
||||
}, 0),
|
||||
);
|
||||
|
||||
const stores = await this.prisma.store.findMany({
|
||||
where: { partnerAccountId: primary.id },
|
||||
select: { id: true },
|
||||
});
|
||||
const storeIds = stores.map((s) => s.id);
|
||||
const redeems = !storeIds.length
|
||||
? []
|
||||
: await this.prisma.redeemRecord.findMany({
|
||||
where: {
|
||||
storeId: { in: storeIds },
|
||||
createdAt: { gte: start, lte: periodEnd },
|
||||
},
|
||||
select: { amount: true },
|
||||
});
|
||||
const redeemCommission = round2(
|
||||
redeems.reduce((sum, r) => sum + Number(r.amount) * redeemCommissionRate, 0),
|
||||
);
|
||||
|
||||
return {
|
||||
...partnerSettlementCycleDto(),
|
||||
prepaymentAmount: round2(orderCommission + redeemCommission),
|
||||
orderCommissionEstimate: orderCommission,
|
||||
redeemCommissionEstimate: redeemCommission,
|
||||
};
|
||||
}
|
||||
|
||||
async getPartnerBill(partnerAccountId: bigint, billId: bigint) {
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const bill = await this.prisma.partnerBill.findFirst({
|
||||
@@ -1637,6 +1727,7 @@ export class SettlementService implements OnModuleInit {
|
||||
include: { items: { orderBy: { occurredAt: 'asc' } } },
|
||||
});
|
||||
if (!bill) throw new NotFoundException('账单不存在');
|
||||
if (Number(bill.totalAmount) <= 0) throw new NotFoundException('账单不存在');
|
||||
this.analyticsService.trackPartnerOneSafe(partnerAccountId, 'PARTNER_H5', {
|
||||
partnerAccountId: primary.id,
|
||||
eventName: 'partner_bill_detail_view',
|
||||
@@ -1659,6 +1750,7 @@ export class SettlementService implements OnModuleInit {
|
||||
partnerId?: string;
|
||||
year?: number;
|
||||
month?: number;
|
||||
weekStartYmd?: string;
|
||||
}) {
|
||||
const page = query.page ?? 1;
|
||||
const pageSize = query.pageSize ?? 20;
|
||||
@@ -1712,18 +1804,22 @@ export class SettlementService implements OnModuleInit {
|
||||
partnerId?: string;
|
||||
year?: number;
|
||||
month?: number;
|
||||
weekStartYmd?: string;
|
||||
}): Prisma.PartnerBillWhereInput {
|
||||
const where: Prisma.PartnerBillWhereInput = {};
|
||||
if (query.status) where.status = query.status as Prisma.EnumPartnerBillStatusFilter['equals'];
|
||||
if (query.partnerId) where.partnerAccountId = BigInt(query.partnerId);
|
||||
if (query.year && query.month) {
|
||||
if (query.weekStartYmd) {
|
||||
const { periodStart, endExclusive } = resolvePartnerWeekPeriod(query.weekStartYmd);
|
||||
where.periodStart = { gte: periodStart, lt: endExclusive };
|
||||
} else if (query.year && query.month) {
|
||||
const { start, endExclusive } = shanghaiMonthRange(query.year, query.month);
|
||||
where.periodStart = { gte: start, lt: endExclusive };
|
||||
}
|
||||
return where;
|
||||
}
|
||||
|
||||
async generateAllPartnerBills(body: { year: number; month: number }) {
|
||||
async generateAllPartnerBills(body: { weekStartYmd: string }) {
|
||||
const partners = await this.prisma.partnerAccount.findMany({
|
||||
where: { isPrimary: 1, status: 'ACTIVE' },
|
||||
select: {
|
||||
@@ -1752,8 +1848,7 @@ export class SettlementService implements OnModuleInit {
|
||||
const bill = await this.generatePartnerBill(
|
||||
{
|
||||
partnerId: p.id.toString(),
|
||||
year: body.year,
|
||||
month: body.month,
|
||||
weekStartYmd: body.weekStartYmd,
|
||||
},
|
||||
{ notify: false },
|
||||
);
|
||||
@@ -1781,10 +1876,7 @@ export class SettlementService implements OnModuleInit {
|
||||
});
|
||||
}
|
||||
}
|
||||
this.notifyPartnerBillDigest(
|
||||
`${body.year}-${String(body.month).padStart(2, '0')}`,
|
||||
created,
|
||||
);
|
||||
this.notifyPartnerBillDigest(body.weekStartYmd, created);
|
||||
return {
|
||||
total: partners.length,
|
||||
success: results.filter((r) => r.ok).length,
|
||||
@@ -1793,10 +1885,15 @@ export class SettlementService implements OnModuleInit {
|
||||
};
|
||||
}
|
||||
|
||||
/** 每月 1 日任务:生成上一自然月账单 */
|
||||
/** 每周一任务:生成上一自然周账单 */
|
||||
async generatePreviousWeekPartnerBills(anchor = new Date()) {
|
||||
const prev = previousShanghaiWeek(anchor);
|
||||
return this.generateAllPartnerBills({ weekStartYmd: shanghaiYmd(prev.start) });
|
||||
}
|
||||
|
||||
/** @deprecated 月账任务已改为周账 */
|
||||
async generatePreviousMonthPartnerBills(anchor = new Date()) {
|
||||
const prev = previousShanghaiMonth(anchor);
|
||||
return this.generateAllPartnerBills({ year: prev.year, month: prev.month });
|
||||
return this.generatePreviousWeekPartnerBills(anchor);
|
||||
}
|
||||
|
||||
async getAdminPartnerBill(id: bigint) {
|
||||
@@ -1814,13 +1911,12 @@ export class SettlementService implements OnModuleInit {
|
||||
}
|
||||
|
||||
async generatePartnerBill(
|
||||
body: { partnerId: string; year: number; month: number },
|
||||
body: { partnerId: string; weekStartYmd: string },
|
||||
opts?: { notify?: boolean },
|
||||
) {
|
||||
const partnerAccountId = BigInt(body.partnerId);
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const { start: periodStart, endExclusive } = shanghaiMonthRange(body.year, body.month);
|
||||
const periodEnd = shanghaiMonthLastInstant(body.year, body.month);
|
||||
const { periodStart, periodEnd, endExclusive } = resolvePartnerWeekPeriod(body.weekStartYmd);
|
||||
|
||||
const existing = await this.prisma.partnerBill.findFirst({
|
||||
where: {
|
||||
@@ -1829,7 +1925,7 @@ export class SettlementService implements OnModuleInit {
|
||||
},
|
||||
});
|
||||
if (existing && existing.status !== 'PENDING_REVIEW') {
|
||||
throw new BadRequestException('该月账单已进入审核流程,不可重复生成');
|
||||
throw new BadRequestException('该账期账单已进入审核流程,不可重复生成');
|
||||
}
|
||||
|
||||
if (!primary.cityId) {
|
||||
@@ -1952,7 +2048,7 @@ export class SettlementService implements OnModuleInit {
|
||||
});
|
||||
cityName = city?.name?.trim() || '—';
|
||||
}
|
||||
this.notifyPartnerBillDigest(`${body.year}-${String(body.month).padStart(2, '0')}`, [
|
||||
this.notifyPartnerBillDigest(body.weekStartYmd, [
|
||||
{
|
||||
cityName,
|
||||
partnerLabel: formatPartnerDashLabel(primary.companyName, primary.name),
|
||||
@@ -1981,6 +2077,9 @@ export class SettlementService implements OnModuleInit {
|
||||
async sendPartnerBill(id: bigint) {
|
||||
const bill = await this.prisma.partnerBill.findUnique({ where: { id } });
|
||||
if (!bill) throw new NotFoundException('账单不存在');
|
||||
if (Number(bill.totalAmount) <= 0) {
|
||||
throw new BadRequestException('零元账单不同步给合伙人');
|
||||
}
|
||||
if (bill.status !== 'PENDING_REVIEW' && bill.status !== 'REJECTED') {
|
||||
throw new BadRequestException('仅待审核或已驳回账单可发送');
|
||||
}
|
||||
@@ -2129,6 +2228,7 @@ export class SettlementService implements OnModuleInit {
|
||||
status?: string;
|
||||
year?: number;
|
||||
month?: number;
|
||||
weekStartYmd?: string;
|
||||
}) {
|
||||
const where = this.buildPartnerBillWhere(query);
|
||||
const bills = await this.prisma.partnerBill.findMany({
|
||||
@@ -2172,8 +2272,8 @@ export class SettlementService implements OnModuleInit {
|
||||
csvEscape(b.billNo),
|
||||
csvEscape(b.partnerAccount.companyName ?? ''),
|
||||
csvEscape(b.partnerAccount.phone ?? ''),
|
||||
shanghaiPeriodYmds(b.periodStart).periodStart,
|
||||
shanghaiPeriodYmds(b.periodStart).periodEnd,
|
||||
shanghaiBillPeriodYmds(b.periodStart, b.periodEnd).periodStart,
|
||||
shanghaiBillPeriodYmds(b.periodStart, b.periodEnd).periodEnd,
|
||||
Number(b.orderCommission),
|
||||
Number(b.redeemCommission),
|
||||
Number(b.totalAmount),
|
||||
|
||||
Reference in New Issue
Block a user