forked from wangziqi/gongxue-base
feat: refine admin forms, attendance and finance workflows
Squash merge PR #23. Included changes: - complete occupancy check-in required fields/default payload - improve responsive admin management pages - fix attendance edge cases and attendance period config - refine wallet/finance-related workflow handling Checks: - npm run typecheck -w apps/admin - npm run typecheck -w apps/server
This commit is contained in:
@@ -110,4 +110,14 @@ export class QueryStudentDto {
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
organizationId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
classId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
teacherId?: number;
|
||||
}
|
||||
|
||||
@@ -175,6 +175,16 @@ export class StudentsController {
|
||||
return this.service.getBasicLookups();
|
||||
}
|
||||
|
||||
@Get('filter-lookups')
|
||||
@RequirePermission('student:view')
|
||||
async getFilterLookups(@Request() req: AuthenticatedRequest) {
|
||||
const classIds = await this.service.getAccessibleClassIds(
|
||||
req.user.id,
|
||||
this.canManageAllStudents(req),
|
||||
);
|
||||
return this.service.getFilterLookups(classIds);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RequirePermission('student:view')
|
||||
async findAll(
|
||||
@@ -194,7 +204,7 @@ export class StudentsController {
|
||||
@Get('export')
|
||||
@RequirePermission('student:export')
|
||||
async exportExcel(
|
||||
@Query('includeArchived') includeArchived?: string,
|
||||
@Query() query: QueryStudentDto,
|
||||
@Res() res?: Response,
|
||||
@Request() req?: any,
|
||||
) {
|
||||
@@ -202,10 +212,7 @@ export class StudentsController {
|
||||
req.user.id,
|
||||
this.canManageAllStudents(req),
|
||||
);
|
||||
const students = await this.service.findAll(
|
||||
{ includeArchived: includeArchived === 'true' },
|
||||
classIds,
|
||||
);
|
||||
const students = await this.service.findAll(query, classIds);
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const ws = workbook.addWorksheet('学生名单');
|
||||
ws.columns = STUDENT_EXPORT_COLUMNS;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, Like, Not, In, FindOptionsWhere } from 'typeorm';
|
||||
import { Repository, Like, Not, In, FindOptionsWhere, IsNull } from 'typeorm';
|
||||
import { Student } from '../entities/student.entity';
|
||||
import { Class } from '../entities/class.entity';
|
||||
import { ClassStudent } from '../entities/class-student.entity';
|
||||
@@ -41,6 +41,8 @@ export class StudentsService {
|
||||
status?: string;
|
||||
includeArchived?: boolean;
|
||||
organizationId?: number | string;
|
||||
classId?: number | string;
|
||||
teacherId?: number | string;
|
||||
},
|
||||
accessibleClassIds?: number[],
|
||||
) {
|
||||
@@ -52,10 +54,28 @@ export class StudentsService {
|
||||
} else if (!query?.includeArchived) {
|
||||
where.status = Not(In(['archived', 'staff']));
|
||||
}
|
||||
if (accessibleClassIds) {
|
||||
if (accessibleClassIds.length === 0) return [];
|
||||
|
||||
let scopedClassIds = accessibleClassIds ? [...accessibleClassIds] : undefined;
|
||||
if (query?.teacherId) {
|
||||
const teacherAssignments = await this.classTeacherRepo.find({
|
||||
where: { userId: Number(query.teacherId) },
|
||||
});
|
||||
const teacherClassIds = [...new Set(teacherAssignments.map((item) => item.classId))];
|
||||
scopedClassIds = scopedClassIds
|
||||
? scopedClassIds.filter((classId) => teacherClassIds.includes(classId))
|
||||
: teacherClassIds;
|
||||
}
|
||||
if (query?.classId) {
|
||||
const classId = Number(query.classId);
|
||||
scopedClassIds = scopedClassIds
|
||||
? scopedClassIds.filter((accessibleClassId) => accessibleClassId === classId)
|
||||
: [classId];
|
||||
}
|
||||
|
||||
if (scopedClassIds) {
|
||||
if (scopedClassIds.length === 0) return [];
|
||||
const classStudents = await this.classStudentRepo.find({
|
||||
where: { classId: In(accessibleClassIds), status: 'active' },
|
||||
where: { classId: In(scopedClassIds), status: 'active' },
|
||||
});
|
||||
const studentIds = [...new Set(classStudents.map((item) => item.studentId))];
|
||||
if (studentIds.length === 0) return [];
|
||||
@@ -64,6 +84,42 @@ export class StudentsService {
|
||||
return this.repo.find({ where, order: { createdAt: 'DESC' }, relations: ['organization'] });
|
||||
}
|
||||
|
||||
async getFilterLookups(accessibleClassIds?: number[]) {
|
||||
if (accessibleClassIds && accessibleClassIds.length === 0) {
|
||||
return { classes: [], teachers: [] };
|
||||
}
|
||||
|
||||
const classWhere = accessibleClassIds
|
||||
? { id: In(accessibleClassIds), isArchived: false }
|
||||
: { isArchived: false };
|
||||
const classes = await this.classRepo.find({
|
||||
select: ['id', 'name', 'code'],
|
||||
where: classWhere,
|
||||
order: { name: 'ASC' },
|
||||
});
|
||||
|
||||
const teacherWhere = accessibleClassIds
|
||||
? { classId: In(accessibleClassIds) }
|
||||
: { classId: In(classes.map((item) => item.id)), userId: Not(IsNull()) };
|
||||
const assignments = classes.length
|
||||
? await this.classTeacherRepo.find({ where: teacherWhere, relations: ['user'] })
|
||||
: [];
|
||||
const teacherMap = new Map<number, { id: number; name: string; username: string }>();
|
||||
for (const assignment of assignments) {
|
||||
if (!assignment.user || !assignment.user.isActive || assignment.user.isArchived) continue;
|
||||
teacherMap.set(assignment.userId, {
|
||||
id: assignment.userId,
|
||||
name: assignment.user.name || assignment.user.username,
|
||||
username: assignment.user.username,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
classes: classes.map((item) => ({ id: item.id, name: item.name, code: item.code })),
|
||||
teachers: [...teacherMap.values()].sort((a, b) => a.name.localeCompare(b.name, 'zh-CN')),
|
||||
};
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const student = await this.repo.findOne({
|
||||
where: { id },
|
||||
|
||||
Reference in New Issue
Block a user