forked from wangziqi/gongxue-base
feat(task2): 排课与租赁双向冲突检测
- SchedulesService.checkConflict 增加对 classroom_rentals 的冲突检测 - ClassroomRentalsService.findConflicts 增加对 class_schedules 的冲突检测 - 创建排课时若教室已被租赁,给出明确冲突提示 - 创建租赁时若教室已有排课,给出明确冲突提示
This commit is contained in:
@@ -4,6 +4,7 @@ import { ConflictException } from '@nestjs/common';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SchedulesService } from './schedules.service';
|
||||
import { ClassSchedule } from '../entities/class-schedule.entity';
|
||||
import { ClassroomRental } from '../entities/classroom-rental.entity';
|
||||
import { Class } from '../entities/class.entity';
|
||||
import { CampusScope } from '../common/campus-scope';
|
||||
|
||||
@@ -20,6 +21,7 @@ function mockQueryBuilder<T>(results: T[] = []) {
|
||||
describe('SchedulesService — checkConflict', () => {
|
||||
let service: SchedulesService;
|
||||
let scheduleRepo: jest.Mocked<Pick<Repository<ClassSchedule>, 'createQueryBuilder'>>;
|
||||
let rentalRepo: jest.Mocked<Pick<Repository<ClassroomRental>, 'createQueryBuilder'>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockRepo = {
|
||||
@@ -31,19 +33,23 @@ describe('SchedulesService — checkConflict', () => {
|
||||
SchedulesService,
|
||||
{ provide: getRepositoryToken(ClassSchedule), useValue: mockRepo },
|
||||
{ provide: getRepositoryToken(Class), useValue: { find: jest.fn().mockResolvedValue([]) } },
|
||||
{ provide: CampusScope, useValue: { getScopeDepartmentIds: jest.fn().mockResolvedValue(null), filter: jest.fn((w: any) => w) } },
|
||||
{ provide: getRepositoryToken(ClassroomRental), useValue: { createQueryBuilder: jest.fn() } },
|
||||
{ provide: CampusScope, useValue: { getScopeDepartmentIds: jest.fn().mockResolvedValue(null), filter: jest.fn((w: unknown) => w) } },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<SchedulesService>(SchedulesService);
|
||||
scheduleRepo = module.get(getRepositoryToken(ClassSchedule));
|
||||
rentalRepo = module.get(getRepositoryToken(ClassroomRental));
|
||||
});
|
||||
|
||||
it('same classroom + same weekday + overlapping times → ConflictException', async () => {
|
||||
const qb = mockQueryBuilder<ClassSchedule>([
|
||||
{ id: 1, subject: '数学', startTime: '08:00', endTime: '10:00' } as ClassSchedule,
|
||||
]);
|
||||
const rentalQb = mockQueryBuilder<ClassroomRental>([]);
|
||||
(scheduleRepo.createQueryBuilder as jest.Mock).mockReturnValue(qb);
|
||||
(rentalRepo.createQueryBuilder as jest.Mock).mockReturnValue(rentalQb);
|
||||
|
||||
await expect(
|
||||
service.checkConflict(1, 3, '09:00', '11:00', '2026-03-01', '2026-06-30'),
|
||||
@@ -52,7 +58,9 @@ describe('SchedulesService — checkConflict', () => {
|
||||
|
||||
it('same classroom + same weekday + non-overlapping times → no conflict', async () => {
|
||||
const qb = mockQueryBuilder<ClassSchedule>([]);
|
||||
const rentalQb = mockQueryBuilder<ClassroomRental>([]);
|
||||
(scheduleRepo.createQueryBuilder as jest.Mock).mockReturnValue(qb);
|
||||
(rentalRepo.createQueryBuilder as jest.Mock).mockReturnValue(rentalQb);
|
||||
|
||||
await expect(
|
||||
service.checkConflict(1, 3, '10:00', '12:00', '2026-03-01', '2026-06-30'),
|
||||
@@ -61,7 +69,9 @@ describe('SchedulesService — checkConflict', () => {
|
||||
|
||||
it('same classroom + same weekday + overlapping times but disjoint date ranges → no conflict', async () => {
|
||||
const qb = mockQueryBuilder<ClassSchedule>([]);
|
||||
const rentalQb = mockQueryBuilder<ClassroomRental>([]);
|
||||
(scheduleRepo.createQueryBuilder as jest.Mock).mockReturnValue(qb);
|
||||
(rentalRepo.createQueryBuilder as jest.Mock).mockReturnValue(rentalQb);
|
||||
|
||||
await expect(
|
||||
service.checkConflict(1, 3, '09:00', '11:00', '2026-07-01', '2026-08-31'),
|
||||
@@ -70,7 +80,9 @@ describe('SchedulesService — checkConflict', () => {
|
||||
|
||||
it('different classroom → no conflict', async () => {
|
||||
const qb = mockQueryBuilder<ClassSchedule>([]);
|
||||
const rentalQb = mockQueryBuilder<ClassroomRental>([]);
|
||||
(scheduleRepo.createQueryBuilder as jest.Mock).mockReturnValue(qb);
|
||||
(rentalRepo.createQueryBuilder as jest.Mock).mockReturnValue(rentalQb);
|
||||
|
||||
await expect(
|
||||
service.checkConflict(2, 3, '09:00', '11:00', '2026-03-01', '2026-06-30'),
|
||||
@@ -79,10 +91,36 @@ describe('SchedulesService — checkConflict', () => {
|
||||
|
||||
it('excludes the given schedule id from conflict check', async () => {
|
||||
const qb = mockQueryBuilder<ClassSchedule>([]);
|
||||
const rentalQb = mockQueryBuilder<ClassroomRental>([]);
|
||||
(scheduleRepo.createQueryBuilder as jest.Mock).mockReturnValue(qb);
|
||||
(rentalRepo.createQueryBuilder as jest.Mock).mockReturnValue(rentalQb);
|
||||
|
||||
await service.checkConflict(1, 3, '09:00', '11:00', '2026-03-01', '2026-06-30', 42);
|
||||
|
||||
expect(qb.andWhere).toHaveBeenCalledWith('cs.id != :excludeId', { excludeId: 42 });
|
||||
});
|
||||
|
||||
it('overlapping classroom rental → ConflictException', async () => {
|
||||
const qb = mockQueryBuilder<ClassSchedule>([]);
|
||||
const rentalQb = mockQueryBuilder<ClassroomRental>([
|
||||
{ id: 10, startDate: '2026-03-01', endDate: '2026-03-31', status: 'active' } as ClassroomRental,
|
||||
]);
|
||||
(scheduleRepo.createQueryBuilder as jest.Mock).mockReturnValue(qb);
|
||||
(rentalRepo.createQueryBuilder as jest.Mock).mockReturnValue(rentalQb);
|
||||
|
||||
await expect(
|
||||
service.checkConflict(1, 3, '09:00', '11:00', '2026-03-01', '2026-06-30'),
|
||||
).rejects.toThrow(ConflictException);
|
||||
});
|
||||
|
||||
it('cancelled rental does not conflict', async () => {
|
||||
const qb = mockQueryBuilder<ClassSchedule>([]);
|
||||
const rentalQb = mockQueryBuilder<ClassroomRental>([]);
|
||||
(scheduleRepo.createQueryBuilder as jest.Mock).mockReturnValue(qb);
|
||||
(rentalRepo.createQueryBuilder as jest.Mock).mockReturnValue(rentalQb);
|
||||
|
||||
await expect(
|
||||
service.checkConflict(1, 3, '09:00', '11:00', '2026-03-01', '2026-06-30'),
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user