chore: commit oxfmt formatting changes and verify artifacts
This commit is contained in:
@@ -19,8 +19,12 @@ export class BillsExportService {
|
||||
/**
|
||||
* 导出账单列表为 Excel
|
||||
*/
|
||||
async exportExcel(query: { periodStart?: string; periodEnd?: string; studentId?: number; status?: string }, res: Response) {
|
||||
const qb = this.billRepo.createQueryBuilder('b')
|
||||
async exportExcel(
|
||||
query: { periodStart?: string; periodEnd?: string; studentId?: number; status?: string },
|
||||
res: Response,
|
||||
) {
|
||||
const qb = this.billRepo
|
||||
.createQueryBuilder('b')
|
||||
.leftJoinAndSelect('b.student', 'student')
|
||||
.leftJoinAndSelect('b.items', 'items')
|
||||
.orderBy('b.generatedAt', 'DESC');
|
||||
@@ -34,7 +38,8 @@ export class BillsExportService {
|
||||
const studentIds = Array.from(new Set(bills.map((b) => b.studentId)));
|
||||
const depMap = new Map<number, number>();
|
||||
if (studentIds.length > 0) {
|
||||
const deposits = await this.depositRepo.createQueryBuilder('d')
|
||||
const deposits = await this.depositRepo
|
||||
.createQueryBuilder('d')
|
||||
.where('d.studentId IN (:...ids)', { ids: studentIds })
|
||||
.andWhere('d.status = :status', { status: 'paid' })
|
||||
.getMany();
|
||||
@@ -65,7 +70,11 @@ export class BillsExportService {
|
||||
ws.getRow(1).font = { bold: true };
|
||||
ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } };
|
||||
|
||||
const statusMap: Record<string, string> = { draft: '草稿', confirmed: '已确认', paid: '已结清' };
|
||||
const statusMap: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
confirmed: '已确认',
|
||||
paid: '已结清',
|
||||
};
|
||||
for (const bill of bills) {
|
||||
const total = Number(bill.totalAmount || 0);
|
||||
const dep = Number((depMap.get(bill.studentId) || 0).toFixed(2));
|
||||
@@ -116,7 +125,10 @@ export class BillsExportService {
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
);
|
||||
res.setHeader('Content-Disposition', `attachment; filename=bills_${Date.now()}.xlsx`);
|
||||
await workbook.xlsx.write(res);
|
||||
res.end();
|
||||
@@ -126,11 +138,18 @@ export class BillsExportService {
|
||||
* 导出单个学生的 PDF 账单
|
||||
*/
|
||||
async exportStudentPdf(billId: number, res: Response) {
|
||||
const bill = await this.billRepo.findOne({ where: { id: billId }, relations: ['student', 'items'] });
|
||||
if (!bill) { res.status(404).json({ message: '账单不存在' }); return; }
|
||||
const bill = await this.billRepo.findOne({
|
||||
where: { id: billId },
|
||||
relations: ['student', 'items'],
|
||||
});
|
||||
if (!bill) {
|
||||
res.status(404).json({ message: '账单不存在' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询该学生的可用押金(已缴未退)
|
||||
const deposits = await this.depositRepo.createQueryBuilder('d')
|
||||
const deposits = await this.depositRepo
|
||||
.createQueryBuilder('d')
|
||||
.where('d.studentId = :sid', { sid: bill.studentId })
|
||||
.andWhere('d.status = :status', { status: 'paid' })
|
||||
.getMany();
|
||||
@@ -146,12 +165,12 @@ export class BillsExportService {
|
||||
|
||||
// 注册中文字体(优先使用系统字体,兼容 macOS 和 Linux)
|
||||
const fontPaths = [
|
||||
'/System/Library/Fonts/PingFang.ttc', // macOS
|
||||
'/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', // Linux Noto
|
||||
'/System/Library/Fonts/PingFang.ttc', // macOS
|
||||
'/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', // Linux Noto
|
||||
'/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc',
|
||||
'/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc',
|
||||
'/usr/share/fonts/noto-cjk/NotoSansSC-Regular.otf',
|
||||
'/usr/share/fonts/wqy-microhei/wqy-microhei.ttc', // Linux WenQuanYi
|
||||
'/usr/share/fonts/wqy-microhei/wqy-microhei.ttc', // Linux WenQuanYi
|
||||
'/usr/share/fonts/truetype/wqy/wqy-microhei.ttc',
|
||||
];
|
||||
let fontRegistered = false;
|
||||
@@ -171,12 +190,19 @@ export class BillsExportService {
|
||||
doc.font('Helvetica');
|
||||
}
|
||||
|
||||
const statusMap: Record<string, string> = { draft: '草稿', confirmed: '已确认', paid: '已结清' };
|
||||
const statusMap: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
confirmed: '已确认',
|
||||
paid: '已结清',
|
||||
};
|
||||
|
||||
// 标题
|
||||
doc.fontSize(20).text('恭学教育基地水电费账单', { align: 'center' });
|
||||
doc.moveDown(0.5);
|
||||
doc.fontSize(10).fillColor('#666').text(`生成时间: ${new Date().toLocaleString('zh-CN')}`, { align: 'center' });
|
||||
doc
|
||||
.fontSize(10)
|
||||
.fillColor('#666')
|
||||
.text(`生成时间: ${new Date().toLocaleString('zh-CN')}`, { align: 'center' });
|
||||
doc.moveDown(1);
|
||||
|
||||
// 基本信息
|
||||
@@ -192,12 +218,24 @@ export class BillsExportService {
|
||||
doc.fontSize(12);
|
||||
doc.text(`分摊费用: ¥${Number(bill.sharedAmount).toFixed(2)}`);
|
||||
doc.text(`个人费用: ¥${Number(bill.personalAmount).toFixed(2)}`);
|
||||
doc.fontSize(14).fillColor('#007AFF').text(`应付总额: ¥${totalAmount.toFixed(2)}`);
|
||||
doc
|
||||
.fontSize(14)
|
||||
.fillColor('#007AFF')
|
||||
.text(`应付总额: ¥${totalAmount.toFixed(2)}`);
|
||||
doc.moveDown(0.3);
|
||||
if (availableDeposit > 0) {
|
||||
doc.fontSize(11).fillColor('#52C41A').text(`可用押金: ¥${availableDeposit.toFixed(2)}`);
|
||||
doc.fontSize(11).fillColor('#FA8C16').text(`押金抵扣: -¥${depositApplied.toFixed(2)}`);
|
||||
doc.fontSize(14).fillColor('#FF3B30').text(`抵扣后应付: ¥${amountAfterDeposit.toFixed(2)}`);
|
||||
doc
|
||||
.fontSize(11)
|
||||
.fillColor('#52C41A')
|
||||
.text(`可用押金: ¥${availableDeposit.toFixed(2)}`);
|
||||
doc
|
||||
.fontSize(11)
|
||||
.fillColor('#FA8C16')
|
||||
.text(`押金抵扣: -¥${depositApplied.toFixed(2)}`);
|
||||
doc
|
||||
.fontSize(14)
|
||||
.fillColor('#FF3B30')
|
||||
.text(`抵扣后应付: ¥${amountAfterDeposit.toFixed(2)}`);
|
||||
}
|
||||
doc.moveDown(1);
|
||||
|
||||
@@ -226,16 +264,23 @@ export class BillsExportService {
|
||||
const y = doc.y;
|
||||
x = 50;
|
||||
doc.fontSize(9).fillColor('#000');
|
||||
doc.text(item.expenseType || '', x, y, { width: colWidths[0] }); x += colWidths[0];
|
||||
doc.text(item.description || '', x, y, { width: colWidths[1] }); x += colWidths[1];
|
||||
doc.text(String(item.days || 0), x, y, { width: colWidths[2] }); x += colWidths[2];
|
||||
doc.text(String(item.totalRoomDays || 0), x, y, { width: colWidths[3] }); x += colWidths[3];
|
||||
doc.text(item.expenseType || '', x, y, { width: colWidths[0] });
|
||||
x += colWidths[0];
|
||||
doc.text(item.description || '', x, y, { width: colWidths[1] });
|
||||
x += colWidths[1];
|
||||
doc.text(String(item.days || 0), x, y, { width: colWidths[2] });
|
||||
x += colWidths[2];
|
||||
doc.text(String(item.totalRoomDays || 0), x, y, { width: colWidths[3] });
|
||||
x += colWidths[3];
|
||||
doc.text(Number(item.studentAmount).toFixed(2), x, y, { width: colWidths[4] });
|
||||
doc.moveDown(0.8);
|
||||
}
|
||||
|
||||
doc.moveDown(2);
|
||||
doc.fontSize(8).fillColor('#999').text('本账单由恭学教育基地管理系统自动生成', { align: 'center' });
|
||||
doc
|
||||
.fontSize(8)
|
||||
.fillColor('#999')
|
||||
.text('本账单由恭学教育基地管理系统自动生成', { align: 'center' });
|
||||
|
||||
doc.end();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user