forked from wangziqi/gongxue-base
chore: commit oxfmt formatting changes and verify artifacts
This commit is contained in:
@@ -1,4 +1,18 @@
|
||||
import { Controller, Get, Post, Put, Delete, Param, Body, Query, UseGuards, Request, Res, UseInterceptors, UploadedFile } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Param,
|
||||
Body,
|
||||
Query,
|
||||
UseGuards,
|
||||
Request,
|
||||
Res,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import type { Response } from 'express';
|
||||
import { OccupanciesService } from './occupancies.service';
|
||||
@@ -12,7 +26,10 @@ import * as ExcelJS from 'exceljs';
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('occupancies')
|
||||
export class OccupanciesController {
|
||||
constructor(private service: OccupanciesService, private logService: OperationLogsService) {}
|
||||
constructor(
|
||||
private service: OccupanciesService,
|
||||
private logService: OperationLogsService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@RequirePermission('occupancy:view')
|
||||
@@ -33,7 +50,15 @@ export class OccupanciesController {
|
||||
async batchCheckOut(@Body() dto: BatchCheckOutDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.batchCheckOut(dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '入住', action: '批量退宿', detail: `退宿 ${dto.ids.length} 人,日期 ${dto.checkOutDate}`, ipAddress, userAgent });
|
||||
await this.logService.log({
|
||||
userId: req.user?.id,
|
||||
username: req.user?.username,
|
||||
module: '入住',
|
||||
action: '批量退宿',
|
||||
detail: `退宿 ${dto.ids.length} 人,日期 ${dto.checkOutDate}`,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -42,7 +67,17 @@ export class OccupanciesController {
|
||||
async checkIn(@Body() dto: CheckInDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.checkIn(dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '入住', action: '入住登记', targetId: result.id, targetType: 'occupancy', detail: `学生${dto.studentId} 入住房间${dto.roomId}`, ipAddress, userAgent });
|
||||
await this.logService.log({
|
||||
userId: req.user?.id,
|
||||
username: req.user?.username,
|
||||
module: '入住',
|
||||
action: '入住登记',
|
||||
targetId: result.id,
|
||||
targetType: 'occupancy',
|
||||
detail: `学生${dto.studentId} 入住房间${dto.roomId}`,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -51,7 +86,16 @@ export class OccupanciesController {
|
||||
async checkOut(@Param('id') id: string, @Body() dto: CheckOutDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.checkOut(+id, dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '入住', action: '退宿', targetId: +id, targetType: 'occupancy', ipAddress, userAgent });
|
||||
await this.logService.log({
|
||||
userId: req.user?.id,
|
||||
username: req.user?.username,
|
||||
module: '入住',
|
||||
action: '退宿',
|
||||
targetId: +id,
|
||||
targetType: 'occupancy',
|
||||
ipAddress,
|
||||
userAgent,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -60,7 +104,17 @@ export class OccupanciesController {
|
||||
async transferRoom(@Param('id') id: string, @Body() dto: TransferRoomDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.transferRoom(+id, dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '入住', action: '换房', targetId: +id, targetType: 'occupancy', detail: `换到房间${dto.newRoomId}`, ipAddress, userAgent });
|
||||
await this.logService.log({
|
||||
userId: req.user?.id,
|
||||
username: req.user?.username,
|
||||
module: '入住',
|
||||
action: '换房',
|
||||
targetId: +id,
|
||||
targetType: 'occupancy',
|
||||
detail: `换到房间${dto.newRoomId}`,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -69,7 +123,16 @@ export class OccupanciesController {
|
||||
async remove(@Param('id') id: string, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.remove(+id);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '入住', action: '删除入住记录', targetId: +id, targetType: 'occupancy', ipAddress, userAgent });
|
||||
await this.logService.log({
|
||||
userId: req.user?.id,
|
||||
username: req.user?.username,
|
||||
module: '入住',
|
||||
action: '删除入住记录',
|
||||
targetId: +id,
|
||||
targetType: 'occupancy',
|
||||
ipAddress,
|
||||
userAgent,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -78,7 +141,15 @@ export class OccupanciesController {
|
||||
async batchRemove(@Body() body: { ids: number[] }, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.batchRemove(body.ids || []);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '入住', action: '批量删除入住记录', detail: `IDs: ${(body.ids || []).join(',')}`, ipAddress, userAgent });
|
||||
await this.logService.log({
|
||||
userId: req.user?.id,
|
||||
username: req.user?.username,
|
||||
module: '入住',
|
||||
action: '批量删除入住记录',
|
||||
detail: `IDs: ${(body.ids || []).join(',')}`,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -122,7 +193,10 @@ export class OccupanciesController {
|
||||
checkOutReason: r.checkOutReason || '',
|
||||
});
|
||||
}
|
||||
res!.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res!.setHeader(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
);
|
||||
res!.setHeader('Content-Disposition', 'attachment; filename=occupancies.xlsx');
|
||||
await workbook.xlsx.write(res!);
|
||||
res!.end();
|
||||
@@ -151,8 +225,36 @@ export class OccupanciesController {
|
||||
ws.getRow(1).font = { bold: true };
|
||||
ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } };
|
||||
// 添加说明行
|
||||
ws.addRow({ roomNumber: '4-102', bedNumber: 1, name: '张三', gender: '男', ethnicity: '汉族', phone: '13800138000', idNumber: '2024001', checkInDate: '2026-04-21', checkOutDate: '', emergencyContact: '张父', emergencyPhone: '13900000000', organization: '', supervisor: '' });
|
||||
ws.addRow({ roomNumber: '4-102', bedNumber: 2, name: '李四', gender: '男', ethnicity: '汉族', phone: '13800138001', idNumber: '2024002', checkInDate: '2026-04-21', checkOutDate: '', emergencyContact: '', emergencyPhone: '', organization: 'XXX教育科技', supervisor: '王老师' });
|
||||
ws.addRow({
|
||||
roomNumber: '4-102',
|
||||
bedNumber: 1,
|
||||
name: '张三',
|
||||
gender: '男',
|
||||
ethnicity: '汉族',
|
||||
phone: '13800138000',
|
||||
idNumber: '2024001',
|
||||
checkInDate: '2026-04-21',
|
||||
checkOutDate: '',
|
||||
emergencyContact: '张父',
|
||||
emergencyPhone: '13900000000',
|
||||
organization: '',
|
||||
supervisor: '',
|
||||
});
|
||||
ws.addRow({
|
||||
roomNumber: '4-102',
|
||||
bedNumber: 2,
|
||||
name: '李四',
|
||||
gender: '男',
|
||||
ethnicity: '汉族',
|
||||
phone: '13800138001',
|
||||
idNumber: '2024002',
|
||||
checkInDate: '2026-04-21',
|
||||
checkOutDate: '',
|
||||
emergencyContact: '',
|
||||
emergencyPhone: '',
|
||||
organization: 'XXX教育科技',
|
||||
supervisor: '王老师',
|
||||
});
|
||||
// 添加使用说明sheet
|
||||
const helpWs = workbook.addWorksheet('使用说明');
|
||||
helpWs.getColumn(1).width = 60;
|
||||
@@ -166,7 +268,10 @@ export class OccupanciesController {
|
||||
helpWs.addRow(['7. 性别约束:同一宿舍只能住同性别学生,首位入住者确定宿舍性别']);
|
||||
helpWs.addRow(['8. 床位号仅做标识参考,不影响入住逻辑']);
|
||||
helpWs.getRow(1).font = { bold: true, size: 14 };
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
);
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=checkin_template.xlsx');
|
||||
await workbook.xlsx.write(res);
|
||||
res.end();
|
||||
@@ -175,7 +280,12 @@ export class OccupanciesController {
|
||||
@Post('import')
|
||||
@RequirePermission('occupancy:checkin')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async importCheckIn(@UploadedFile() file: Express.Multer.File, @Request() req: any, @Query('autoDeposit') autoDeposit?: string, @Query('depositAmount') depositAmount?: string) {
|
||||
async importCheckIn(
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@Request() req: any,
|
||||
@Query('autoDeposit') autoDeposit?: string,
|
||||
@Query('depositAmount') depositAmount?: string,
|
||||
) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.load(file.buffer as any);
|
||||
@@ -225,7 +335,15 @@ export class OccupanciesController {
|
||||
autoDeposit: autoDeposit === 'true',
|
||||
depositAmount: depositAmount ? +depositAmount : undefined,
|
||||
});
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '入住', action: '批量导入入住', detail: result.message, ipAddress, userAgent });
|
||||
await this.logService.log({
|
||||
userId: req.user?.id,
|
||||
username: req.user?.username,
|
||||
module: '入住',
|
||||
action: '批量导入入住',
|
||||
detail: result.message,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource, IsNull, Between, LessThanOrEqual, MoreThanOrEqual, In } from 'typeorm';
|
||||
import {
|
||||
Repository,
|
||||
DataSource,
|
||||
IsNull,
|
||||
Between,
|
||||
LessThanOrEqual,
|
||||
MoreThanOrEqual,
|
||||
In,
|
||||
} from 'typeorm';
|
||||
import { Occupancy } from '../entities/occupancy.entity';
|
||||
import { Room } from '../entities/room.entity';
|
||||
import { Student } from '../entities/student.entity';
|
||||
@@ -19,7 +27,8 @@ export class OccupanciesService {
|
||||
) {}
|
||||
|
||||
async findAll(query?: { roomId?: number; studentId?: number; active?: boolean }) {
|
||||
const qb = this.repo.createQueryBuilder('o')
|
||||
const qb = this.repo
|
||||
.createQueryBuilder('o')
|
||||
.leftJoinAndSelect('o.student', 'student')
|
||||
.leftJoinAndSelect('o.room', 'room')
|
||||
.orderBy('o.checkInDate', 'DESC');
|
||||
@@ -31,7 +40,9 @@ export class OccupanciesService {
|
||||
|
||||
async checkIn(dto: CheckInDto) {
|
||||
// 检查学生是否已有活跃入住
|
||||
const existing = await this.repo.findOne({ where: { studentId: dto.studentId, checkOutDate: IsNull() } });
|
||||
const existing = await this.repo.findOne({
|
||||
where: { studentId: dto.studentId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (existing) throw new BadRequestException('该学生已有在住记录,请先办理退宿');
|
||||
|
||||
// 检查宿舍容量
|
||||
@@ -44,7 +55,9 @@ export class OccupanciesService {
|
||||
const student = await this.studentRepo.findOne({ where: { id: dto.studentId } });
|
||||
if (!student) throw new NotFoundException('学生不存在');
|
||||
if (student.gender && room.gender && student.gender !== room.gender) {
|
||||
throw new BadRequestException(`该宿舍当前为${room.gender}生寝室,${student.gender}生无法入住`);
|
||||
throw new BadRequestException(
|
||||
`该宿舍当前为${room.gender}生寝室,${student.gender}生无法入住`,
|
||||
);
|
||||
}
|
||||
|
||||
const occ = this.repo.create({
|
||||
@@ -82,7 +95,9 @@ export class OccupanciesService {
|
||||
await this.roomRepo.update(occ.roomId, { status: 'available' });
|
||||
|
||||
// 如果房间已无在住人员,重置房间性别
|
||||
const remaining = await this.repo.count({ where: { roomId: occ.roomId, checkOutDate: IsNull() } });
|
||||
const remaining = await this.repo.count({
|
||||
where: { roomId: occ.roomId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (remaining === 0) {
|
||||
await this.roomRepo.update(occ.roomId, { gender: null as any });
|
||||
}
|
||||
@@ -106,7 +121,9 @@ export class OccupanciesService {
|
||||
await runner.manager.save(oldOcc);
|
||||
await runner.manager.update(Room, oldOcc.roomId, { status: 'available' });
|
||||
// 旧房如果已无在住人员,重置性别
|
||||
const oldRemaining = await runner.manager.count(Occupancy, { where: { roomId: oldOcc.roomId, checkOutDate: IsNull() } });
|
||||
const oldRemaining = await runner.manager.count(Occupancy, {
|
||||
where: { roomId: oldOcc.roomId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (oldRemaining === 0) {
|
||||
await runner.manager.update(Room, oldOcc.roomId, { gender: null as any });
|
||||
}
|
||||
@@ -114,7 +131,9 @@ export class OccupanciesService {
|
||||
// 检查新房容量
|
||||
const newRoom = await runner.manager.findOne(Room, { where: { id: dto.newRoomId } });
|
||||
if (!newRoom) throw new NotFoundException('目标宿舍不存在');
|
||||
const count = await runner.manager.count(Occupancy, { where: { roomId: dto.newRoomId, checkOutDate: IsNull() } });
|
||||
const count = await runner.manager.count(Occupancy, {
|
||||
where: { roomId: dto.newRoomId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (count >= newRoom.capacity) throw new BadRequestException('目标宿舍已满');
|
||||
|
||||
// 换房性别约束检查
|
||||
@@ -160,7 +179,8 @@ export class OccupanciesService {
|
||||
|
||||
// 获取某宿舍在指定时间段内的入住记录(用于计费)
|
||||
async getRoomOccupanciesInPeriod(roomId: number, periodStart: string, periodEnd: string) {
|
||||
return this.repo.createQueryBuilder('o')
|
||||
return this.repo
|
||||
.createQueryBuilder('o')
|
||||
.leftJoinAndSelect('o.student', 'student')
|
||||
.where('o.roomId = :roomId', { roomId })
|
||||
.andWhere('o.billingStartDate <= :periodEnd', { periodEnd })
|
||||
@@ -190,19 +210,26 @@ export class OccupanciesService {
|
||||
}
|
||||
let deleted = 0;
|
||||
if (deletableIds.length > 0) {
|
||||
const result = await this.repo.createQueryBuilder()
|
||||
const result = await this.repo
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.where('id IN (:...ids)', { ids: deletableIds })
|
||||
.execute();
|
||||
deleted = result.affected || 0;
|
||||
}
|
||||
const message = skipped.length > 0
|
||||
? `成功删除 ${deleted} 条;${skipped.length} 条在住记录被跳过(${skipped.slice(0, 5).join('、')}${skipped.length > 5 ? '…' : ''}),请先办理退宿`
|
||||
: `批量删除成功,共 ${deleted} 条`;
|
||||
const message =
|
||||
skipped.length > 0
|
||||
? `成功删除 ${deleted} 条;${skipped.length} 条在住记录被跳过(${skipped.slice(0, 5).join('、')}${skipped.length > 5 ? '…' : ''}),请先办理退宿`
|
||||
: `批量删除成功,共 ${deleted} 条`;
|
||||
return { message, deleted, skipped: skipped.length };
|
||||
}
|
||||
|
||||
async batchCheckOut(dto: { ids: number[]; checkOutDate: string; billingEndDate?: string; checkOutReason?: string }) {
|
||||
async batchCheckOut(dto: {
|
||||
ids: number[];
|
||||
checkOutDate: string;
|
||||
billingEndDate?: string;
|
||||
checkOutReason?: string;
|
||||
}) {
|
||||
if (!dto.ids || dto.ids.length === 0) {
|
||||
throw new BadRequestException('请选择要退宿的记录');
|
||||
}
|
||||
@@ -213,9 +240,18 @@ export class OccupanciesService {
|
||||
const errors: string[] = [];
|
||||
try {
|
||||
for (const id of dto.ids) {
|
||||
const occ = await runner.manager.findOne(Occupancy, { where: { id }, relations: ['student'] });
|
||||
if (!occ) { errors.push(`记录${id}不存在`); continue; }
|
||||
if (occ.checkOutDate) { errors.push(`${occ.student?.name || id}已退宿`); continue; }
|
||||
const occ = await runner.manager.findOne(Occupancy, {
|
||||
where: { id },
|
||||
relations: ['student'],
|
||||
});
|
||||
if (!occ) {
|
||||
errors.push(`记录${id}不存在`);
|
||||
continue;
|
||||
}
|
||||
if (occ.checkOutDate) {
|
||||
errors.push(`${occ.student?.name || id}已退宿`);
|
||||
continue;
|
||||
}
|
||||
occ.checkOutDate = dto.checkOutDate;
|
||||
occ.billingEndDate = dto.billingEndDate || dto.checkOutDate;
|
||||
occ.checkOutReason = dto.checkOutReason || '';
|
||||
@@ -223,7 +259,9 @@ export class OccupanciesService {
|
||||
// 更新房间状态
|
||||
await runner.manager.update(Room, occ.roomId, { status: 'available' });
|
||||
// 如果房间已无在住人员,重置性别
|
||||
const remaining = await runner.manager.count(Occupancy, { where: { roomId: occ.roomId, checkOutDate: IsNull() } });
|
||||
const remaining = await runner.manager.count(Occupancy, {
|
||||
where: { roomId: occ.roomId, checkOutDate: IsNull() },
|
||||
});
|
||||
if (remaining === 0) {
|
||||
await runner.manager.update(Room, occ.roomId, { gender: null as any });
|
||||
}
|
||||
@@ -236,7 +274,12 @@ export class OccupanciesService {
|
||||
} finally {
|
||||
await runner.release();
|
||||
}
|
||||
return { success, failed: errors.length, message: `已成功退宿 ${success} 人${errors.length > 0 ? `,${errors.length} 条失败` : ''}`, errors: errors.length > 0 ? errors : undefined };
|
||||
return {
|
||||
success,
|
||||
failed: errors.length,
|
||||
message: `已成功退宿 ${success} 人${errors.length > 0 ? `,${errors.length} 条失败` : ''}`,
|
||||
errors: errors.length > 0 ? errors : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,13 +287,24 @@ export class OccupanciesService {
|
||||
* 每行数据:姓名、电话、学号、房间号、楼栋、入住日期
|
||||
* 自动创建不存在的学生和宿舍,并登记入住
|
||||
*/
|
||||
async batchImportCheckIn(rows: {
|
||||
name: string; phone?: string; idNumber?: string;
|
||||
gender?: string; ethnicity?: string; emergencyContact?: string; emergencyPhone?: string;
|
||||
organization?: string; supervisor?: string;
|
||||
roomNumber: string; building?: string;
|
||||
checkInDate: string; checkOutDate?: string;
|
||||
}[], options?: { autoDeposit?: boolean; depositAmount?: number }) {
|
||||
async batchImportCheckIn(
|
||||
rows: {
|
||||
name: string;
|
||||
phone?: string;
|
||||
idNumber?: string;
|
||||
gender?: string;
|
||||
ethnicity?: string;
|
||||
emergencyContact?: string;
|
||||
emergencyPhone?: string;
|
||||
organization?: string;
|
||||
supervisor?: string;
|
||||
roomNumber: string;
|
||||
building?: string;
|
||||
checkInDate: string;
|
||||
checkOutDate?: string;
|
||||
}[],
|
||||
options?: { autoDeposit?: boolean; depositAmount?: number },
|
||||
) {
|
||||
let imported = 0;
|
||||
let skipped = 0;
|
||||
let depositsCreated = 0;
|
||||
@@ -269,17 +323,19 @@ export class OccupanciesService {
|
||||
// 1. 查找或创建学生
|
||||
let student = await this.studentRepo.findOne({ where: { name: row.name.trim() } });
|
||||
if (!student) {
|
||||
student = await this.studentRepo.save(this.studentRepo.create({
|
||||
name: row.name.trim(),
|
||||
phone: row.phone?.trim() || undefined,
|
||||
idNumber: row.idNumber?.trim() || undefined,
|
||||
gender: row.gender?.trim() || undefined,
|
||||
ethnicity: row.ethnicity?.trim() || undefined,
|
||||
emergencyContact: row.emergencyContact?.trim() || undefined,
|
||||
emergencyPhone: row.emergencyPhone?.trim() || undefined,
|
||||
organization: row.organization?.trim() || undefined,
|
||||
supervisor: row.supervisor?.trim() || undefined,
|
||||
}));
|
||||
student = await this.studentRepo.save(
|
||||
this.studentRepo.create({
|
||||
name: row.name.trim(),
|
||||
phone: row.phone?.trim() || undefined,
|
||||
idNumber: row.idNumber?.trim() || undefined,
|
||||
gender: row.gender?.trim() || undefined,
|
||||
ethnicity: row.ethnicity?.trim() || undefined,
|
||||
emergencyContact: row.emergencyContact?.trim() || undefined,
|
||||
emergencyPhone: row.emergencyPhone?.trim() || undefined,
|
||||
organization: row.organization?.trim() || undefined,
|
||||
supervisor: row.supervisor?.trim() || undefined,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// 更新已有学生的缺失信息
|
||||
const updates: any = {};
|
||||
@@ -287,10 +343,14 @@ export class OccupanciesService {
|
||||
if (!student.idNumber && row.idNumber?.trim()) updates.idNumber = row.idNumber.trim();
|
||||
if (!student.gender && row.gender?.trim()) updates.gender = row.gender.trim();
|
||||
if (!student.ethnicity && row.ethnicity?.trim()) updates.ethnicity = row.ethnicity.trim();
|
||||
if (!student.emergencyContact && row.emergencyContact?.trim()) updates.emergencyContact = row.emergencyContact.trim();
|
||||
if (!student.emergencyPhone && row.emergencyPhone?.trim()) updates.emergencyPhone = row.emergencyPhone.trim();
|
||||
if (!student.organization && row.organization?.trim()) updates.organization = row.organization.trim();
|
||||
if (!student.supervisor && row.supervisor?.trim()) updates.supervisor = row.supervisor.trim();
|
||||
if (!student.emergencyContact && row.emergencyContact?.trim())
|
||||
updates.emergencyContact = row.emergencyContact.trim();
|
||||
if (!student.emergencyPhone && row.emergencyPhone?.trim())
|
||||
updates.emergencyPhone = row.emergencyPhone.trim();
|
||||
if (!student.organization && row.organization?.trim())
|
||||
updates.organization = row.organization.trim();
|
||||
if (!student.supervisor && row.supervisor?.trim())
|
||||
updates.supervisor = row.supervisor.trim();
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await this.studentRepo.update(student.id, updates);
|
||||
Object.assign(student, updates);
|
||||
@@ -301,19 +361,26 @@ export class OccupanciesService {
|
||||
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: row.building?.trim() || 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: row.building?.trim() || parsed.building || undefined,
|
||||
floor: parsed.floor || undefined,
|
||||
capacity: parsed.capacity || 4,
|
||||
roomType: parsed.roomType || undefined,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 检查是否已有活跃入住
|
||||
const existing = await this.repo.findOne({ where: { studentId: student.id, checkOutDate: IsNull() }, relations: ['room'] });
|
||||
const existing = await this.repo.findOne({
|
||||
where: { studentId: student.id, checkOutDate: IsNull() },
|
||||
relations: ['room'],
|
||||
});
|
||||
if (existing) {
|
||||
errors.push(`第${rowNum}行: ${row.name} 已在住(${existing.room?.roomNumber || '房间' + existing.roomId}),跳过`);
|
||||
errors.push(
|
||||
`第${rowNum}行: ${row.name} 已在住(${existing.room?.roomNumber || '房间' + existing.roomId}),跳过`,
|
||||
);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
@@ -321,14 +388,18 @@ export class OccupanciesService {
|
||||
// 4. 检查宿舍容量
|
||||
const count = await this.repo.count({ where: { roomId: room.id, checkOutDate: IsNull() } });
|
||||
if (count >= room.capacity) {
|
||||
errors.push(`第${rowNum}行: 宿舍 ${row.roomNumber} 已满(${count}/${room.capacity}),跳过 ${row.name}`);
|
||||
errors.push(
|
||||
`第${rowNum}行: 宿舍 ${row.roomNumber} 已满(${count}/${room.capacity}),跳过 ${row.name}`,
|
||||
);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 5. 房间级别性别约束
|
||||
if (student.gender && room.gender && student.gender !== room.gender) {
|
||||
errors.push(`第${rowNum}行: 宿舍 ${row.roomNumber} 为${room.gender}生寝室,${row.name}(${student.gender})无法入住,跳过`);
|
||||
errors.push(
|
||||
`第${rowNum}行: 宿舍 ${row.roomNumber} 为${room.gender}生寝室,${row.name}(${student.gender})无法入住,跳过`,
|
||||
);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
@@ -361,15 +432,19 @@ export class OccupanciesService {
|
||||
|
||||
// 9. 自动收取押金(仅对新入住且非历史记录的学生)
|
||||
if (options?.autoDeposit && !row.checkOutDate?.trim()) {
|
||||
const existingDeposit = await this.depositRepo.findOne({ where: { studentId: student.id, status: 'paid' } });
|
||||
const existingDeposit = await this.depositRepo.findOne({
|
||||
where: { studentId: student.id, status: 'paid' },
|
||||
});
|
||||
if (!existingDeposit) {
|
||||
await this.depositRepo.save(this.depositRepo.create({
|
||||
studentId: student.id,
|
||||
amount: options.depositAmount || 500,
|
||||
paidDate: checkInDate,
|
||||
status: 'paid',
|
||||
notes: '入住导入自动收取',
|
||||
}));
|
||||
await this.depositRepo.save(
|
||||
this.depositRepo.create({
|
||||
studentId: student.id,
|
||||
amount: options.depositAmount || 500,
|
||||
paidDate: checkInDate,
|
||||
status: 'paid',
|
||||
notes: '入住导入自动收取',
|
||||
}),
|
||||
);
|
||||
depositsCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user