import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Deposit } from '../entities/deposit.entity'; import { Student } from '../entities/student.entity'; import { DepositInstallment } from '../entities/deposit-installment.entity'; import { CreateDepositDto, RefundDepositDto } from './dto/deposit.dto'; const money = (value: number | string | null | undefined) => Number(Number(value || 0).toFixed(2)); @Injectable() export class DepositsService { constructor( @InjectRepository(Deposit) private repo: Repository, @InjectRepository(DepositInstallment) private installmentRepo: Repository, @InjectRepository(Student) private studentRepo: Repository, ) {} async getStudentLookups() { return this.studentRepo.find({ select: ['id', 'name', 'studentNo'], where: { status: 'active' }, order: { name: 'ASC' }, }); } async findAll(query?: { studentId?: number; status?: string }) { const qb = this.repo .createQueryBuilder('d') .leftJoinAndSelect('d.student', 'student') .leftJoinAndSelect('d.installments', 'installments') .orderBy('d.createdAt', 'DESC'); if (query?.studentId) qb.andWhere('d.studentId = :studentId', { studentId: query.studentId }); if (query?.status) qb.andWhere('d.status = :status', { status: query.status }); return qb.getMany(); } async findOne(id: number) { const deposit = await this.repo.findOne({ where: { id }, relations: ['student', 'installments'] }); if (!deposit) throw new NotFoundException('押金记录不存在'); return deposit; } async create(dto: CreateDepositDto, userId?: number) { const student = await this.studentRepo.findOne({ where: { id: dto.studentId } }); if (!student) throw new NotFoundException('学生不存在'); const amount = money(dto.amount); if (!Number.isFinite(dto.amount) || Math.abs(dto.amount * 100 - Math.round(dto.amount * 100)) > 1e-8) { throw new BadRequestException('收取金额最多保留两位小数'); } if (amount <= 0) throw new BadRequestException('收取金额必须大于0'); const existing = await this.repo.findOne({ where: { studentId: dto.studentId } }); if (existing) { existing.amount = money(Number(existing.amount || 0) + amount); existing.paidDate = dto.paidDate; existing.status = 'paid'; existing.recordedBy = userId ?? null; existing.refundDate = null as unknown as string; existing.refundAmount = null as unknown as number; existing.refundedBy = null; existing.refundedAt = null; if (dto.notes) existing.notes = dto.notes; return this.repo.save(existing); } return this.repo.save( this.repo.create({ studentId: dto.studentId, amount, paidDate: dto.paidDate, notes: dto.notes, status: 'paid', recordedBy: userId, }), ); } async addInstallment(depositId: number, amount: number, dueDate: string) { const normalizedAmount = money(amount); if (!Number.isFinite(amount) || Math.abs(amount * 100 - Math.round(amount * 100)) > 1e-8) { throw new BadRequestException('分期金额最多保留两位小数'); } if (normalizedAmount <= 0) throw new BadRequestException('分期金额必须大于0'); const deposit = await this.repo.findOne({ where: { id: depositId } }); if (!deposit) throw new NotFoundException('押金记录不存在'); const installment = this.installmentRepo.create({ depositId, amount: normalizedAmount, dueDate, status: 'pending', }); return this.installmentRepo.save(installment); } async updateInstallment(id: number, data: { paidDate?: string; status?: string }) { const installment = await this.installmentRepo.findOne({ where: { id } }); if (!installment) throw new NotFoundException('分期记录不存在'); if (data.paidDate !== undefined) installment.paidDate = data.paidDate; if (data.status !== undefined) installment.status = data.status; return this.installmentRepo.save(installment); } async deleteInstallment(id: number) { const installment = await this.installmentRepo.findOne({ where: { id } }); if (!installment) throw new NotFoundException('分期记录不存在'); await this.installmentRepo.delete(id); return { message: '删除成功' }; } async refund(id: number, dto: RefundDepositDto, userId?: number) { const deposit = await this.repo.findOne({ where: { id } }); if (!deposit) throw new NotFoundException('押金记录不存在'); if (deposit.status !== 'paid' || Number(deposit.amount) <= 0) { throw new BadRequestException('该学生当前没有可退押金'); } const refundAmount = money(deposit.amount); deposit.refundDate = dto.refundDate; deposit.refundAmount = refundAmount; deposit.amount = 0; deposit.status = 'refunded'; if (dto.notes) deposit.notes = dto.notes; deposit.refundedBy = userId ?? null; deposit.refundedAt = new Date(); return this.repo.save(deposit); } async remove(id: number) { const deposit = await this.repo.findOne({ where: { id } }); if (!deposit) throw new NotFoundException('押金记录不存在'); await this.repo.delete(id); return { message: '删除成功' }; } async getStats() { const qb = this.repo .createQueryBuilder('d') .select('d.status', 'status') .addSelect('COUNT(*)', 'count') .addSelect('SUM(d.amount)', 'totalAmount'); qb.groupBy('d.status'); return qb.getRawMany(); } }