feat: link deposits to bill payment

This commit is contained in:
2026-07-14 16:38:38 +08:00
parent eac336a54a
commit 598b4e8acd
12 changed files with 272 additions and 218 deletions

View File

@@ -170,7 +170,7 @@ export class DepositsController {
action: '退还押金',
targetId: +id,
targetType: 'deposit',
detail: `退还¥${result.refundAmount}, 扣除¥${result.deductionAmount}`,
detail: `退还全部可用押金 ¥${result.refundAmount}`,
ipAddress,
userAgent,
});
@@ -182,7 +182,7 @@ export class DepositsController {
recipientIds: [student.userId],
type: 'deposit_refunded',
title: '押金已退还',
content: `您的押金已退还,退还¥${result.refundAmount},扣除¥${result.deductionAmount}`,
content: `您的剩余押金已全部退还,金额: ¥${result.refundAmount}`,
});
}
} catch (_) { /* don't block response */ }

View File

@@ -46,16 +46,28 @@ export class DepositsService {
async create(dto: CreateDepositDto, userId?: number) {
const student = await this.studentRepo.findOne({ where: { id: dto.studentId } });
if (!student) throw new NotFoundException('学生不存在');
const deposit = this.repo.create({
studentId: dto.studentId,
amount: dto.amount,
paidDate: dto.paidDate,
notes: dto.notes,
status: 'paid',
recordedBy: userId,
});
if (Number(dto.amount) <= 0) throw new BadRequestException('收取金额必须大于0');
return this.repo.save(deposit);
const existing = await this.repo.findOne({ where: { studentId: dto.studentId } });
if (existing) {
existing.amount = Number((Number(existing.amount || 0) + Number(dto.amount)).toFixed(2));
existing.paidDate = dto.paidDate;
existing.status = 'paid';
existing.recordedBy = userId ?? null;
if (dto.notes) existing.notes = dto.notes;
return this.repo.save(existing);
}
return this.repo.save(
this.repo.create({
studentId: dto.studentId,
amount: dto.amount,
paidDate: dto.paidDate,
notes: dto.notes,
status: 'paid',
recordedBy: userId,
}),
);
}
async addInstallment(depositId: number, amount: number, dueDate: string) {
@@ -90,18 +102,16 @@ export class DepositsService {
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') throw new BadRequestException('该押金已处理');
if (deposit.status !== 'paid' || Number(deposit.amount) <= 0) {
throw new BadRequestException('该学生当前没有可退押金');
}
const deduction = dto.deductionAmount || 0;
const refundAmount = Number(deposit.amount) - deduction;
if (refundAmount < 0) throw new BadRequestException('扣除金额不能大于押金金额');
const refundAmount = Number(deposit.amount);
deposit.refundDate = dto.refundDate;
deposit.deductionAmount = deduction;
deposit.deductionReason = dto.deductionReason || '';
deposit.refundAmount = refundAmount;
deposit.status =
deduction > 0 ? (refundAmount > 0 ? 'partial_refund' : 'deducted') : 'refunded';
deposit.amount = 0;
deposit.status = 'refunded';
if (dto.notes) deposit.notes = dto.notes;
deposit.refundedBy = userId ?? null;
deposit.refundedAt = new Date();

View File

@@ -19,14 +19,6 @@ export class RefundDepositDto {
@IsString()
refundDate: string;
@IsOptional()
@IsNumber()
deductionAmount?: number;
@IsOptional()
@IsString()
deductionReason?: string;
@IsOptional()
@IsString()
notes?: string;