refactor(server): remove Department/UserDepartment entities, CampusScope, and departmentId from all entities

- Delete department.entity.ts, user-department.entity.ts
- Remove Department/UserDepartment from entities/index.ts
- Remove departmentId column from 18 entities (AttendanceRecord, ArchiveAttachment, Bill, ClassSchedule, Classroom, ClassroomRental, Deposit, DepositInstallment, ExamScore, LearningRecord, Occupancy, PersonalExpense, ResultArchive, Room, RoomExpense, Student, StudentEnrollment, StudentProfile, StudentReport)
- Remove departments/ module entirely
- Delete campus-scope.ts, campus-scope.middleware.ts (request-utils.ts kept — it's just IP extraction)
- Simplify common.module.ts to empty module
- Remove CampusScopeMiddleware from app.module.ts
- Remove all CampusScope injections and filter calls across all services
- Remove departmentId from all DTOs and controllers
- Simplify dingtalk/wecom sync to only sync users (no dept table)
- Update seed module to remove department seeding
- Clean frontend compilation
This commit is contained in:
2026-07-09 17:51:32 +08:00
parent b0f7883f33
commit 6029d8e2fd
68 changed files with 220 additions and 1516 deletions

View File

@@ -13,7 +13,7 @@ import { Class } from '../entities/class.entity';
import { Deposit } from '../entities/deposit.entity';
import { ClassroomRental } from '../entities/classroom-rental.entity';
import { ClassTeacher } from '../entities/class-teacher.entity';
import { CampusScope } from '../common/campus-scope';
@Injectable()
export class DashboardService {
@@ -31,23 +31,20 @@ export class DashboardService {
@InjectRepository(Deposit) private depositRepo: Repository<Deposit>,
@InjectRepository(ClassroomRental) private rentalRepo: Repository<ClassroomRental>,
@InjectRepository(ClassTeacher) private classTeacherRepo: Repository<ClassTeacher>,
private readonly scope: CampusScope,
) {}
async getStats() {
const today = new Date();
const todayStr = today.toISOString().slice(0, 10);
const currentMonth = todayStr.slice(0, 7); // YYYY-MM
const scopeIds = await this.scope.getScopeDepartmentIds();
const totalRooms = await this.roomRepo.count({ where: await this.scope.filter({ status: Not('archived') }) });
const totalStudents = await this.studentRepo.count({ where: await this.scope.filter({ status: 'active' }) });
const occupiedBeds = await this.occRepo.count({ where: await this.scope.filter({ checkOutDate: IsNull() }) });
const totalRooms = await this.roomRepo.count({ where: { status: Not('archived') } });
const totalStudents = await this.studentRepo.count({ where: { status: 'active' } });
const occupiedBeds = await this.occRepo.count({ where: { checkOutDate: IsNull() } });
const capQb = this.roomRepo
.createQueryBuilder('r')
.select('SUM(r.capacity)', 'total')
.where('r.status != :archived', { archived: 'archived' });
if (scopeIds) capQb.andWhere('r.departmentId IN (:...scopeIds)', { scopeIds });
const totalCapacity = await capQb.getRawOne();
const cap = totalCapacity?.total || 0;
const occupancyRate = cap > 0 ? ((occupiedBeds / cap) * 100).toFixed(1) : 0;
@@ -58,11 +55,10 @@ export class DashboardService {
.addSelect('COUNT(*)', 'count')
.addSelect('SUM(b.totalAmount)', 'total')
.groupBy('b.status');
if (scopeIds) billStatsQb.andWhere('b.departmentId IN (:...scopeIds)', { scopeIds });
const billStats = await billStatsQb.getRawMany();
// New fields
const classroomCount = await this.classroomRepo.count({ where: await this.scope.filter({}) });
const classroomCount = await this.classroomRepo.count({ where: {} });
const occQb = this.scheduleRepo
.createQueryBuilder('s')
@@ -70,7 +66,6 @@ export class DashboardService {
.where('s.status = :active', { active: 'active' })
.andWhere('s.startDate <= :today', { today: todayStr })
.andWhere('s.endDate >= :today', { today: todayStr });
if (scopeIds) occQb.andWhere('s.departmentId IN (:...scopeIds)', { scopeIds });
const occResult = await occQb.getRawOne();
const occupiedClassrooms = parseInt(occResult?.cnt || '0', 10);
const classroomOccupancyRate = classroomCount > 0
@@ -83,7 +78,6 @@ export class DashboardService {
.addSelect('COUNT(*)', 'count')
.where('a.attendanceDate = :today', { today: todayStr })
.groupBy('a.status');
if (scopeIds) attTodayQb.andWhere('a.departmentId IN (:...scopeIds)', { scopeIds });
const attTodayStats = await attTodayQb.getRawMany();
const todayTotal = attTodayStats.reduce((sum, r) => sum + parseInt(r.count, 10), 0);
const todayPresent = attTodayStats
@@ -99,7 +93,6 @@ export class DashboardService {
.where('b.status = :paid', { paid: 'paid' })
.andWhere('b.periodStart >= :start', { start: `${currentMonth}-01` })
.andWhere('b.periodStart < :end', { end: this.nextMonth(currentMonth) });
if (scopeIds) incomeQb.andWhere('b.departmentId IN (:...scopeIds)', { scopeIds });
const incomeResult = await incomeQb.getRawOne();
const monthlyIncome = parseFloat(incomeResult?.total || '0');
@@ -107,9 +100,8 @@ export class DashboardService {
const incomeTrend = await this.getIncomeTrend(currentMonth);
// --- New stats ---
const classCount = await this.classRepo.count({ where: await this.scope.filter({}) });
const classCount = await this.classRepo.count({ where: {} });
// classTeacherRepo does not have departmentId — skip scope filtering
const teacherResult = await this.classTeacherRepo
.createQueryBuilder('ct')
.select('COUNT(DISTINCT ct.userId)', 'cnt')
@@ -121,11 +113,10 @@ export class DashboardService {
.select('SUM(d.amount)', 'total')
.where('d.status = :paid', { paid: 'paid' })
.andWhere('d.refundStatus IS NULL');
if (scopeIds) pendingQb.andWhere('d.departmentId IN (:...scopeIds)', { scopeIds });
const pendingResult = await pendingQb.getRawOne();
const pendingDeposits = parseFloat(pendingResult?.total || '0');
const activeRentals = await this.rentalRepo.count({ where: await this.scope.filter({ endDate: MoreThanOrEqual(todayStr) }) });
const activeRentals = await this.rentalRepo.count({ where: { endDate: MoreThanOrEqual(todayStr) } });
const occByBldQb = this.occRepo
.createQueryBuilder('o')
@@ -133,7 +124,6 @@ export class DashboardService {
.select('r.building', 'building')
.addSelect('COUNT(*)', 'count')
.where('o.checkOutDate IS NULL');
if (scopeIds) occByBldQb.andWhere('r.departmentId IN (:...scopeIds)', { scopeIds });
const occupancyByBuilding = await occByBldQb.groupBy('r.building').getRawMany();
const attendanceByStatus = attTodayStats.reduce((acc, r) => {
@@ -147,7 +137,6 @@ export class DashboardService {
.addSelect('SUM(e.amount)', 'total')
.where('e.periodStart >= :start', { start: `${currentMonth}-01` })
.andWhere('e.periodEnd <= :end', { end: this.nextMonth(currentMonth) });
if (scopeIds) expByTypeQb.andWhere('e.departmentId IN (:...scopeIds)', { scopeIds });
const expenseByType = await expByTypeQb.groupBy('e.expenseType').getRawMany();
return {
@@ -238,7 +227,6 @@ export class DashboardService {
// 甘特图数据:每个宿舍的入住时间线
async getGanttData(query?: { periodStart?: string; periodEnd?: string; building?: string }) {
const scopeIds = await this.scope.getScopeDepartmentIds();
const qb = this.occRepo
.createQueryBuilder('o')
.leftJoinAndSelect('o.student', 'student')
@@ -247,7 +235,6 @@ export class DashboardService {
.orderBy('room.roomNumber', 'ASC')
.addOrderBy('o.checkInDate', 'ASC');
if (scopeIds) qb.andWhere('room.departmentId IN (:...scopeIds)', { scopeIds });
if (query?.building) {
qb.andWhere('room.building = :building', { building: query.building });
@@ -283,13 +270,11 @@ export class DashboardService {
}
// 费用统计
async getExpenseStats(periodStart?: string, periodEnd?: string) {
const scopeIds = await this.scope.getScopeDepartmentIds();
const qb = this.expRepo
.createQueryBuilder('e')
.select('e.expenseType', 'type')
.addSelect('SUM(e.amount)', 'total')
.groupBy('e.expenseType');
if (scopeIds) qb.andWhere('e.departmentId IN (:...scopeIds)', { scopeIds });
if (periodStart) qb.andWhere('e.periodStart >= :ps', { ps: periodStart });
if (periodEnd) qb.andWhere('e.periodEnd <= :pe', { pe: periodEnd });
return qb.getRawMany();
@@ -297,7 +282,6 @@ export class DashboardService {
// 各宿舍费用排行
async getRoomExpenseRanking(periodStart?: string, periodEnd?: string) {
const scopeIds = await this.scope.getScopeDepartmentIds();
const qb = this.expRepo
.createQueryBuilder('e')
.leftJoin('e.room', 'room')
@@ -307,7 +291,6 @@ export class DashboardService {
.groupBy('e.roomId')
.orderBy('total', 'DESC')
.limit(20);
if (scopeIds) qb.andWhere('e.departmentId IN (:...scopeIds)', { scopeIds });
if (periodStart) qb.andWhere('e.periodStart >= :ps', { ps: periodStart });
if (periodEnd) qb.andWhere('e.periodEnd <= :pe', { pe: periodEnd });
return qb.getRawMany();
@@ -315,7 +298,6 @@ export class DashboardService {
// 班级考勤排行
async getClassAttendanceRanking() {
const scopeIds = await this.scope.getScopeDepartmentIds();
const qb = this.attendanceRepo
.createQueryBuilder('a')
.leftJoin('a.class', 'class')
@@ -324,7 +306,6 @@ export class DashboardService {
.addSelect('a.status', 'status')
.addSelect('COUNT(*)', 'count')
.groupBy('class.id').addGroupBy('class.name').addGroupBy('a.status');
if (scopeIds) qb.andWhere('a.departmentId IN (:...scopeIds)', { scopeIds });
const raw = await qb.getRawMany();
const classMap = new Map<number, { className: string; present: number; total: number }>();
@@ -345,9 +326,8 @@ export class DashboardService {
}
async getClassroomOccupancy() {
const scopeIds = await this.scope.getScopeDepartmentIds();
const classrooms = await this.classroomRepo.find({
where: await this.scope.filter({ status: Not('archived') }),
where: { status: Not('archived') },
order: { building: 'ASC', name: 'ASC' },
});
const today = new Date().toISOString().slice(0, 10);
@@ -359,7 +339,6 @@ export class DashboardService {
.andWhere('s.scheduleType = :type', { type: 'INTERNAL' })
.andWhere('s.startDate <= :today AND s.endDate >= :today', { today })
.groupBy('s.classroomId');
if (scopeIds) schedQb.andWhere('s.departmentId IN (:...scopeIds)', { scopeIds });
const schedules = await schedQb.getRawMany();
const rentalQb = this.rentalRepo
.createQueryBuilder('r')
@@ -368,7 +347,6 @@ export class DashboardService {
.where('r.status != :cancelled', { cancelled: 'cancelled' })
.andWhere('r.startDate <= :today AND r.endDate >= :today', { today })
.groupBy('r.classroomId');
if (scopeIds) rentalQb.andWhere('r.departmentId IN (:...scopeIds)', { scopeIds });
const rentals = await rentalQb.getRawMany();
const sMap: Record<number, number> = {};
const rMap: Record<number, number> = {};
@@ -385,9 +363,8 @@ export class DashboardService {
}
async getClassroomUtilizationStats() {
const scopeIds = await this.scope.getScopeDepartmentIds();
const totalClassrooms = await this.classroomRepo.count({
where: await this.scope.filter({ status: Not('archived') }),
where: { status: Not('archived') },
});
const today = new Date().toISOString().slice(0, 10);
@@ -399,7 +376,6 @@ export class DashboardService {
.where('s.status = :active', { active: 'active' })
.andWhere('s.scheduleType = :type', { type: 'INTERNAL' })
.andWhere('s.startDate <= :today AND s.endDate >= :today', { today });
if (scopeIds) schedQb.andWhere('s.departmentId IN (:...scopeIds)', { scopeIds });
const schedResult = await schedQb.getRawOne();
// Count classrooms with active rentals today
@@ -408,7 +384,6 @@ export class DashboardService {
.select('COUNT(DISTINCT r.classroomId)', 'cnt')
.where('r.status != :cancelled', { cancelled: 'cancelled' })
.andWhere('r.startDate <= :today AND r.endDate >= :today', { today });
if (scopeIds) rentalQb.andWhere('r.departmentId IN (:...scopeIds)', { scopeIds });
const rentalResult = await rentalQb.getRawOne();
// Combine: use Set merge of both
@@ -419,7 +394,6 @@ export class DashboardService {
.andWhere('s.scheduleType = :type', { type: 'INTERNAL' })
.andWhere('s.startDate <= :today AND s.endDate >= :today', { today })
.groupBy('s.classroomId');
if (scopeIds) combinedQb.andWhere('s.departmentId IN (:...scopeIds)', { scopeIds });
const schedIds = await combinedQb.getRawMany();
const combinedRentalQb = this.rentalRepo
@@ -428,7 +402,6 @@ export class DashboardService {
.where('r.status != :cancelled', { cancelled: 'cancelled' })
.andWhere('r.startDate <= :today AND r.endDate >= :today', { today })
.groupBy('r.classroomId');
if (scopeIds) combinedRentalQb.andWhere('r.departmentId IN (:...scopeIds)', { scopeIds });
const rentalIds = await combinedRentalQb.getRawMany();
const allInUseIds = new Set([