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

154 lines
5.6 KiB
TypeScript

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<Deposit>,
@InjectRepository(DepositInstallment)
private installmentRepo: Repository<DepositInstallment>,
@InjectRepository(Student)
private studentRepo: Repository<Student>,
) {}
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();
}
}