forked from wangziqi/gongxue-base
feat: link deposits to bill payment
This commit is contained in:
@@ -34,7 +34,7 @@ export class BillsExportService {
|
||||
if (query.status) qb.andWhere('b.status = :status', { status: query.status });
|
||||
const bills = await qb.getMany();
|
||||
|
||||
// 查询涉及学生的"已缴未退"押金,用于导出押金抵扣字段
|
||||
// 查询涉及学生当前押金余额。账单生成时不冻结押金,导出只展示实时余额和已实际扣款。
|
||||
const studentIds = Array.from(new Set(bills.map((b) => b.studentId)));
|
||||
const depMap = new Map<number, number>();
|
||||
if (studentIds.length > 0) {
|
||||
@@ -61,8 +61,7 @@ export class BillsExportService {
|
||||
{ header: '个人费用', key: 'personal', width: 12 },
|
||||
{ header: '总金额', key: 'total', width: 12 },
|
||||
{ header: '可用押金', key: 'deposit', width: 12 },
|
||||
{ header: '押金抵扣', key: 'depositApplied', width: 12 },
|
||||
{ header: '抵扣后应付', key: 'afterDeposit', width: 14 },
|
||||
{ header: '已扣押金', key: 'depositDeducted', width: 12 },
|
||||
{ header: '状态', key: 'status', width: 10 },
|
||||
{ header: '生成时间', key: 'generatedAt', width: 20 },
|
||||
];
|
||||
@@ -72,14 +71,11 @@ export class BillsExportService {
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
confirmed: '已确认',
|
||||
paid: '已结清',
|
||||
};
|
||||
for (const bill of bills) {
|
||||
const total = Number(bill.totalAmount || 0);
|
||||
const dep = Number((depMap.get(bill.studentId) || 0).toFixed(2));
|
||||
const applied = Number(Math.min(dep, total).toFixed(2));
|
||||
const after = Number(Math.max(0, total - applied).toFixed(2));
|
||||
ws.addRow({
|
||||
id: bill.id,
|
||||
studentName: (bill as any).student?.name || '-',
|
||||
@@ -88,8 +84,7 @@ export class BillsExportService {
|
||||
personal: Number(bill.personalAmount),
|
||||
total,
|
||||
deposit: dep,
|
||||
depositApplied: applied,
|
||||
afterDeposit: after,
|
||||
depositDeducted: Number(bill.depositDeductedAmount || 0),
|
||||
status: statusMap[bill.status] || bill.status,
|
||||
generatedAt: bill.generatedAt ? new Date(bill.generatedAt).toLocaleString('zh-CN') : '',
|
||||
});
|
||||
@@ -147,7 +142,7 @@ export class BillsExportService {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询该学生的可用押金(已缴未退)
|
||||
// 查询该学生的当前可用押金。草稿账单只展示余额,不预生成抵扣金额。
|
||||
const deposits = await this.depositRepo
|
||||
.createQueryBuilder('d')
|
||||
.where('d.studentId = :sid', { sid: bill.studentId })
|
||||
@@ -155,8 +150,7 @@ export class BillsExportService {
|
||||
.getMany();
|
||||
const availableDeposit = deposits.reduce((s, d) => s + Number(d.amount || 0), 0);
|
||||
const totalAmount = Number(bill.totalAmount || 0);
|
||||
const depositApplied = Math.min(availableDeposit, totalAmount);
|
||||
const amountAfterDeposit = Math.max(0, totalAmount - depositApplied);
|
||||
const depositDeducted = Number(bill.depositDeductedAmount || 0);
|
||||
|
||||
const doc = new PDFDocument({ size: 'A4', margin: 50 });
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
@@ -192,7 +186,6 @@ export class BillsExportService {
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
confirmed: '已确认',
|
||||
paid: '已结清',
|
||||
};
|
||||
|
||||
@@ -223,19 +216,17 @@ export class BillsExportService {
|
||||
.fillColor('#007AFF')
|
||||
.text(`应付总额: ¥${totalAmount.toFixed(2)}`);
|
||||
doc.moveDown(0.3);
|
||||
if (availableDeposit > 0) {
|
||||
if (availableDeposit > 0 || depositDeducted > 0) {
|
||||
doc
|
||||
.fontSize(11)
|
||||
.fillColor('#52C41A')
|
||||
.text(`可用押金: ¥${availableDeposit.toFixed(2)}`);
|
||||
doc
|
||||
.fontSize(11)
|
||||
.fillColor('#FA8C16')
|
||||
.text(`押金抵扣: -¥${depositApplied.toFixed(2)}`);
|
||||
doc
|
||||
.fontSize(14)
|
||||
.fillColor('#FF3B30')
|
||||
.text(`抵扣后应付: ¥${amountAfterDeposit.toFixed(2)}`);
|
||||
if (depositDeducted > 0) {
|
||||
doc
|
||||
.fontSize(11)
|
||||
.fillColor('#FA8C16')
|
||||
.text(`已扣押金: -¥${depositDeducted.toFixed(2)}`);
|
||||
}
|
||||
}
|
||||
doc.moveDown(1);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export class BillsController {
|
||||
username: req.user?.username,
|
||||
module: '账单管理',
|
||||
action: '生成账单',
|
||||
detail: `周期 ${dto.periodStart}~${dto.periodEnd}, 生成 ${result.count} 条`,
|
||||
detail: `周期 ${result.periodStart}~${result.periodEnd}, 生成 ${result.count} 条`,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
});
|
||||
@@ -67,7 +67,7 @@ export class BillsController {
|
||||
recipientIds: [student.userId],
|
||||
type: NotificationType.BILL_GENERATED,
|
||||
title: '新账单',
|
||||
content: `您有一笔新账单,金额: ¥${bill.totalAmount}, 周期: ${dto.periodStart}~${dto.periodEnd}`,
|
||||
content: `您有一笔新账单,金额: ¥${bill.totalAmount}, 周期: ${result.periodStart}~${result.periodEnd}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ export class BillsController {
|
||||
userId: req.user?.id,
|
||||
username: req.user?.username,
|
||||
module: '账单管理',
|
||||
action: '确认账单',
|
||||
action: '确认账单并扣押金',
|
||||
detail: `IDs: ${body.ids.join(',')}`,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
@@ -144,7 +144,7 @@ export class BillsController {
|
||||
userId: req.user?.id,
|
||||
username: req.user?.username,
|
||||
module: '账单管理',
|
||||
action: '确认账单',
|
||||
action: '确认账单并扣押金',
|
||||
targetId: id,
|
||||
targetType: 'bill',
|
||||
ipAddress,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, In, DataSource } from 'typeorm';
|
||||
import { Repository, In, DataSource, EntityManager } from 'typeorm';
|
||||
import { Bill } from '../entities/bill.entity';
|
||||
import { BillItem } from '../entities/bill-item.entity';
|
||||
import { RoomExpense } from '../entities/room-expense.entity';
|
||||
@@ -28,26 +28,15 @@ export class BillsService {
|
||||
* 核心计费引擎:按"人天数"加权分摊
|
||||
*/
|
||||
async generateBills(dto: GenerateBillsDto) {
|
||||
const { periodStart, periodEnd } = dto;
|
||||
const { periodStart, periodEnd } = this.resolveBillingPeriod(dto.billingMonth);
|
||||
const pStart = new Date(periodStart);
|
||||
const pEnd = new Date(periodEnd);
|
||||
|
||||
// 删除该周期已有的草稿账单
|
||||
const existingDrafts = await this.billRepo.find({
|
||||
where: { periodStart, periodEnd, status: 'draft' },
|
||||
const existingBills = await this.billRepo.find({
|
||||
where: { periodStart, periodEnd },
|
||||
});
|
||||
if (existingDrafts.length > 0) {
|
||||
const draftIds = existingDrafts.map((b) => b.id);
|
||||
await this.itemRepo
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.where('billId IN (:...ids)', { ids: draftIds })
|
||||
.execute();
|
||||
await this.billRepo
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.where('id IN (:...ids)', { ids: draftIds })
|
||||
.execute();
|
||||
if (existingBills.length > 0) {
|
||||
throw new BadRequestException(`${dto.billingMonth} 月账单已生成,不能重复生成`);
|
||||
}
|
||||
|
||||
// 获取账单周期内所有有费用的宿舍
|
||||
@@ -208,7 +197,41 @@ export class BillsService {
|
||||
bills.push(savedBill);
|
||||
}
|
||||
|
||||
return { message: `成功生成 ${bills.length} 条账单`, count: bills.length, bills };
|
||||
return {
|
||||
message: `成功生成 ${dto.billingMonth} 月 ${bills.length} 条账单`,
|
||||
count: bills.length,
|
||||
periodStart,
|
||||
periodEnd,
|
||||
bills,
|
||||
};
|
||||
}
|
||||
|
||||
private resolveBillingPeriod(billingMonth: string) {
|
||||
const matched = /^(\d{4})-(\d{2})$/.exec(billingMonth || '');
|
||||
if (!matched) {
|
||||
throw new BadRequestException('账单月份格式错误,请使用 YYYY-MM');
|
||||
}
|
||||
|
||||
const year = Number(matched[1]);
|
||||
const month = Number(matched[2]);
|
||||
if (month < 1 || month > 12) {
|
||||
throw new BadRequestException('账单月份格式错误,请使用 YYYY-MM');
|
||||
}
|
||||
|
||||
const targetMonthStart = new Date(year, month - 1, 1);
|
||||
const currentMonthStart = new Date();
|
||||
currentMonthStart.setDate(1);
|
||||
currentMonthStart.setHours(0, 0, 0, 0);
|
||||
if (targetMonthStart >= currentMonthStart) {
|
||||
throw new BadRequestException('只能生成已结束月份的账单');
|
||||
}
|
||||
|
||||
const targetMonthEnd = new Date(year, month, 0);
|
||||
const pad = (value: number) => String(value).padStart(2, '0');
|
||||
return {
|
||||
periodStart: `${year}-${pad(month)}-01`,
|
||||
periodEnd: `${year}-${pad(month)}-${pad(targetMonthEnd.getDate())}`,
|
||||
};
|
||||
}
|
||||
|
||||
async findAll(query?: {
|
||||
@@ -242,9 +265,9 @@ export class BillsService {
|
||||
|
||||
/**
|
||||
* 给账单挂上"押金联动"信息:
|
||||
* - availableDeposit: 当前学生处于已缴未退状态(paid)的押金总额
|
||||
* - depositApplied: 本张账单可从押金抵扣的金额(min(押金, 应付总额))
|
||||
* - amountAfterDeposit: 抵扣押金后学生需另外支付的金额
|
||||
* - availableDeposit: 学生当前实时可用押金余额,生成账单时不会冻结
|
||||
* - depositSufficient: 草稿账单是否已有足够余额可确认
|
||||
* - depositDeductedAmount: 已确认账单实际扣除的押金金额
|
||||
*/
|
||||
private async attachDepositInfo(bills: Bill[]): Promise<any[]> {
|
||||
if (!bills || bills.length === 0) return bills;
|
||||
@@ -253,7 +276,6 @@ export class BillsService {
|
||||
const deposits = await this.depositRepo
|
||||
.createQueryBuilder('d')
|
||||
.where('d.studentId IN (:...ids)', { ids: studentIds })
|
||||
.andWhere('d.status = :status', { status: 'paid' })
|
||||
.getMany();
|
||||
const depMap = new Map<number, number>();
|
||||
for (const d of deposits) {
|
||||
@@ -262,42 +284,83 @@ export class BillsService {
|
||||
return bills.map((b) => {
|
||||
const total = Number(b.totalAmount || 0);
|
||||
const available = Number((depMap.get(b.studentId) || 0).toFixed(2));
|
||||
const applied = Number(Math.min(available, total).toFixed(2));
|
||||
const afterDeposit = Number(Math.max(0, total - applied).toFixed(2));
|
||||
return Object.assign({}, b, {
|
||||
availableDeposit: available,
|
||||
depositApplied: applied,
|
||||
amountAfterDeposit: afterDeposit,
|
||||
depositSufficient: available >= total,
|
||||
depositDeductedAmount: Number(b.depositDeductedAmount || 0),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async updateStatus(id: number, dto: UpdateBillStatusDto) {
|
||||
const bill = await this.billRepo.findOne({ where: { id } });
|
||||
if (!bill) throw new NotFoundException('账单不存在');
|
||||
bill.status = dto.status;
|
||||
return this.billRepo.save(bill);
|
||||
if (dto.status !== 'paid') {
|
||||
throw new BadRequestException('账单只能通过确认支付完成扣款');
|
||||
}
|
||||
return this.dataSource.transaction((manager) => this.payBill(manager, id));
|
||||
}
|
||||
|
||||
async batchUpdateStatus(ids: number[], status: string) {
|
||||
await this.billRepo
|
||||
.createQueryBuilder()
|
||||
.update()
|
||||
.set({ status })
|
||||
.where('id IN (:...ids)', { ids })
|
||||
.execute();
|
||||
return { message: `成功更新 ${ids.length} 条账单状态` };
|
||||
if (status !== 'paid') {
|
||||
throw new BadRequestException('账单只能通过确认支付完成扣款');
|
||||
}
|
||||
const uniqueIds = Array.from(new Set(ids));
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
for (const id of uniqueIds) await this.payBill(manager, id);
|
||||
});
|
||||
return { message: `成功确认 ${uniqueIds.length} 条账单并扣除押金` };
|
||||
}
|
||||
|
||||
private async payBill(manager: EntityManager, id: number) {
|
||||
const billRepo = manager.getRepository(Bill);
|
||||
const depositRepo = manager.getRepository(Deposit);
|
||||
const lock = this.supportsPessimisticLocks()
|
||||
? ({ mode: 'pessimistic_write' } as const)
|
||||
: undefined;
|
||||
const bill = await billRepo.findOne({ where: { id }, ...(lock ? { lock } : {}) });
|
||||
if (!bill) throw new NotFoundException(`账单 ${id} 不存在`);
|
||||
if (bill.status === 'paid') return bill;
|
||||
if (bill.status !== 'draft') throw new BadRequestException(`账单 ${id} 当前状态无法确认支付`);
|
||||
|
||||
const deposit = await depositRepo.findOne({
|
||||
where: { studentId: bill.studentId },
|
||||
...(lock ? { lock } : {}),
|
||||
});
|
||||
const available = Number(deposit?.amount || 0);
|
||||
const required = Number(bill.totalAmount || 0);
|
||||
if (!deposit || available < required) {
|
||||
throw new BadRequestException(
|
||||
`账单 ${id} 押金不足:需 ¥${required.toFixed(2)},当前可用 ¥${available.toFixed(2)},请先到押金管理收取押金`,
|
||||
);
|
||||
}
|
||||
|
||||
deposit.amount = Number((available - required).toFixed(2));
|
||||
deposit.status = deposit.amount > 0 ? 'paid' : 'depleted';
|
||||
bill.depositDeductedAmount = required;
|
||||
bill.status = 'paid';
|
||||
await depositRepo.save(deposit);
|
||||
return billRepo.save(bill);
|
||||
}
|
||||
|
||||
private supportsPessimisticLocks() {
|
||||
return ['mysql', 'mariadb', 'postgres', 'cockroachdb', 'mssql', 'oracle'].includes(
|
||||
String(this.dataSource.options.type),
|
||||
);
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const exists = await this.billRepo.findOne({ where: { id } });
|
||||
if (!exists) throw new NotFoundException('账单不存在');
|
||||
if (exists.status === 'paid') throw new BadRequestException('已支付账单不能删除');
|
||||
await this.itemRepo.delete({ billId: id });
|
||||
await this.billRepo.delete(id);
|
||||
return { message: '账单已删除' };
|
||||
}
|
||||
|
||||
async batchRemove(ids: number[]) {
|
||||
const bills = await this.billRepo.find({ where: { id: In(ids) } });
|
||||
if (bills.some((bill) => bill.status === 'paid')) {
|
||||
throw new BadRequestException('已支付账单不能删除');
|
||||
}
|
||||
await this.itemRepo
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import { ArrayNotEmpty, IsArray, IsIn, IsInt, IsString } from 'class-validator';
|
||||
import { ArrayNotEmpty, IsArray, IsIn, IsInt, IsOptional, IsString, Matches } from 'class-validator';
|
||||
|
||||
export class GenerateBillsDto {
|
||||
@IsString()
|
||||
periodStart: string; // YYYY-MM-DD
|
||||
@Matches(/^\d{4}-\d{2}$/)
|
||||
billingMonth: string; // YYYY-MM
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
periodEnd: string; // YYYY-MM-DD
|
||||
periodStart?: string; // deprecated, derived from billingMonth
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
periodEnd?: string; // deprecated, derived from billingMonth
|
||||
}
|
||||
|
||||
export class UpdateBillStatusDto {
|
||||
@IsIn(['draft', 'confirmed', 'paid'])
|
||||
status: 'draft' | 'confirmed' | 'paid';
|
||||
@IsIn(['paid'])
|
||||
status: 'paid';
|
||||
}
|
||||
|
||||
export class BatchUpdateBillStatusDto extends UpdateBillStatusDto {
|
||||
|
||||
@@ -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 */ }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -19,14 +19,6 @@ export class RefundDepositDto {
|
||||
@IsString()
|
||||
refundDate: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
deductionAmount?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
deductionReason?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
|
||||
@@ -33,6 +33,9 @@ export class Bill {
|
||||
@Column({ name: 'total_amount', type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||
totalAmount: number;
|
||||
|
||||
@Column({ name: 'deposit_deducted_amount', type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||
depositDeductedAmount: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'draft' })
|
||||
status: string;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export class Deposit {
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2, default: 500 })
|
||||
amount: number;
|
||||
|
||||
// paid: 已缴 | refunded: 已退 | deducted: 已扣除(部分或全部)
|
||||
// paid: 有可用余额 | refunded: 余额已全部退还 | depleted: 余额已被账单扣完
|
||||
@Column({ type: 'varchar', length: 20, default: 'paid' })
|
||||
status: string;
|
||||
|
||||
@@ -43,8 +43,8 @@ export class Deposit {
|
||||
@Column({ type: 'text', nullable: true })
|
||||
notes: string;
|
||||
|
||||
@Column({ name: 'recorded_by', nullable: true })
|
||||
recordedBy: number;
|
||||
@Column({ name: 'recorded_by', type: 'integer', nullable: true })
|
||||
recordedBy: number | null;
|
||||
|
||||
@Column({ name: 'refunded_by', type: 'integer', nullable: true })
|
||||
refundedBy: number | null;
|
||||
|
||||
@@ -106,9 +106,18 @@ export class OccupanciesService {
|
||||
|
||||
if (dto.collectDeposit) {
|
||||
const existingDeposit = await this.depositRepo.findOne({
|
||||
where: { studentId: dto.studentId, status: 'paid' },
|
||||
where: { studentId: dto.studentId },
|
||||
});
|
||||
if (!existingDeposit) {
|
||||
if (existingDeposit) {
|
||||
existingDeposit.amount = Number(
|
||||
(Number(existingDeposit.amount || 0) + Number(dto.depositAmount ?? 500)).toFixed(2),
|
||||
);
|
||||
existingDeposit.status = 'paid';
|
||||
existingDeposit.paidDate = dto.checkInDate;
|
||||
existingDeposit.recordedBy = userId ?? null;
|
||||
existingDeposit.notes = '入住登记自动收取';
|
||||
await this.depositRepo.save(existingDeposit);
|
||||
} else {
|
||||
await this.depositRepo.save(
|
||||
this.depositRepo.create({
|
||||
studentId: dto.studentId,
|
||||
@@ -529,9 +538,18 @@ export class OccupanciesService {
|
||||
// 9. 自动收取押金(仅对新入住且非历史记录的学生)
|
||||
if (options?.autoDeposit && !row.checkOutDate?.trim()) {
|
||||
const existingDeposit = await this.depositRepo.findOne({
|
||||
where: { studentId: student.id, status: 'paid' },
|
||||
where: { studentId: student.id },
|
||||
});
|
||||
if (!existingDeposit) {
|
||||
if (existingDeposit) {
|
||||
existingDeposit.amount = Number(
|
||||
(Number(existingDeposit.amount || 0) + Number(options.depositAmount || 500)).toFixed(2),
|
||||
);
|
||||
existingDeposit.status = 'paid';
|
||||
existingDeposit.paidDate = checkInDate;
|
||||
existingDeposit.notes = '入住导入自动收取';
|
||||
await this.depositRepo.save(existingDeposit);
|
||||
depositsCreated++;
|
||||
} else {
|
||||
await this.depositRepo.save(
|
||||
this.depositRepo.create({
|
||||
studentId: student.id,
|
||||
|
||||
Reference in New Issue
Block a user