159 lines
5.6 KiB
TypeScript
159 lines
5.6 KiB
TypeScript
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { ClassSchedule } from '../entities';
|
|
import { CampusScope } from '../common/campus-scope';
|
|
import {
|
|
CreateScheduleDto,
|
|
UpdateScheduleDto,
|
|
QueryScheduleDto,
|
|
WeeklyViewQueryDto,
|
|
} from './dto/schedule.dto';
|
|
|
|
@Injectable()
|
|
export class SchedulesService {
|
|
constructor(
|
|
@InjectRepository(ClassSchedule)
|
|
private readonly scheduleRepo: Repository<ClassSchedule>,
|
|
private readonly scope: CampusScope,
|
|
) {}
|
|
|
|
async findAll(query: QueryScheduleDto) {
|
|
const qb = this.scheduleRepo.createQueryBuilder('cs');
|
|
const scopeIds = await this.scope.getScopeDepartmentIds();
|
|
if (scopeIds) {
|
|
qb.andWhere('cs.departmentId IN (:...scopeIds)', { scopeIds });
|
|
}
|
|
|
|
if (query.classroomId) qb.andWhere('cs.classroomId = :classroomId', { classroomId: query.classroomId });
|
|
if (query.classId) qb.andWhere('cs.classId = :classId', { classId: query.classId });
|
|
if (query.weekDay) qb.andWhere('cs.weekDay = :weekDay', { weekDay: query.weekDay });
|
|
if (query.startDate) qb.andWhere('cs.startDate >= :startDate', { startDate: query.startDate });
|
|
if (query.endDate) qb.andWhere('cs.endDate <= :endDate', { endDate: query.endDate });
|
|
|
|
qb.orderBy('cs.weekDay', 'ASC')
|
|
.addOrderBy('cs.startTime', 'ASC');
|
|
|
|
return qb.getMany();
|
|
}
|
|
|
|
async findOne(id: number) {
|
|
const schedule = await this.scheduleRepo.findOne({ where: { id } });
|
|
if (!schedule) throw new NotFoundException('排课记录不存在');
|
|
return schedule;
|
|
}
|
|
|
|
async create(dto: CreateScheduleDto) {
|
|
await this.checkConflict(dto.classroomId, dto.weekDay, dto.startTime, dto.endTime, dto.startDate, dto.endDate);
|
|
|
|
const schedule = this.scheduleRepo.create(dto);
|
|
const saved = await this.scheduleRepo.save(schedule);
|
|
return this.findOne(saved.id);
|
|
}
|
|
|
|
async update(id: number, dto: UpdateScheduleDto) {
|
|
const existing = await this.scheduleRepo.findOne({ where: { id } });
|
|
if (!existing) throw new NotFoundException('排课记录不存在');
|
|
|
|
// If classroom, weekDay, or times are changing, check conflicts excluding self
|
|
const classroomId = dto.classroomId ?? existing.classroomId;
|
|
const weekDay = dto.weekDay ?? existing.weekDay;
|
|
const startTime = dto.startTime ?? existing.startTime;
|
|
const endTime = dto.endTime ?? existing.endTime;
|
|
const startDate = dto.startDate ?? existing.startDate;
|
|
const endDate = dto.endDate ?? existing.endDate;
|
|
|
|
await this.checkConflict(classroomId, weekDay, startTime, endTime, startDate, endDate, id);
|
|
|
|
await this.scheduleRepo.update(id, dto as Record<string, unknown>);
|
|
return this.findOne(id);
|
|
}
|
|
|
|
async remove(id: number) {
|
|
const schedule = await this.scheduleRepo.findOne({ where: { id } });
|
|
if (!schedule) throw new NotFoundException('排课记录不存在');
|
|
await this.scheduleRepo.remove(schedule);
|
|
return { success: true };
|
|
}
|
|
|
|
async checkConflict(
|
|
classroomId: number,
|
|
weekDay: number,
|
|
startTime: string,
|
|
endTime: string,
|
|
startDate: string,
|
|
endDate: string,
|
|
excludeId?: number,
|
|
) {
|
|
const qb = this.scheduleRepo
|
|
.createQueryBuilder('cs')
|
|
.where('cs.classroomId = :classroomId', { classroomId })
|
|
.andWhere('cs.weekDay = :weekDay', { weekDay })
|
|
.andWhere('cs.status = :status', { status: 'active' })
|
|
.andWhere('cs.startTime < :endTime', { endTime })
|
|
.andWhere('cs.endTime > :startTime', { startTime })
|
|
.andWhere('cs.startDate <= :endDate', { endDate })
|
|
.andWhere('cs.endDate >= :startDate', { startDate });
|
|
|
|
if (excludeId) qb.andWhere('cs.id != :excludeId', { excludeId });
|
|
|
|
const conflicts = await qb.getMany();
|
|
if (conflicts.length > 0) {
|
|
throw new ConflictException(
|
|
`该时间段与已有排课冲突: ${conflicts.map((c) => `${c.subject}(${c.startTime}-${c.endTime})`).join(', ')}`,
|
|
);
|
|
}
|
|
return conflicts;
|
|
}
|
|
|
|
async getWeeklyView(query: WeeklyViewQueryDto) {
|
|
const qb = this.scheduleRepo.createQueryBuilder('cs');
|
|
const scopeIds = await this.scope.getScopeDepartmentIds();
|
|
if (scopeIds) {
|
|
qb.andWhere('cs.departmentId IN (:...scopeIds)', { scopeIds });
|
|
}
|
|
if (query.classroomId) {
|
|
qb.andWhere('cs.classroomId = :classroomId', { classroomId: query.classroomId });
|
|
}
|
|
if (query.startDate) {
|
|
qb.andWhere('cs.endDate >= :startDate', { startDate: query.startDate });
|
|
}
|
|
if (query.endDate) {
|
|
qb.andWhere('cs.startDate <= :endDate', { endDate: query.endDate });
|
|
}
|
|
|
|
const schedules = await qb
|
|
.andWhere('cs.status = :status', { status: 'active' })
|
|
.orderBy('cs.weekDay', 'ASC')
|
|
.addOrderBy('cs.startTime', 'ASC')
|
|
.getMany();
|
|
|
|
// Group by classroomId → weekDay
|
|
const matrix: Record<number, Record<number, typeof schedules>> = {};
|
|
for (const s of schedules) {
|
|
if (!matrix[s.classroomId]) matrix[s.classroomId] = {};
|
|
if (!matrix[s.classroomId][s.weekDay]) matrix[s.classroomId][s.weekDay] = [];
|
|
matrix[s.classroomId][s.weekDay].push(s);
|
|
}
|
|
|
|
return matrix;
|
|
}
|
|
|
|
async getClassroomOccupancy(classroomId: number, date?: string) {
|
|
const qb = this.scheduleRepo
|
|
.createQueryBuilder('cs')
|
|
.where('cs.classroomId = :classroomId', { classroomId })
|
|
.andWhere('cs.status = :status', { status: 'active' });
|
|
|
|
if (date) {
|
|
qb.andWhere('cs.startDate <= :date', { date })
|
|
.andWhere('cs.endDate >= :date', { date });
|
|
}
|
|
|
|
return qb
|
|
.orderBy('cs.weekDay', 'ASC')
|
|
.addOrderBy('cs.startTime', 'ASC')
|
|
.getMany();
|
|
}
|
|
}
|