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,
Body,
Query,
ParseIntPipe,
UseGuards,
Request,
Res,
@@ -20,7 +21,11 @@ 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 {
BatchUpdateBillStatusDto,
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';
@@ -88,47 +93,15 @@ export class BillsController {
@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;
findOne(@Param('id', ParseIntPipe) id: number) {
return this.service.findOne(id);
}
// 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')
@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 result = await this.service.batchUpdateStatus(body.ids, body.status);
await this.logService.log({
@@ -158,17 +131,51 @@ export class BillsController {
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')
@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 result = await this.service.remove(+id);
const result = await this.service.remove(id);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '账单管理',
action: '删除账单',
targetId: +id,
targetId: id,
targetType: 'bill',
ipAddress,
userAgent,
@@ -226,18 +233,18 @@ export class BillsController {
@Get('export/pdf/:id')
@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);
await this.logService.log({
userId: req?.user?.id,
username: req?.user?.username,
module: '账单管理',
action: '导出账单',
targetId: +id,
targetId: id,
targetType: 'bill',
ipAddress,
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 {
@IsString()
@@ -9,6 +9,13 @@ export class GenerateBillsDto {
}
export class UpdateBillStatusDto {
@IsString()
@IsIn(['draft', 'confirmed', 'paid'])
status: 'draft' | 'confirmed' | 'paid';
}
export class BatchUpdateBillStatusDto extends UpdateBillStatusDto {
@IsArray()
@ArrayNotEmpty()
@IsInt({ each: true })
ids: number[];
}