forked from wangziqi/gongxue-base
fix permissions and teacher attendance workflows
This commit is contained in:
@@ -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('学生不存在');
|
||||
|
||||
Reference in New Issue
Block a user