fix: 修复批量更新账单状态功能

This commit is contained in:
2026-07-14 14:24:19 +08:00
parent 05a936bbc2
commit ce5fd1c6cb
2 changed files with 60 additions and 46 deletions

View File

@@ -7,6 +7,7 @@ import {
Param, Param,
Body, Body,
Query, Query,
ParseIntPipe,
UseGuards, UseGuards,
Request, Request,
Res, Res,
@@ -20,7 +21,11 @@ import { NotificationType } from '../entities/notification.entity';
import { Student } from '../entities/student.entity'; import { Student } from '../entities/student.entity';
import { Bill } from '../entities/bill.entity'; import { Bill } from '../entities/bill.entity';
import { BillsExportService } from './bills-export.service'; import { BillsExportService } from './bills-export.service';
import { GenerateBillsDto, UpdateBillStatusDto } from './dto/bill.dto'; import {
BatchUpdateBillStatusDto,
GenerateBillsDto,
UpdateBillStatusDto,
} from './dto/bill.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { OperationLogsService } from '../operation-logs/operation-logs.service'; import { OperationLogsService } from '../operation-logs/operation-logs.service';
import { extractRequestInfo } from '../common/request-utils'; import { extractRequestInfo } from '../common/request-utils';
@@ -88,47 +93,15 @@ export class BillsController {
@Get(':id') @Get(':id')
@RequirePermission('bill:view') @RequirePermission('bill:view')
findOne(@Param('id') id: string) { findOne(@Param('id', ParseIntPipe) id: number) {
return this.service.findOne(+id); 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;
} }
// Static routes must be declared before /:id/status, otherwise "batch" is
// treated as an id and converted to NaN by the parameterized route.
@Put('batch/status') @Put('batch/status')
@RequirePermission('bill:confirm') @RequirePermission('bill:confirm')
async batchUpdateStatus(@Body() body: { ids: number[]; status: string }, @Request() req: any) { async batchUpdateStatus(@Body() body: BatchUpdateBillStatusDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req); const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.batchUpdateStatus(body.ids, body.status); const result = await this.service.batchUpdateStatus(body.ids, body.status);
await this.logService.log({ await this.logService.log({
@@ -158,17 +131,51 @@ export class BillsController {
return result; return result;
} }
@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;
}
@Delete(':id') @Delete(':id')
@RequirePermission('bill:delete') @RequirePermission('bill:delete')
async remove(@Param('id') id: string, @Request() req: any) { async remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req); const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.remove(+id); const result = await this.service.remove(id);
await this.logService.log({ await this.logService.log({
userId: req.user?.id, userId: req.user?.id,
username: req.user?.username, username: req.user?.username,
module: '账单管理', module: '账单管理',
action: '删除账单', action: '删除账单',
targetId: +id, targetId: id,
targetType: 'bill', targetType: 'bill',
ipAddress, ipAddress,
userAgent, userAgent,
@@ -226,18 +233,18 @@ export class BillsController {
@Get('export/pdf/:id') @Get('export/pdf/:id')
@RequirePermission('bill:export-pdf') @RequirePermission('bill:export-pdf')
async exportPdf(@Param('id') id: string, @Res() res: Response, @Req() req: any) { async exportPdf(@Param('id', ParseIntPipe) id: number, @Res() res: Response, @Req() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req); const { ipAddress, userAgent } = extractRequestInfo(req);
await this.logService.log({ await this.logService.log({
userId: req?.user?.id, userId: req?.user?.id,
username: req?.user?.username, username: req?.user?.username,
module: '账单管理', module: '账单管理',
action: '导出账单', action: '导出账单',
targetId: +id, targetId: id,
targetType: 'bill', targetType: 'bill',
ipAddress, ipAddress,
userAgent, userAgent,
}); });
return this.exportService.exportStudentPdf(+id, res); return this.exportService.exportStudentPdf(id, res);
} }
} }

View File

@@ -1,4 +1,4 @@
import { IsString, IsOptional } from 'class-validator'; import { ArrayNotEmpty, IsArray, IsIn, IsInt, IsString } from 'class-validator';
export class GenerateBillsDto { export class GenerateBillsDto {
@IsString() @IsString()
@@ -9,6 +9,13 @@ export class GenerateBillsDto {
} }
export class UpdateBillStatusDto { export class UpdateBillStatusDto {
@IsString() @IsIn(['draft', 'confirmed', 'paid'])
status: 'draft' | 'confirmed' | 'paid'; status: 'draft' | 'confirmed' | 'paid';
} }
export class BatchUpdateBillStatusDto extends UpdateBillStatusDto {
@IsArray()
@ArrayNotEmpty()
@IsInt({ each: true })
ids: number[];
}