chore: commit oxfmt formatting changes and verify artifacts

This commit is contained in:
2026-07-02 15:55:09 +08:00
parent 34726cc46d
commit 4f4cee157a
77 changed files with 5576 additions and 1400 deletions

View File

@@ -5,7 +5,11 @@ import { RoomExpense } from '../entities/room-expense.entity';
import { PersonalExpense } from '../entities/personal-expense.entity';
import { Room } from '../entities/room.entity';
import { Student } from '../entities/student.entity';
import { CreateRoomExpenseDto, CreatePersonalExpenseDto, BatchRoomExpenseDto } from './dto/expense.dto';
import {
CreateRoomExpenseDto,
CreatePersonalExpenseDto,
BatchRoomExpenseDto,
} from './dto/expense.dto';
import { RoomsService } from '../rooms/rooms.service';
@Injectable()
@@ -38,7 +42,8 @@ export class ExpensesService {
}
async findRoomExpenses(query?: { roomId?: number; periodStart?: string; periodEnd?: string }) {
const qb = this.roomExpRepo.createQueryBuilder('e')
const qb = this.roomExpRepo
.createQueryBuilder('e')
.leftJoinAndSelect('e.room', 'room')
.orderBy('e.createdAt', 'DESC');
if (query?.roomId) qb.andWhere('e.roomId = :roomId', { roomId: query.roomId });
@@ -56,7 +61,8 @@ export class ExpensesService {
async batchDeleteRoomExpenses(ids: number[]) {
if (!ids || ids.length === 0) throw new BadRequestException('请选择要删除的记录');
const result = await this.roomExpRepo.createQueryBuilder()
const result = await this.roomExpRepo
.createQueryBuilder()
.delete()
.where('id IN (:...ids)', { ids })
.execute();
@@ -78,7 +84,11 @@ export class ExpensesService {
async findPersonalExpenses(query?: { studentId?: number }) {
const where: any = {};
if (query?.studentId) where.studentId = query.studentId;
return this.personalExpRepo.find({ where, relations: ['student'], order: { createdAt: 'DESC' } });
return this.personalExpRepo.find({
where,
relations: ['student'],
order: { createdAt: 'DESC' },
});
}
async deletePersonalExpense(id: number) {
@@ -90,7 +100,8 @@ export class ExpensesService {
async batchDeletePersonalExpenses(ids: number[]) {
if (!ids || ids.length === 0) throw new BadRequestException('请选择要删除的记录');
const result = await this.personalExpRepo.createQueryBuilder()
const result = await this.personalExpRepo
.createQueryBuilder()
.delete()
.where('id IN (:...ids)', { ids })
.execute();
@@ -109,15 +120,18 @@ export class ExpensesService {
* Excel格式: 序号|时间|房间号|房间电量|电费|冷水用量(吨)|水费|应缴金额
* 时间格式: "2026-01-21 - 2026-02-08"
*/
async batchImportUtilityExpenses(rows: {
periodStr: string;
roomNumber: string;
electricityAmount: number;
electricityFee: number;
waterAmount: number;
waterFee: number;
totalFee: number;
}[], userId?: number) {
async batchImportUtilityExpenses(
rows: {
periodStr: string;
roomNumber: string;
electricityAmount: number;
electricityFee: number;
waterAmount: number;
waterFee: number;
totalFee: number;
}[],
userId?: number,
) {
let imported = 0;
let skipped = 0;
const errors: string[] = [];
@@ -126,20 +140,25 @@ export class ExpensesService {
const row = rows[i];
const rowNum = i + 2;
if (!row.roomNumber?.trim()) { skipped++; continue; }
if (!row.roomNumber?.trim()) {
skipped++;
continue;
}
try {
// 查找或创建宿舍
let room = await this.roomRepo.findOne({ where: { roomNumber: row.roomNumber.trim() } });
if (!room) {
const parsed = RoomsService.parseRoomNumber(row.roomNumber.trim());
room = await this.roomRepo.save(this.roomRepo.create({
roomNumber: row.roomNumber.trim(),
building: parsed.building || undefined,
floor: parsed.floor || undefined,
capacity: parsed.capacity || 4,
roomType: parsed.roomType || undefined,
}));
room = await this.roomRepo.save(
this.roomRepo.create({
roomNumber: row.roomNumber.trim(),
building: parsed.building || undefined,
floor: parsed.floor || undefined,
capacity: parsed.capacity || 4,
roomType: parsed.roomType || undefined,
}),
);
}
// 解析时间段 "2026-01-21 - 2026-02-08" 或 "2026-01-21~2026-02-08"
@@ -169,13 +188,16 @@ export class ExpensesService {
// 关键校验:电费 + 水费 都为 0 时,多半是 Excel 公式未正确计算或字段缺失,
// 必须给出明确错误,避免出现"提示成功但无数据"的迷之现象。
if ((row.electricityFee || 0) <= 0 && (row.waterFee || 0) <= 0) {
errors.push(`${rowNum}行: ${row.roomNumber} 电费和水费均为 0可能 Excel 中是未生效的公式(请打开文件让公式重算后再保存导入),已跳过`);
errors.push(
`${rowNum}行: ${row.roomNumber} 电费和水费均为 0可能 Excel 中是未生效的公式(请打开文件让公式重算后再保存导入),已跳过`,
);
skipped++;
continue;
}
// 幂等:先删除该房间在同一周期已有的水/电费用记录,避免重复导入产生脏数据
await this.roomExpRepo.createQueryBuilder()
await this.roomExpRepo
.createQueryBuilder()
.delete()
.where('roomId = :roomId', { roomId: room.id })
.andWhere('periodStart = :ps AND periodEnd = :pe', { ps: periodStart, pe: periodEnd })
@@ -185,34 +207,41 @@ export class ExpensesService {
let savedAny = false;
// 导入电费
if (row.electricityFee > 0) {
await this.roomExpRepo.save(this.roomExpRepo.create({
roomId: room.id,
expenseType: 'electricity',
amount: row.electricityFee,
periodStart,
periodEnd,
description: `电量${row.electricityAmount}kWh`,
recordedBy: userId,
}));
await this.roomExpRepo.save(
this.roomExpRepo.create({
roomId: room.id,
expenseType: 'electricity',
amount: row.electricityFee,
periodStart,
periodEnd,
description: `电量${row.electricityAmount}kWh`,
recordedBy: userId,
}),
);
savedAny = true;
}
// 导入水费
if (row.waterFee > 0) {
await this.roomExpRepo.save(this.roomExpRepo.create({
roomId: room.id,
expenseType: 'water',
amount: row.waterFee,
periodStart,
periodEnd,
description: `用水${row.waterAmount}`,
recordedBy: userId,
}));
await this.roomExpRepo.save(
this.roomExpRepo.create({
roomId: room.id,
expenseType: 'water',
amount: row.waterFee,
periodStart,
periodEnd,
description: `用水${row.waterAmount}`,
recordedBy: userId,
}),
);
savedAny = true;
}
if (savedAny) imported++;
else { skipped++; errors.push(`${rowNum}行: ${row.roomNumber} 无有效金额`); }
else {
skipped++;
errors.push(`${rowNum}行: ${row.roomNumber} 无有效金额`);
}
} catch (e: any) {
errors.push(`${rowNum}行: ${row.roomNumber} 导入失败 - ${e.message}`);
skipped++;
@@ -220,9 +249,10 @@ export class ExpensesService {
}
return {
message: imported > 0
? `成功导入 ${imported} 间宿舍水电费${skipped > 0 ? `,跳过 ${skipped}` : ''}`
: `未导入任何记录${skipped > 0 ? ` ${skipped}被跳过` : ''}`,
message:
imported > 0
? `成功导入 ${imported} 间宿舍水电费${skipped > 0 ? `跳过 ${skipped}` : ''}`
: `未导入任何记录${skipped > 0 ? `,共 ${skipped} 条被跳过` : ''}`,
imported,
skipped,
errors: errors.length > 0 ? errors : undefined,
@@ -242,32 +272,42 @@ export class ExpensesService {
* 个人附加费Excel批量导入
* Excel格式: 学生姓名|费用类型|金额|费用日期|说明
*/
async batchImportPersonalExpenses(rows: {
studentName: string;
expenseType: string;
amount: number;
expenseDate: string;
description?: string;
}[], userId?: number) {
async batchImportPersonalExpenses(
rows: {
studentName: string;
expenseType: string;
amount: number;
expenseDate: string;
description?: string;
}[],
userId?: number,
) {
let imported = 0;
let skipped = 0;
const errors: string[] = [];
const typeMap: Record<string, string> = {
'物品损坏': 'damage', '损坏': 'damage',
'保洁费': 'cleaning', '保洁': 'cleaning',
'罚款': 'penalty',
'钥匙费': 'key', '钥匙': 'key',
'空调遥控器': 'remote', '遥控器': 'remote',
'押金扣除': 'deposit_deduction',
'其他': 'other',
: 'damage',
: 'damage',
: 'cleaning',
: 'cleaning',
: 'penalty',
: 'key',
: 'key',
: 'remote',
: 'remote',
: 'deposit_deduction',
: 'other',
};
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const rowNum = i + 2;
if (!row.studentName?.trim()) { skipped++; continue; }
if (!row.studentName?.trim()) {
skipped++;
continue;
}
try {
// 查找学生
@@ -283,7 +323,15 @@ export class ExpensesService {
if (typeMap[expenseType]) {
expenseType = typeMap[expenseType];
}
const validTypes = ['damage', 'cleaning', 'penalty', 'key', 'remote', 'deposit_deduction', 'other'];
const validTypes = [
'damage',
'cleaning',
'penalty',
'key',
'remote',
'deposit_deduction',
'other',
];
if (!validTypes.includes(expenseType)) {
errors.push(`${rowNum}行: 费用类型"${row.expenseType}"无效`);
skipped++;
@@ -304,14 +352,16 @@ export class ExpensesService {
}
}
await this.personalExpRepo.save(this.personalExpRepo.create({
studentId: student.id,
expenseType,
amount: row.amount,
expenseDate,
description: row.description || undefined,
recordedBy: userId,
}));
await this.personalExpRepo.save(
this.personalExpRepo.create({
studentId: student.id,
expenseType,
amount: row.amount,
expenseDate,
description: row.description || undefined,
recordedBy: userId,
}),
);
imported++;
} catch (e: any) {