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

219 lines
6.8 KiB
TypeScript

import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
Query,
UseGuards,
Request,
ParseIntPipe,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Student } from '../entities/student.entity';
import { DepositsService } from './deposits.service';
import { NotificationsService } from '../notifications/notifications.service';
import { NotificationType } from '../entities/notification.entity';
import {
CreateDepositDto,
CreateDepositInstallmentDto,
RefundDepositDto,
UpdateDepositInstallmentDto,
} from './dto/deposit.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';
@UseGuards(JwtAuthGuard)
@Controller('deposits')
export class DepositsController {
constructor(
private service: DepositsService,
private logService: OperationLogsService,
private readonly notificationsService: NotificationsService,
@InjectRepository(Student) private studentRepo: Repository<Student>,
) {}
@Get('student-lookups')
@RequirePermission('deposit:create')
getStudentLookups() {
return this.service.getStudentLookups();
}
@Get()
@RequirePermission('deposit:view')
findAll(
@Query('studentId', new ParseIntPipe({ optional: true })) studentId?: number,
@Query('status') status?: string,
) {
return this.service.findAll({
studentId,
status: status || undefined,
});
}
@Get('stats')
@RequirePermission('deposit:view')
getStats() {
return this.service.getStats();
}
@Get(':id')
@RequirePermission('deposit:view')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.service.findOne(id);
}
@Post()
@RequirePermission('deposit:create')
async create(@Body() dto: CreateDepositDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.create(dto, req.user?.id);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '押金管理',
action: '收取押金',
targetId: result.id,
targetType: 'deposit',
detail: `学生${dto.studentId} ¥${dto.amount}`,
ipAddress,
userAgent,
});
// Send deposit_due notification
try {
const student = await this.studentRepo.findOne({ where: { id: dto.studentId } });
if (student?.userId) {
void this.notificationsService.create({
recipientIds: [student.userId],
type: 'deposit_due',
title: '押金待缴',
content: `您有一笔押金待缴纳,金额: ¥${dto.amount}`,
});
}
} catch (_) { /* don't block response */ }
return result;
}
@Post(':id/installments')
@RequirePermission('deposit:edit')
async addInstallment(
@Param('id', ParseIntPipe) id: number,
@Body() body: CreateDepositInstallmentDto,
@Request() req: { user?: { id: number; username: string }; headers?: Record<string, string> },
) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.addInstallment(id, body.amount, body.dueDate);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '押金管理',
action: '新增分期',
targetId: result.id,
targetType: 'deposit-installment',
detail: `押金${id} 新增分期 ¥${result.amount}`,
ipAddress,
userAgent,
});
return result;
}
@Put('installments/:installmentId')
@RequirePermission('deposit:edit')
async updateInstallment(
@Param('installmentId', ParseIntPipe) installmentId: number,
@Body() body: UpdateDepositInstallmentDto,
@Request() req: { user?: { id: number; username: string }; headers?: Record<string, string> },
) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.updateInstallment(installmentId, body);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '押金管理',
action: '更新分期',
targetId: installmentId,
targetType: 'deposit-installment',
detail: `更新分期${installmentId}, 状态:${result.status ?? '-'}, 实付日:${result.paidDate ?? '-'}`,
ipAddress,
userAgent,
});
return result;
}
@Delete('installments/:installmentId')
@RequirePermission('deposit:delete')
async deleteInstallment(
@Param('installmentId', ParseIntPipe) installmentId: number,
@Request() req: { user?: { id: number; username: string }; headers?: Record<string, string> },
) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.deleteInstallment(installmentId);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '押金管理',
action: '删除分期',
targetId: installmentId,
targetType: 'deposit-installment',
detail: `删除分期${installmentId}`,
ipAddress,
userAgent,
});
return result;
}
@Put(':id/refund')
@RequirePermission('deposit:refund')
async refund(@Param('id', ParseIntPipe) id: number, @Body() dto: RefundDepositDto, @Request() req: any) {
const { ipAddress, userAgent } = extractRequestInfo(req);
const result = await this.service.refund(id, dto, req.user?.id);
await this.logService.log({
userId: req.user?.id,
username: req.user?.username,
module: '押金管理',
action: '退还押金',
targetId: id,
targetType: 'deposit',
detail: `退还全部可用押金 ¥${result.refundAmount}`,
ipAddress,
userAgent,
});
// Send deposit_refunded notification
try {
const student = await this.studentRepo.findOne({ where: { id: result.studentId } });
if (student?.userId) {
void this.notificationsService.create({
recipientIds: [student.userId],
type: 'deposit_refunded',
title: '押金已退还',
content: `您的剩余押金已全部退还,金额: ¥${result.refundAmount}`,
});
}
} catch (_) { /* don't block response */ }
return result;
}
@Delete(':id')
@RequirePermission('deposit: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: 'deposit',
ipAddress,
userAgent,
});
return result;
}
}