fix permissions and teacher attendance workflows

This commit is contained in:
2026-07-10 20:40:52 +08:00
parent 247879f276
commit 8ed1682b90
95 changed files with 4745 additions and 1237 deletions

View File

@@ -5,7 +5,7 @@ import {
ConflictException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Not } from 'typeorm';
import { Repository, Not, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { ClassroomRental } from '../entities/classroom-rental.entity';
import { Classroom } from '../entities/classroom.entity';
import { Tenant } from '../entities/tenant.entity';
@@ -80,6 +80,47 @@ export class ClassroomRentalsService {
return rental;
}
async getUnavailableDates(classroomId: number, year: number, month: number, excludeId?: number) {
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
const monthStart = `${year}-${String(month).padStart(2, '0')}-01`;
const monthEnd = `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
const [rentals, schedules] = await Promise.all([
this.repo.find({
where: {
...(excludeId ? { id: Not(excludeId) } : {}),
classroomId,
status: Not('cancelled'),
startDate: LessThanOrEqual(monthEnd),
endDate: MoreThanOrEqual(monthStart),
},
}),
this.scheduleRepo.find({
where: {
classroomId,
status: 'active',
scheduleType: 'INTERNAL',
startDate: LessThanOrEqual(monthEnd),
endDate: MoreThanOrEqual(monthStart),
},
}),
]);
const unavailableDates = new Set<string>();
for (const rental of rentals) {
this.addDateRange(
unavailableDates,
rental.startDate > monthStart ? rental.startDate : monthStart,
rental.endDate < monthEnd ? rental.endDate : monthEnd,
);
}
for (const schedule of schedules) {
this.addScheduleOccurrences(unavailableDates, schedule, monthStart, monthEnd);
}
return { dates: Array.from(unavailableDates).sort() };
}
/**
* 查找与给定区间冲突的租赁订单,同时检测同一教室同一日期段的内部排课
* 重叠判定start1 <= end2 AND start2 <= end1
@@ -96,13 +137,17 @@ export class ClassroomRentalsService {
const rentals = await qb.getMany();
// 检测同一教室同一日期段是否存在内部排课
const scheduleConflicts = await this.scheduleRepo
const scheduleCandidates = await this.scheduleRepo
.createQueryBuilder('cs')
.where('cs.classroomId = :cid', { cid: classroomId })
.andWhere('cs.status = :status', { status: 'active' })
.andWhere('cs.scheduleType = :scheduleType', { scheduleType: 'INTERNAL' })
.andWhere('cs.startDate <= :end', { end: endDate })
.andWhere('cs.endDate >= :start', { start: startDate })
.getMany();
const scheduleConflicts = scheduleCandidates.filter((schedule) =>
this.hasScheduleOccurrence(schedule, startDate, endDate),
);
if (scheduleConflicts.length > 0) {
throw new ConflictException({
@@ -119,6 +164,48 @@ export class ClassroomRentalsService {
return rentals;
}
private hasScheduleOccurrence(schedule: ClassSchedule, startDate: string, endDate: string): boolean {
const overlapStart = schedule.startDate > startDate ? schedule.startDate : startDate;
const overlapEnd = schedule.endDate < endDate ? schedule.endDate : endDate;
if (overlapStart > overlapEnd) return false;
const startUtc = this.toUtcDate(overlapStart);
const endUtc = this.toUtcDate(overlapEnd);
const startWeekDay = startUtc.getUTCDay() || 7;
const daysUntilOccurrence = (schedule.weekDay - startWeekDay + 7) % 7;
startUtc.setUTCDate(startUtc.getUTCDate() + daysUntilOccurrence);
return startUtc <= endUtc;
}
private toUtcDate(date: string): Date {
const [year, month, day] = date.split('-').map(Number);
return new Date(Date.UTC(year, month - 1, day));
}
private addDateRange(dates: Set<string>, startDate: string, endDate: string) {
const current = this.toUtcDate(startDate);
const end = this.toUtcDate(endDate);
while (current <= end) {
dates.add(current.toISOString().slice(0, 10));
current.setUTCDate(current.getUTCDate() + 1);
}
}
private addScheduleOccurrences(dates: Set<string>, schedule: ClassSchedule, startDate: string, endDate: string) {
const overlapStart = schedule.startDate > startDate ? schedule.startDate : startDate;
const overlapEnd = schedule.endDate < endDate ? schedule.endDate : endDate;
if (overlapStart > overlapEnd) return;
const current = this.toUtcDate(overlapStart);
const end = this.toUtcDate(overlapEnd);
const startWeekDay = current.getUTCDay() || 7;
current.setUTCDate(current.getUTCDate() + ((schedule.weekDay - startWeekDay + 7) % 7));
while (current <= end) {
dates.add(current.toISOString().slice(0, 10));
current.setUTCDate(current.getUTCDate() + 7);
}
}
async create(dto: CreateRentalDto, userId?: number) {
if (dto.startDate > dto.endDate) throw new BadRequestException('起始日期不能晚于结束日期');
const classroom = await this.classroomRepo.findOne({ where: { id: dto.classroomId } });