import { Repository, DataSource } from 'typeorm'; import { OccupanciesService } from './occupancies.service'; import { Occupancy } from '../entities/occupancy.entity'; import { Room } from '../entities/room.entity'; import { Student } from '../entities/student.entity'; import { Deposit } from '../entities/deposit.entity'; import { Bed } from '../entities/bed.entity'; import { Locker } from '../entities/locker.entity'; describe('OccupanciesService — responsible organization', () => { it('always takes the responsible organization from the student', async () => { const occupancyRepo = { findOne: jest.fn().mockResolvedValue(null), count: jest.fn().mockResolvedValue(0), create: jest.fn((value) => value), save: jest.fn(async (value) => ({ ...value, id: 10 })), } as any as Repository; const roomRepo = { findOne: jest.fn().mockResolvedValue({ id: 2, capacity: 4 }), update: jest.fn(), } as any as Repository; const studentRepo = { findOne: jest.fn().mockResolvedValue({ id: 3, gender: '男', organizationId: 7 }), } as any as Repository; const service = new OccupanciesService( occupancyRepo, roomRepo, studentRepo, {} as Repository, { findOne: jest.fn().mockResolvedValue({ id: 4, roomId: 2, status: 'available' }), update: jest.fn(), } as any as Repository, {} as Repository, {} as Repository, {} as DataSource, ); await service.checkIn({ studentId: 3, roomId: 2, checkInDate: '2026-07-10', bedId: 4, responsibleOrganizationId: 99, } as any); expect(occupancyRepo.create).toHaveBeenCalledWith( expect.objectContaining({ responsibleOrganizationId: 7 }), ); }); }); describe('OccupanciesService — manual check-in deposit', () => { const createService = (existingDeposit: Deposit | null = null) => { const occupancyRepo = { findOne: jest.fn().mockResolvedValue(null), count: jest.fn().mockResolvedValue(0), create: jest.fn((value) => value), save: jest.fn(async (value) => ({ ...value, id: 10 })), } as any as Repository; const roomRepo = { findOne: jest.fn().mockResolvedValue({ id: 2, capacity: 4 }), update: jest.fn(), } as any as Repository; const studentRepo = { findOne: jest.fn().mockResolvedValue({ id: 3, organizationId: 7 }), } as any as Repository; const depositRepo = { findOne: jest.fn().mockResolvedValue(existingDeposit), create: jest.fn((value) => value), save: jest.fn(async (value) => ({ ...value, id: 20 })), } as any as Repository; const bedRepo = { findOne: jest.fn().mockResolvedValue({ id: 4, roomId: 2, status: 'available' }), update: jest.fn(), } as any as Repository; return { service: new OccupanciesService( occupancyRepo, roomRepo, studentRepo, depositRepo, bedRepo, {} as Repository, {} as Repository, {} as DataSource, ), depositRepo, }; }; it('creates a paid deposit together with manual check-in', async () => { const { service, depositRepo } = createService(); await service.checkIn( { studentId: 3, roomId: 2, checkInDate: '2026-07-14', bedId: 4, collectDeposit: true, depositAmount: 800, }, 11, ); expect(depositRepo.create).toHaveBeenCalledWith({ studentId: 3, amount: 800, paidDate: '2026-07-14', status: 'paid', recordedBy: 11, notes: '入住登记自动收取', }); expect(depositRepo.save).toHaveBeenCalledTimes(1); }); it('does not create another paid deposit when one already exists', async () => { const { service, depositRepo } = createService({ id: 99 } as Deposit); await service.checkIn({ studentId: 3, roomId: 2, checkInDate: '2026-07-14', bedId: 4, collectDeposit: true, depositAmount: 800, }); expect(depositRepo.create).not.toHaveBeenCalled(); expect(depositRepo.save).not.toHaveBeenCalled(); }); }); describe('OccupanciesService — import bed capacity', () => { it('rejects creating a new bed when the room already has its capacity in beds', async () => { const occupancyRepo = { findOne: jest.fn().mockResolvedValue(null), count: jest.fn().mockResolvedValue(0), create: jest.fn((value) => value), save: jest.fn(), } as any as Repository; const roomRepo = { findOne: jest.fn().mockResolvedValue({ id: 2, roomNumber: '4-102', capacity: 4 }), create: jest.fn((value) => value), save: jest.fn(), update: jest.fn(), } as any as Repository; const studentRepo = { findOne: jest.fn().mockResolvedValue({ id: 3, name: '张三', organizationId: 7 }), create: jest.fn((value) => value), save: jest.fn(), update: jest.fn(), } as any as Repository; const bedRepo = { findOne: jest.fn().mockResolvedValue(null), count: jest.fn().mockResolvedValue(4), create: jest.fn((value) => value), save: jest.fn(), update: jest.fn(), } as any as Repository; const organizationRepo = { findOne: jest.fn().mockResolvedValue({ id: 7, name: '本机构', isHost: true }), create: jest.fn((value) => value), save: jest.fn(), } as any as Repository; const service = new OccupanciesService( occupancyRepo, roomRepo, studentRepo, { findOne: jest.fn(), create: jest.fn(), save: jest.fn() } as any as Repository, bedRepo, { findOne: jest.fn() } as any as Repository, organizationRepo, {} as DataSource, ); const result = await service.batchImportCheckIn([ { name: '张三', phone: '13800138000', roomNumber: '4-102', bedNumber: '5号床', checkInDate: '2026-07-14', }, ]); expect(result).toEqual( expect.objectContaining({ imported: 0, skipped: 1, errors: [expect.stringContaining('不能超过额定人数 4')], }), ); expect(bedRepo.save).not.toHaveBeenCalled(); expect(occupancyRepo.save).not.toHaveBeenCalled(); }); }); describe('OccupanciesService — import student matching', () => { it('associates an existing student by phone and keeps the student organization', async () => { const existingStudent = { id: 3, name: '学生档案姓名', phone: '13800138000', organizationId: 7, }; const occupancyRepo = { findOne: jest.fn().mockResolvedValue(null), count: jest.fn().mockResolvedValue(0), create: jest.fn((value) => value), save: jest.fn(async (value) => ({ ...value, id: 10 })), } as any as Repository; const roomRepo = { findOne: jest.fn().mockResolvedValue({ id: 2, roomNumber: '4-102', capacity: 4 }), create: jest.fn((value) => value), save: jest.fn(), update: jest.fn(), } as any as Repository; const studentRepo = { findOne: jest.fn().mockResolvedValue(existingStudent), create: jest.fn((value) => value), save: jest.fn(), update: jest.fn(), } as any as Repository; const bedRepo = { findOne: jest.fn().mockResolvedValue({ id: 4, roomId: 2, bedNumber: '1号床', status: 'available', }), count: jest.fn(), create: jest.fn((value) => value), save: jest.fn(), update: jest.fn(), } as any as Repository; const organizationRepo = { findOne: jest.fn(), create: jest.fn((value) => value), save: jest.fn(), } as any as Repository; const service = new OccupanciesService( occupancyRepo, roomRepo, studentRepo, { findOne: jest.fn() } as any as Repository, bedRepo, { findOne: jest.fn() } as any as Repository, organizationRepo, {} as DataSource, ); const result = await service.batchImportCheckIn([ { name: 'Excel姓名', phone: '13800138000', roomNumber: '4-102', bedNumber: '1号床', checkInDate: '2026-07-14', }, ]); expect(studentRepo.findOne).toHaveBeenCalledWith({ where: { phone: '13800138000' } }); expect(studentRepo.save).not.toHaveBeenCalled(); expect(organizationRepo.findOne).not.toHaveBeenCalled(); expect(occupancyRepo.create).toHaveBeenCalledWith( expect.objectContaining({ studentId: 3, responsibleOrganizationId: 7 }), ); expect(result).toEqual(expect.objectContaining({ imported: 1, skipped: 0 })); }); });