forked from wangziqi/gongxue-base
test: harden business boundary conditions
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, IsNull, Not, MoreThanOrEqual, In } from 'typeorm';
|
||||
import { Room } from '../entities/room.entity';
|
||||
@@ -40,8 +40,7 @@ export class DashboardService {
|
||||
}
|
||||
|
||||
async getStats(accessibleClassIds?: number[]) {
|
||||
const today = new Date();
|
||||
const todayStr = today.toISOString().slice(0, 10);
|
||||
const todayStr = this.getChinaDate(new Date());
|
||||
const currentMonth = todayStr.slice(0, 7); // YYYY-MM
|
||||
|
||||
const totalRooms = await this.roomRepo.count({ where: { status: Not('archived') } });
|
||||
@@ -180,6 +179,10 @@ export class DashboardService {
|
||||
accessibleClassIds?: number[],
|
||||
) {
|
||||
if (accessibleClassIds) {
|
||||
if (accessibleClassIds.length === 0) {
|
||||
qb.andWhere('1 = 0');
|
||||
return;
|
||||
}
|
||||
qb.andWhere(`${alias}.classId IN (:...accessibleClassIds)`, { accessibleClassIds });
|
||||
}
|
||||
}
|
||||
@@ -260,6 +263,7 @@ export class DashboardService {
|
||||
|
||||
// 甘特图数据:每个宿舍的入住时间线
|
||||
async getGanttData(query?: { periodStart?: string; periodEnd?: string; building?: string }) {
|
||||
this.assertPeriodRange(query?.periodStart, query?.periodEnd);
|
||||
const qb = this.occRepo
|
||||
.createQueryBuilder('o')
|
||||
.leftJoinAndSelect('o.student', 'student')
|
||||
@@ -302,6 +306,7 @@ export class DashboardService {
|
||||
}
|
||||
// 费用统计
|
||||
async getExpenseStats(periodStart?: string, periodEnd?: string) {
|
||||
this.assertPeriodRange(periodStart, periodEnd);
|
||||
const qb = this.expRepo
|
||||
.createQueryBuilder('e')
|
||||
.select('e.expenseType', 'type')
|
||||
@@ -314,6 +319,7 @@ export class DashboardService {
|
||||
|
||||
// 各宿舍费用排行
|
||||
async getRoomExpenseRanking(periodStart?: string, periodEnd?: string) {
|
||||
this.assertPeriodRange(periodStart, periodEnd);
|
||||
const qb = this.expRepo
|
||||
.createQueryBuilder('e')
|
||||
.leftJoin('e.room', 'room')
|
||||
@@ -368,7 +374,7 @@ export class DashboardService {
|
||||
where: { status: 'available' as const },
|
||||
order: { building: 'ASC', name: 'ASC' },
|
||||
});
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const today = this.getChinaDate(new Date());
|
||||
const schedQb = this.scheduleRepo
|
||||
.createQueryBuilder('s')
|
||||
.select('s.classroomId', 'classroomId')
|
||||
@@ -400,12 +406,31 @@ export class DashboardService {
|
||||
}));
|
||||
}
|
||||
|
||||
private assertPeriodRange(periodStart?: string, periodEnd?: string) {
|
||||
if (periodStart && periodEnd && periodStart > periodEnd) {
|
||||
throw new BadRequestException('结束日期不能早于开始日期');
|
||||
}
|
||||
}
|
||||
|
||||
private getChinaDate(date: Date): string {
|
||||
const parts = new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
}).formatToParts(date);
|
||||
const values = Object.fromEntries(
|
||||
parts.filter((part) => part.type !== 'literal').map((part) => [part.type, part.value]),
|
||||
);
|
||||
return `${values.year}-${values.month}-${values.day}`;
|
||||
}
|
||||
|
||||
async getClassroomUtilizationStats() {
|
||||
const totalClassrooms = await this.classroomRepo.count({
|
||||
where: { status: 'available' as const },
|
||||
});
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const today = this.getChinaDate(new Date());
|
||||
|
||||
// Count classrooms with active schedules today
|
||||
const schedQb = this.scheduleRepo
|
||||
|
||||
Reference in New Issue
Block a user