patch3
This commit is contained in:
@@ -225,7 +225,10 @@ export function createAdminRoutes(context) {
|
||||
const profile = db.candidateProfiles.find(item => item.userId === preference.userId) || {};
|
||||
return { ...preference, candidate: { registrationNumber: account.candidateNumber, name: profile.name }, choices: (preference.payload?.choices || []).map(choice => ({ ...choice, schoolName: db.schools.find(item => item.id === choice.schoolId)?.name || '' })) };
|
||||
});
|
||||
const schoolAccounts = db.users.filter(item => item.role === 'admission_school').map(safeUser);
|
||||
const schoolAccounts = db.users.filter(item => item.role === 'admission_school').map(item => {
|
||||
const school = db.schools.find(entry => entry.id === item.schoolId);
|
||||
return { ...safeUser(item), active: item.active !== false, createdAt: item.createdAt, schoolName: school?.name || '', schoolCode: school?.code || '' };
|
||||
});
|
||||
return sendJson(response, 200, { ok: true, settings, plans, preferences, placements, schoolAccounts, schools: db.schools.filter(item => item.active), admissionSchools: db.schools.filter(item => item.active && item.isAdmissionSchool), sourceSchools: db.schools.filter(item => item.active && item.isSourceSchool), exams: db.exams.filter(item => !item.archivedAt) });
|
||||
}
|
||||
if (pathname === '/api/admin/admission-school-accounts' && request.method === 'POST') {
|
||||
@@ -240,6 +243,30 @@ export function createAdminRoutes(context) {
|
||||
await database.createAdmissionSchoolAccount(account, logAction(db, user, '创建招生学校账号', `${school.name} · ${username}`));
|
||||
return sendJson(response, 201, { ok: true, account: safeUser(account) });
|
||||
}
|
||||
const admissionAccountMatch = pathname.match(/^\/api\/admin\/admission-school-accounts\/([^/]+)$/);
|
||||
if (admissionAccountMatch && request.method === 'PATCH') {
|
||||
if (user.adminLevel !== 'super') return sendError(response, 403, '只有超级管理员可以维护招生学校账号');
|
||||
const target = db.users.find(item => item.id === admissionAccountMatch[1] && item.role === 'admission_school');
|
||||
if (!target) return sendError(response, 404, '招生学校账号不存在');
|
||||
const body = await readJson(request);
|
||||
target.active = body.active == null ? target.active : Boolean(body.active);
|
||||
target.displayName = cleanText(body.displayName || target.displayName, 80);
|
||||
await database.updateAdmin(target, false, logAction(db, user, target.active ? '启用招生学校账号' : '停用招生学校账号', `${target.displayName} · ${target.username}`));
|
||||
if (!target.active) for (const [token, session] of sessions) if (session.userId === target.id) sessions.delete(token);
|
||||
return sendJson(response, 200, { ok: true, account: { ...safeUser(target), active: target.active } });
|
||||
}
|
||||
const admissionAccountResetMatch = pathname.match(/^\/api\/admin\/admission-school-accounts\/([^/]+)\/reset-password$/);
|
||||
if (admissionAccountResetMatch && request.method === 'POST') {
|
||||
if (user.adminLevel !== 'super') return sendError(response, 403, '只有超级管理员可以重置招生学校账号密码');
|
||||
const target = db.users.find(item => item.id === admissionAccountResetMatch[1] && item.role === 'admission_school');
|
||||
if (!target) return sendError(response, 404, '招生学校账号不存在');
|
||||
const temporaryPassword = `Reset-${randomBytes(7).toString('base64url')}`;
|
||||
target.passwordHash = hashPassword(temporaryPassword);
|
||||
target.active = true;
|
||||
await database.updateAdmin(target, true, logAction(db, user, '重置招生学校账号密码', `${target.displayName} · ${target.username}`));
|
||||
for (const [token, session] of sessions) if (session.userId === target.id) sessions.delete(token);
|
||||
return sendJson(response, 200, { ok: true, username: target.username, temporaryPassword });
|
||||
}
|
||||
const settingMatch = pathname.match(/^\/api\/admin\/admissions\/([^/]+)\/setting$/);
|
||||
if (settingMatch && request.method === 'PUT') {
|
||||
if (user.adminLevel !== 'super') return sendError(response, 403, '只有超级管理员可以设置志愿填报');
|
||||
|
||||
@@ -62,7 +62,7 @@ export function createAdmissionRoutes(context) {
|
||||
return { subjectName: exam?.subjects.find(subject => subject.id === result.subjectId)?.name || result.subjectId, score: result.score };
|
||||
});
|
||||
const qualification = resolveProfileSpecialty(profile);
|
||||
return { ...item, candidate: { registrationNumber: account.candidateNumber, name: profile.name, gender: profile.gender, idNumberMasked: maskId(profile.idNumber), specialtyCategory: qualification.category, specialtyType: qualification.type, specialtyLabel: specialtyLabel(qualification.category, qualification.type), specialtyCertificate: profile.specialtyCertificate || '', policyEligibility: profile.policyEligibility || '' }, featureScore: Number(registration?.featureScore || 0), results };
|
||||
return { ...item, examName: db.exams.find(exam => exam.id === item.examId)?.name || item.examId, candidate: { registrationNumber: account.candidateNumber, name: profile.name, gender: profile.gender, idNumberMasked: maskId(profile.idNumber), specialtyCategory: qualification.category, specialtyType: qualification.type, specialtyLabel: specialtyLabel(qualification.category, qualification.type), specialtyCertificate: profile.specialtyCertificate || '', policyEligibility: profile.policyEligibility || '' }, featureScore: Number(registration?.featureScore || 0), results };
|
||||
});
|
||||
const completedExams = db.exams.filter(exam => admissionRecords(db, 'setting', exam.id).some(setting => setting.status === 'completed') && placements.some(item => item.examId === exam.id && item.status === 'final'));
|
||||
return sendJson(response, 200, { ok: true, school, placements, completedExams });
|
||||
@@ -93,6 +93,26 @@ export function createAdmissionRoutes(context) {
|
||||
const buffer = Buffer.from(await buildWorkbook('admitted_candidates', rows, { subtitle: `${exam.name}|${school.name}` }));
|
||||
return sendWorkbook(response, buffer, `${exam.name}-${school.name}-录取考生信息.xlsx`);
|
||||
}
|
||||
if (request.method === 'POST' && pathname === '/api/admission/placements/bulk') {
|
||||
const body = await readJson(request);
|
||||
const ids = [...new Set((Array.isArray(body.ids) ? body.ids : []).map(id => cleanText(id, 64)).filter(Boolean))];
|
||||
const decision = cleanText(body.decision, 30);
|
||||
const note = cleanText(body.note, 500);
|
||||
if (!ids.length) return sendError(response, 400, '请至少选择一名待审核考生');
|
||||
if (!['accept', 'withdraw'].includes(decision)) return sendError(response, 400, '请选择接收或申请退档');
|
||||
if (decision === 'withdraw' && note.length < 8) return sendError(response, 400, '批量申请退档必须填写至少 8 个字的特殊理由');
|
||||
const placements = admissionRecords(db, 'placement').filter(item => ids.includes(item.id) && item.schoolId === school.id && item.status === 'school_review');
|
||||
if (placements.length !== ids.length) return sendError(response, 409, '所选记录中包含已处理或不属于本校的投档记录,请刷新后重试');
|
||||
const now = nowIso();
|
||||
for (const placement of placements) {
|
||||
placement.status = decision === 'accept' ? 'admitted' : 'withdrawal_pending';
|
||||
placement.payload.schoolDecisionNote = note;
|
||||
if (decision === 'withdraw') placement.payload.withdrawalReason = note;
|
||||
placement.updatedAt = now;
|
||||
}
|
||||
await database.saveAdmissionRecords(placements, logAction(db, user, decision === 'accept' ? '批量接收投档考生' : '批量申请退档', `${school.name} · ${placements.length} 人`));
|
||||
return sendJson(response, 200, { ok: true, count: placements.length, decision });
|
||||
}
|
||||
const placementMatch = pathname.match(/^\/api\/admission\/placements\/([^/]+)$/);
|
||||
if (request.method === 'PATCH' && placementMatch) {
|
||||
const placement = admissionRecords(db, 'placement').find(item => item.id === placementMatch[1] && item.schoolId === school.id);
|
||||
|
||||
Reference in New Issue
Block a user