@@ -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(','),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user