Files
gongxue-base/apps/server/src/bills/bills.controller.ts

264 lines
8.3 KiB
TypeScript

import {
Controller,
Get,
Post,
Put,
Delete,
Param,
Body,
Query,
UseGuards,
Request,
Res,
Req,
ParseIntPipe,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In } from 'typeorm';
import { BillsService } from './bills.service';
import { NotificationsService } from '../notifications/notifications.service';
import { NotificationType } from '../entities/notification.entity';
import { Student } from '../entities/student.entity';
import { Bill } from '../entities/bill.entity';
import { BillsExportService } from './bills-export.service';
import { CancelBillDto, GenerateBillsDto, UpdateBillStatusDto } from './dto/bill.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { OperationLogsService } from '../operation-logs/operation-logs.service';
import { extractRequestInfo } from '../common/request-utils';
import { RequirePermission } from '../auth/decorators/permission.decorator';
import type { Response } from 'express';
@UseGuards(JwtAuthGuard)
@Controller('bills')
export class BillsController {
constructor(
private service: BillsService,
private exportService: BillsExportService,
private logService: OperationLogsService,
private readonly notificationsService: NotificationsService,
@InjectRepository(Student) private studentRepo: Repository<Student>,
@InjectRepository(Bill) private billRepo: Repository<Bill>,
) {}
@Post('generate')
@RequirePermission('bill:generate')
async generateBills(@Body() dto: GenerateBillsDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.generateBills(dto);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '账单管理',
action: '生成账单',
detail: `周期 ${result.periodStart}~${result.periodEnd}, 生成 ${result.count}`,
ipAddress,
userAgent,
});
// Send bill_generated notifications
try {
for (const bill of result.bills) {
const student = await this.studentRepo.findOne({ where: { id: bill.studentId } });
if (student?.userId) {
void this.notificationsService.create({
recipientIds: [student.userId],
type: NotificationType.BILL_GENERATED,
title: '新账单',
content: `您有一笔新账单,金额: ¥${bill.totalAmount}, 周期: ${result.periodStart}~${result.periodEnd}`,
});
}
}
} catch (_) { /* don't block response */ }
return result;
}
@Get()
@RequirePermission('bill:view')
findAll(
@Query('periodStart') periodStart?: string,
@Query('periodEnd') periodEnd?: string,
@Query('studentId', new ParseIntPipe({ optional: true })) studentId?: number,
@Query('status') status?: string,
@Query('expenseType') expenseType?: string,
) {
return this.service.findAll({
periodStart, periodEnd,
studentId,
status, expenseType,
});
}
@Get(':id')
@RequirePermission('bill:view')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.service.findOne(id);
}
@Put(':id/status')
@RequirePermission('bill:confirm')
async updateStatus(
@Param('id', ParseIntPipe) id: number,
@Body() dto: UpdateBillStatusDto,
@Request() req: any,
) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.updateStatus(id, dto);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '账单管理',
action: '确认账单',
targetId: id,
targetType: 'bill',
ipAddress,
userAgent,
});
// Send bill_paid notification
try {
const student = await this.studentRepo.findOne({ where: { id: result.studentId } });
if (student?.userId) {
void this.notificationsService.create({
recipientIds: [student.userId],
type: NotificationType.BILL_PAID,
title: '账单已确认',
content: `账单 #${result.id} 已确认收款,金额: ¥${result.totalAmount}`,
});
}
} catch (_) { /* don't block response */ }
return result;
}
@Put('batch/status')
@RequirePermission('bill:confirm')
async batchUpdateStatus(@Body() body: { ids: number[]; status: string }, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.batchUpdateStatus(body.ids, body.status);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '账单管理',
action: '确认账单',
detail: `IDs: ${body.ids.join(',')}`,
ipAddress,
userAgent,
});
// Send bill_paid notifications (batch)
try {
const bills = await this.billRepo.findBy({ id: In(body.ids) });
for (const bill of bills) {
const student = await this.studentRepo.findOne({ where: { id: bill.studentId } });
if (student?.userId) {
void this.notificationsService.create({
recipientIds: [student.userId],
type: NotificationType.BILL_PAID,
title: '账单已确认',
content: `账单 #${bill.id} 已确认收款,金额: ¥${bill.totalAmount}`,
});
}
}
} catch (_) { /* don't block response */ }
return result;
}
@Post(':id/cancel')
@RequirePermission('bill:delete')
async cancel(@Param('id', ParseIntPipe) id: number, @Body() dto: CancelBillDto, @Request() req: any) {
const result = await this.service.cancel(id, dto, req.user?.id);
const { ipAddress, userAgent } = extractRequestInfo(req);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '账单管理',
action: '取消账单并冲正',
targetId: id,
targetType: 'bill',
detail: dto.reason,
ipAddress,
userAgent,
});
return result;
}
@Delete(':id')
@RequirePermission('bill:delete')
async remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.remove(id);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '账单管理',
action: '归档账单',
targetId: id,
targetType: 'bill',
ipAddress,
userAgent,
});
return result;
}
@Post('batch/delete')
@RequirePermission('bill:delete')
async batchRemove(@Body() body: { ids: number[] }, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.batchRemove(body.ids);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '账单管理',
action: '批量归档账单',
detail: `IDs: ${body.ids.join(',')}`,
ipAddress,
userAgent,
});
return result;
}
@Get('export/excel')
@RequirePermission('bill:export-excel')
async exportExcel(
@Query('periodStart') periodStart?: string,
@Query('periodEnd') periodEnd?: string,
@Query('studentId', new ParseIntPipe({ optional: true })) studentId?: number,
@Query('status') status?: string,
@Res() res?: Response,
@Req() req?: any,
) {
const { ipAddress, userAgent } = extractRequestInfo(req);
await this.logService.log({
userId: req?.user?.id,
username: req?.user?.username,
module: '账单管理',
action: '导出账单',
detail: `筛选: 周期${periodStart || '全部'}~${periodEnd || '全部'}, 状态${status || '全部'}`,
ipAddress,
userAgent,
});
return this.exportService.exportExcel(
{
periodStart,
periodEnd,
studentId,
status,
},
res!,
);
}
@Get('export/pdf/:id')
@RequirePermission('bill:export-pdf')
async exportPdf(@Param('id', ParseIntPipe) id: number, @Res() res: Response, @Req() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
await this.logService.log({
userId: req?.user?.id,
username: req?.user?.username,
module: '账单管理',
action: '导出账单',
targetId: id,
targetType: 'bill',
ipAddress,
userAgent,
});
return this.exportService.exportStudentPdf(id, res);
}
}