import { Controller, Get, Post, Put, Delete, Param, Body, Query, UseGuards, Request, Res, Req, } 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 { 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, @InjectRepository(Bill) private billRepo: Repository, ) {} @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: `周期 ${dto.periodStart}~${dto.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}, 周期: ${dto.periodStart}~${dto.periodEnd}`, }); } } } catch (_) { /* don't block response */ } return result; } @Get() @RequirePermission('bill:view') findAll( @Query('periodStart') periodStart?: string, @Query('periodEnd') periodEnd?: string, @Query('studentId') studentId?: string, @Query('status') status?: string, ) { return this.service.findAll({ periodStart, periodEnd, studentId: studentId ? +studentId : undefined, status, }); } @Get(':id') @RequirePermission('bill:view') findOne(@Param('id') id: string) { return this.service.findOne(+id); } @Put(':id/status') @RequirePermission('bill:confirm') async updateStatus( @Param('id') id: string, @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; } @Delete(':id') @RequirePermission('bill:delete') async remove(@Param('id') id: string, @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') studentId?: string, @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: studentId ? +studentId : undefined, status, }, res!, ); } @Get('export/pdf/:id') @RequirePermission('bill:export-pdf') async exportPdf(@Param('id') id: 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: '导出账单', targetId: +id, targetType: 'bill', ipAddress, userAgent, }); return this.exportService.exportStudentPdf(+id, res); } }