fix permissions and teacher attendance workflows

This commit is contained in:
2026-07-10 20:40:52 +08:00
parent 247879f276
commit 8ed1682b90
95 changed files with 4745 additions and 1237 deletions

View File

@@ -4,20 +4,35 @@ import { Repository, Like, Not, In, FindOptionsWhere } from 'typeorm';
import { Student } from '../entities/student.entity';
import { Class } from '../entities/class.entity';
import { ClassStudent } from '../entities/class-student.entity';
import { ClassTeacher } from '../entities/class-teacher.entity';
import { AttendanceRecord } from '../entities/attendance-record.entity';
import { CreateStudentDto, UpdateStudentDto } from './dto/student.dto';
@Injectable()
export class StudentsService {
constructor(
@InjectRepository(Student) private repo: Repository<Student>,
@InjectRepository(ClassStudent) private classStudentRepo: Repository<ClassStudent>,
@InjectRepository(Class) private classRepo: Repository<Class>,
@InjectRepository(AttendanceRecord) private attendanceRepo: Repository<AttendanceRecord>,
@InjectRepository(ClassTeacher) private classTeacherRepo: Repository<ClassTeacher>,
) {}
async findAll(query?: { name?: string; status?: string; includeArchived?: boolean; tenantId?: number | string }) {
async getAccessibleClassIds(userId: number, canManageAll = false): Promise<number[] | undefined> {
if (canManageAll) return undefined;
const assignments = await this.classTeacherRepo.find({ where: { userId } });
return [...new Set(assignments.map((assignment) => assignment.classId))];
}
async findAll(
query?: {
name?: string;
status?: string;
includeArchived?: boolean;
tenantId?: number | string;
},
accessibleClassIds?: number[],
) {
const where: FindOptionsWhere<Student> = {};
if (query?.name) where.name = Like(`%${query.name}%`);
if (query?.tenantId) where.tenantId = Number(query.tenantId);
@@ -26,6 +41,15 @@ export class StudentsService {
} else if (!query?.includeArchived) {
where.status = Not(In(['archived', 'staff']));
}
if (accessibleClassIds) {
if (accessibleClassIds.length === 0) return [];
const classStudents = await this.classStudentRepo.find({
where: { classId: In(accessibleClassIds), status: 'active' },
});
const studentIds = [...new Set(classStudents.map((item) => item.studentId))];
if (studentIds.length === 0) return [];
where.id = In(studentIds);
}
return this.repo.find({ where, order: { createdAt: 'DESC' }, relations: ['tenant'] });
}
@@ -141,6 +165,74 @@ export class StudentsService {
};
}
async matchImport(
rows: {
name: string;
phone?: string;
idNumber?: string;
gender?: string;
ethnicity?: string;
emergencyContact?: string;
emergencyPhone?: string;
organization?: string;
supervisor?: string;
tenantId?: number;
}[],
) {
let matched = 0;
let skipped = 0;
for (const row of rows) {
if (!row.name || !row.name.trim()) {
skipped++;
continue;
}
// Match by phone first, then idNumber
let student = row.phone?.trim()
? await this.repo.findOne({ where: { phone: row.phone.trim() } })
: null;
if (!student && row.idNumber?.trim()) {
student = await this.repo.findOne({ where: { idNumber: row.idNumber.trim() } });
}
if (!student) {
skipped++;
continue;
}
// Update matched student with non-empty imported fields
const updates: Partial<
Pick<
Student,
| 'name'
| 'phone'
| 'idNumber'
| 'gender'
| 'ethnicity'
| 'emergencyContact'
| 'emergencyPhone'
| 'organization'
| 'supervisor'
| 'tenantId'
>
> = {};
if (row.name?.trim()) updates.name = row.name.trim();
if (row.phone?.trim()) updates.phone = row.phone.trim();
if (row.idNumber?.trim()) updates.idNumber = row.idNumber.trim();
if (row.gender) updates.gender = row.gender;
if (row.ethnicity) updates.ethnicity = row.ethnicity;
if (row.emergencyContact) updates.emergencyContact = row.emergencyContact;
if (row.emergencyPhone) updates.emergencyPhone = row.emergencyPhone;
if (row.organization) updates.organization = row.organization;
if (row.supervisor) updates.supervisor = row.supervisor;
if (row.tenantId) updates.tenantId = row.tenantId;
await this.repo.update(student.id, updates as Partial<Student>);
matched++;
}
return {
message: `匹配更新 ${matched} 人,跳过 ${skipped} 条(无匹配)`,
matched,
skipped,
};
}
async compareClasses(studentId: number) {
const student = await this.repo.findOne({ where: { id: studentId } });
if (!student) throw new NotFoundException('学生不存在');