fix: 修复批量更新账单状态功能
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user