实现考试归档并永久锁定成绩流程

This commit is contained in:
2026-07-20 17:08:17 +08:00 Unverified
parent 6d22b41ce8
commit 1a87e06fc8
15 changed files with 265 additions and 39 deletions
+25
View File
@@ -753,6 +753,7 @@ export function createAdminRoutes(context) {
const body = await readJson(request);
const registration = db.registrations.find(item => item.id === registrationMatch[1]);
if (!registration) return sendError(response, 404, '报名记录不存在');
if (db.exams.find(item => item.id === registration.examId)?.archivedAt) return sendError(response, 409, '该考试已归档,报名流程已冻结');
if (!registrationInScope(db, user, registration) && user.adminLevel !== 'super') return sendError(response, 403, '该报名不在你的数据范围内');
if (!['approved', 'rejected'].includes(body.status)) return sendError(response, 400, '审核状态无效');
const profile = db.candidateProfiles.find(item => item.userId === registration.userId);
@@ -802,6 +803,7 @@ export function createAdminRoutes(context) {
const note = cleanText(body.reviewNote, 300);
const action = { id: uid('flow_action'), instanceId: instance.id, actorId: user.id, action: body.status === 'approved' ? 'approve' : 'reject', note, fromAssigneeId: instance.assigneeId, toAssigneeId: null, createdAt: nowIso() };
const exam = db.exams.find(item => item.id === registration.examId);
if (exam?.archivedAt) return sendError(response, 409, '该考试已归档,成绩及复议流程已永久锁定');
const subject = exam?.subjects.find(item => item.id === result.subjectId);
const finalStep = instance.currentStep >= workflow.steps.length;
let reviewedScore = null;
@@ -841,6 +843,8 @@ export function createAdminRoutes(context) {
const arrangementMatch = pathname.match(/^\/api\/admin\/exams\/([^/]+)\/admission-arrangement(\/preview)?$/);
if (request.method === 'POST' && arrangementMatch) {
if (!requirePermission(user, response, '*')) return true;
const arrangementExam = db.exams.find(item => item.id === arrangementMatch[1]);
if (arrangementExam?.archivedAt) return sendError(response, 409, '该考试已归档,不能重新编排准考证');
const body = await readJson(request);
const generatedAt = nowIso();
const result = buildAdmissionArrangement(db, {
@@ -897,12 +901,32 @@ export function createAdminRoutes(context) {
await database.createExam(exam, log);
return sendJson(response, 201, { ok: true, exam });
}
const examArchiveMatch = pathname.match(/^\/api\/admin\/exams\/([^/]+)\/archive$/);
if (request.method === 'POST' && examArchiveMatch) {
if (!requirePermission(user, response, '*')) return true;
const exam = db.exams.find(item => item.id === examArchiveMatch[1]);
if (!exam) return sendError(response, 404, '考试不存在');
if (exam.archivedAt) return sendError(response, 409, '该考试已经归档,归档操作不可撤销');
const resultIds = new Set(db.results.filter(result => {
const registration = db.registrations.find(item => item.id === result.registrationId);
return registration?.examId === exam.id;
}).map(result => result.id));
const pendingAppeals = db.workflowInstances.filter(instance => instance.businessType === 'score_appeal' && instance.status === 'pending' && resultIds.has(instance.businessId));
if (pendingAppeals.length) return sendError(response, 409, `本场还有 ${pendingAppeals.length} 项成绩复议待处理,请先办结后再归档`);
exam.archivedAt = nowIso();
exam.archivedBy = user.id;
exam.status = 'closed';
const log = logAction(db, user, '归档考试并永久锁定成绩', `${exam.name} · ${exam.code}`);
await database.archiveExam(exam, log);
return sendJson(response, 200, { ok: true, exam: publicExam(exam), message: '考试已归档,全部成绩已永久锁定' });
}
const examMatch = pathname.match(/^\/api\/admin\/exams\/([^/]+)$/);
if (request.method === 'PATCH' && examMatch) {
if (!requirePermission(user, response, '*')) return true;
const body = await readJson(request);
const exam = db.exams.find(item => item.id === examMatch[1]);
if (!exam) return sendError(response, 404, '考试不存在');
if (exam.archivedAt) return sendError(response, 409, '该考试已归档,所有配置和成绩均已锁定');
const originalStatus = exam.status;
const detailFields = ['code', 'name', 'description', 'location', 'registrationStart', 'registrationEnd', 'examStart', 'examEnd', 'admitDownloadStart', 'admitDownloadEnd'];
const editingDetails = detailFields.some(field => body[field] != null) || body.subjects != null || body.passPolicy != null || body.passValue != null;
@@ -1021,6 +1045,7 @@ export function createAdminRoutes(context) {
const registration = db.registrations.find(item => item.id === body.registrationId && item.status === 'approved');
if (!registration) return sendError(response, 404, '已通过的报名记录不存在');
const exam = db.exams.find(item => item.id === registration.examId);
if (exam?.archivedAt) return sendError(response, 409, '该考试已归档,成绩已永久锁定');
if (!registration.subjectIds.includes(body.subjectId) || !exam.subjects.some(item => item.id === body.subjectId)) return sendError(response, 400, '该考生未报名此科目');
const score = Number(body.score);
const subject = exam.subjects.find(item => item.id === body.subjectId);