v3.5.6版本迭代
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-08-23 16:41:22 +08:00
parent 608e3e5ec5
commit 7e12eb3ca6
33 changed files with 1537 additions and 278 deletions
@@ -22,6 +22,11 @@ import { WecomMessagePushService } from '../../integrations/wecom/wecom-message-
import { AnalyticsService } from '../analytics/analytics.service';
import { PartnerCityService } from '../city-scope/partner-city.service';
import { FulfillmentProviderService } from '../fulfillment/fulfillment-provider.service';
import {
loadStorePrimaryBank,
loadStorePrimaryBanksMap,
loadWineryBankConfig,
} from '../../common/store/store-bank.util';
function generateBillNo(prefix: string) {
return `${prefix}${Date.now()}${Math.floor(Math.random() * 900 + 100)}`;
@@ -1043,11 +1048,20 @@ export class SettlementService {
const total = rows.length;
const slice = rows.slice((page - 1) * pageSize, page * pageSize);
const storeIds = [...new Set(rows.filter((r) => r.kind === 'T1_BILL').map((r) => r.storeId))];
const bankMap = await loadStorePrimaryBanksMap(this.prisma, storeIds);
const redeemAmount = bills.reduce((s, b) => s + Number(b.redeemAmount), 0);
const payoutAmount = rows.reduce((s, r) => s + r.amount, 0);
return serializeBigInt({
items: slice,
items: slice.map((r) => ({
...r,
bankAccount:
r.kind === 'T1_BILL'
? bankMap.get(String(r.storeId)) ?? null
: null,
})),
total,
page,
pageSize,
@@ -1112,10 +1126,11 @@ export class SettlementService {
},
});
if (!bill) throw new NotFoundException('门店对账单不存在');
return serializeBigInt(bill);
const storeAccount = await loadStorePrimaryBank(this.prisma, bill.storeId);
return serializeBigInt({ ...bill, storeAccount });
}
async confirmStoreBill(id: bigint) {
async confirmStoreBill(id: bigint, dto: { paymentRef?: string } = {}) {
const bill = await this.prisma.storeBill.findUnique({ where: { id } });
if (!bill) throw new NotFoundException('门店对账单不存在');
if (bill.status !== 'UNPAID') throw new BadRequestException('仅未打款账单可确认打款');
@@ -1124,7 +1139,11 @@ export class SettlementService {
const updated = await this.prisma.$transaction(async (tx) => {
const b = await tx.storeBill.update({
where: { id },
data: { status: 'PAID', paidAt },
data: {
status: 'PAID',
paidAt,
paymentRef: dto.paymentRef?.trim() || null,
},
});
await tx.storePayout.updateMany({
where: { storeBillId: id, status: 'PENDING' },
@@ -1157,12 +1176,32 @@ export class SettlementService {
const where = this.buildStoreBillWhere(query);
const bills = await this.prisma.storeBill.findMany({
where,
include: { store: { select: { name: true, phone: true, cityName: true } } },
include: { store: { select: { id: true, name: true, phone: true, cityName: true } } },
orderBy: { billDate: 'desc' },
});
const header = ['账单号', '账单日', '门店', '城市', '核销笔数', '核销金额', '结算比例', '应付金额', '状态', '打款时间'].join(',');
const rows = bills.map((b) =>
[
const bankMap = await loadStorePrimaryBanksMap(
this.prisma,
bills.map((b) => b.storeId),
);
const header = [
'账单号',
'账单日',
'门店',
'城市',
'核销笔数',
'核销金额',
'结算比例',
'应付金额',
'状态',
'打款时间',
'打款凭证',
'收款户名',
'收款账号',
'开户行',
].join(',');
const rows = bills.map((b) => {
const bank = bankMap.get(String(b.storeId));
return [
csvEscape(b.billNo),
b.billDate.toISOString().slice(0, 10),
csvEscape(b.store.name),
@@ -1173,8 +1212,12 @@ export class SettlementService {
Number(b.payoutAmount),
b.status,
b.paidAt ? b.paidAt.toISOString().slice(0, 19).replace('T', ' ') : '',
].join(','),
);
csvEscape(b.paymentRef ?? ''),
csvEscape(bank?.bankAccountName ?? ''),
csvEscape(bank?.bankAccountNo ?? ''),
csvEscape(bank?.bankBranch ?? ''),
].join(',');
});
return { csv: `\uFEFF${[header, ...rows].join('\n')}`, count: bills.length };
}
@@ -1262,7 +1305,15 @@ export class SettlementService {
take: pageSize,
include: {
partnerAccount: {
select: { id: true, companyName: true, name: true, phone: true },
select: {
id: true,
companyName: true,
name: true,
phone: true,
bankAccountName: true,
bankAccountNo: true,
bankBranch: true,
},
},
},
}),
@@ -1553,14 +1604,19 @@ export class SettlementService {
return serializeBigInt(updated);
}
async markPartnerBillPaid(id: bigint, _dto: { paymentRef?: string } = {}) {
async markPartnerBillPaid(id: bigint, dto: { paymentRef?: string } = {}) {
const bill = await this.prisma.partnerBill.findUnique({ where: { id } });
if (!bill) throw new NotFoundException('账单不存在');
if (bill.status !== 'UNPAID') throw new BadRequestException('仅未打款账单可标记打款');
const updated = await this.prisma.partnerBill.update({
where: { id },
data: { status: 'PAID', paidAt: new Date(), rejectReason: null },
data: {
status: 'PAID',
paidAt: new Date(),
rejectReason: null,
paymentRef: dto.paymentRef?.trim() || null,
},
});
return serializeBigInt(updated);
@@ -1588,7 +1644,17 @@ export class SettlementService {
const where = this.buildPartnerBillWhere(query);
const bills = await this.prisma.partnerBill.findMany({
where,
include: { partnerAccount: { select: { companyName: true, phone: true } } },
include: {
partnerAccount: {
select: {
companyName: true,
phone: true,
bankAccountName: true,
bankAccountNo: true,
bankBranch: true,
},
},
},
orderBy: { periodStart: 'desc' },
});
@@ -1605,6 +1671,10 @@ export class SettlementService {
'发送时间',
'确认时间',
'打款时间',
'打款凭证',
'收款户名',
'收款账号',
'开户行',
'驳回理由',
].join(',');
const rows = bills.map((b) =>
@@ -1621,6 +1691,10 @@ export class SettlementService {
b.sentAt ? b.sentAt.toISOString().slice(0, 10) : '',
b.confirmedAt ? b.confirmedAt.toISOString().slice(0, 10) : '',
b.paidAt ? b.paidAt.toISOString().slice(0, 10) : '',
csvEscape(b.paymentRef ?? ''),
csvEscape(b.partnerAccount.bankAccountName ?? ''),
csvEscape(b.partnerAccount.bankAccountNo ?? ''),
csvEscape(b.partnerAccount.bankBranch ?? ''),
csvEscape(b.rejectReason ?? ''),
].join(','),
);
@@ -1752,10 +1826,11 @@ export class SettlementService {
include: { items: { orderBy: { paidAt: 'desc' } } },
});
if (!bill) throw new NotFoundException('酒厂对账单不存在');
return serializeBigInt(bill);
const wineryBank = await loadWineryBankConfig(this.prisma);
return serializeBigInt({ ...bill, wineryBank });
}
async confirmWineryBill(id: bigint) {
async confirmWineryBill(id: bigint, dto: { paymentRef?: string } = {}) {
const bill = await this.prisma.wineryBill.findUnique({ where: { id } });
if (!bill) throw new NotFoundException('酒厂对账单不存在');
if (bill.status !== 'UNPAID') throw new BadRequestException('仅未打款账单可确认打款');
@@ -1764,7 +1839,11 @@ export class SettlementService {
}
const updated = await this.prisma.wineryBill.update({
where: { id },
data: { status: 'PAID', paidAt: new Date() },
data: {
status: 'PAID',
paidAt: new Date(),
paymentRef: dto.paymentRef?.trim() || null,
},
});
return serializeBigInt(updated);
}
@@ -1795,6 +1874,7 @@ export class SettlementService {
include: { items: true },
orderBy: { billDate: 'desc' },
});
const wineryBank = await loadWineryBankConfig(this.prisma);
const header = [
'账单号',
@@ -1806,6 +1886,11 @@ export class SettlementService {
'应付',
'支付时间',
'账单状态',
'打款凭证',
'收款户名',
'开户银行',
'开户支行',
'收款账号',
].join(',');
const rows: string[] = [];
const statusLabel = (b: { status: string; wineryAmount: Prisma.Decimal | number }) => {
@@ -1814,6 +1899,13 @@ export class SettlementService {
if (b.status === 'UNPAID') return '未打款';
return b.status;
};
const bankCols = (b: { paymentRef?: string | null }) => [
csvEscape(b.paymentRef ?? ''),
csvEscape(wineryBank.bankAccountName ?? ''),
csvEscape(wineryBank.bankName ?? ''),
csvEscape(wineryBank.bankBranch ?? ''),
csvEscape(wineryBank.bankAccountNo ?? ''),
];
for (const b of bills) {
if (b.items.length === 0) {
rows.push(
@@ -1827,6 +1919,7 @@ export class SettlementService {
Number(b.wineryAmount),
'',
statusLabel(b),
...bankCols(b),
].join(','),
);
continue;
@@ -1843,6 +1936,7 @@ export class SettlementService {
Number(item.wineryAmount),
item.paidAt.toISOString().slice(0, 19).replace('T', ' '),
statusLabel(b),
...bankCols(b),
].join(','),
);
}
@@ -2275,11 +2369,17 @@ export class SettlementService {
});
}
async confirmLogisticsBill(id: bigint) {
async confirmLogisticsBill(id: bigint, dto: { paymentRef?: string } = {}) {
const bill = await this.prisma.logisticsBill.findUnique({ where: { id } });
if (!bill) throw new NotFoundException('物流对账单不存在');
if (bill.status !== 'UNPAID') throw new BadRequestException('仅未结算账单可确认');
const paidData = {
status: 'PAID' as const,
paidAt: new Date(),
paymentRef: dto.paymentRef?.trim() || null,
};
if (bill.settlementMethod === 'PREPAID') {
const deducted = await this.prisma.$transaction(async (tx) => {
const result = await this.fulfillmentProviderService.deductPrepaid(
@@ -2295,7 +2395,7 @@ export class SettlementService {
}
return tx.logisticsBill.update({
where: { id },
data: { status: 'PAID', paidAt: new Date() },
data: paidData,
});
});
return serializeBigInt(deducted);
@@ -2303,7 +2403,7 @@ export class SettlementService {
const updated = await this.prisma.logisticsBill.update({
where: { id },
data: { status: 'PAID', paidAt: new Date() },
data: paidData,
});
return serializeBigInt(updated);
}
@@ -2331,7 +2431,16 @@ export class SettlementService {
const bills = await this.prisma.logisticsBill.findMany({
where,
include: {
fulfillmentProvider: { select: { code: true, name: true } },
fulfillmentProvider: {
select: {
code: true,
name: true,
bankAccountName: true,
bankName: true,
bankBranch: true,
bankAccountNo: true,
},
},
items: true,
},
orderBy: { periodStart: 'desc' },
@@ -2349,15 +2458,28 @@ export class SettlementService {
'物流费',
'发货时间',
'账单状态',
'打款凭证',
'收款户名',
'开户银行',
'开户支行',
'收款账号',
].join(',');
const rows: string[] = [];
for (const b of bills) {
const p = b.fulfillmentProvider;
const bankCols = [
csvEscape(b.paymentRef ?? ''),
csvEscape(p.bankAccountName ?? ''),
csvEscape(p.bankName ?? ''),
csvEscape(p.bankBranch ?? ''),
csvEscape(p.bankAccountNo ?? ''),
];
if (b.items.length === 0) {
rows.push(
[
csvEscape(b.billNo),
csvEscape(b.fulfillmentProvider.code),
csvEscape(b.fulfillmentProvider.name),
csvEscape(p.code),
csvEscape(p.name),
b.periodStart.toISOString().slice(0, 10),
b.periodEnd.toISOString().slice(0, 10),
b.settlementMethod,
@@ -2366,6 +2488,7 @@ export class SettlementService {
Number(b.logisticsAmount),
'',
b.status,
...bankCols,
].join(','),
);
continue;
@@ -2374,8 +2497,8 @@ export class SettlementService {
rows.push(
[
csvEscape(b.billNo),
csvEscape(b.fulfillmentProvider.code),
csvEscape(b.fulfillmentProvider.name),
csvEscape(p.code),
csvEscape(p.name),
b.periodStart.toISOString().slice(0, 10),
b.periodEnd.toISOString().slice(0, 10),
b.settlementMethod,
@@ -2384,6 +2507,7 @@ export class SettlementService {
Number(item.logisticsAmount),
item.shippedAt.toISOString().slice(0, 19).replace('T', ' '),
b.status,
...bankCols,
].join(','),
);
}