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

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
+5 -4
View File
@@ -102,7 +102,7 @@ export function createCandidateRoutes(context) {
}
if (request.method === 'GET' && pathname === '/api/candidate/exams') {
const registrations = db.registrations.filter(item => item.userId === user.id);
const exams = db.exams.filter(item => item.status === 'published').map(exam => ({ ...publicExam(exam), registration: registrations.find(reg => reg.examId === exam.id) || null }));
const exams = db.exams.filter(item => item.status === 'published' && !item.archivedAt).map(exam => ({ ...publicExam(exam), registration: registrations.find(reg => reg.examId === exam.id) || null }));
return sendJson(response, 200, { ok: true, profileStatus: profile.status, exams });
}
if (request.method === 'GET' && pathname === '/api/candidate/registrations') {
@@ -111,7 +111,7 @@ export function createCandidateRoutes(context) {
if (request.method === 'POST' && pathname === '/api/candidate/registrations') {
if (profile.status !== 'approved') return sendError(response, 403, '个人资料审核通过后才能报名考试');
const body = await readJson(request);
const exam = db.exams.find(item => item.id === body.examId && item.status === 'published');
const exam = db.exams.find(item => item.id === body.examId && item.status === 'published' && !item.archivedAt);
if (!exam) return sendError(response, 404, '考试不存在或尚未发布');
const state = publicExam(exam).registrationState;
if (state !== 'open') return sendError(response, 400, state === 'upcoming' ? '报名尚未开始' : '报名已经截止');
@@ -134,7 +134,7 @@ export function createCandidateRoutes(context) {
const rank = resultRankInfo(db, result);
const pass = subjectPassEvaluation(db, result, subject);
return {
...result, ...rank, grade: rank.grade, examId: exam.id, examName: exam.name, examCode: exam.code, examStart: exam.examStart,
...result, ...rank, grade: rank.grade, examId: exam.id, examName: exam.name, examCode: exam.code, examStart: exam.examStart, archivedAt: exam.archivedAt || null,
subjectName: subject?.name || result.subjectId, fullScore: subject?.fullScore || 150,
passRule: subject?.passRule || 'fixed_score', passValue: subject?.passValue ?? subject?.passScore,
passScore: pass.passScore, cutoffRank: pass.cutoffRank, passText: subjectPassText(subject), qualified: pass.qualified,
@@ -149,13 +149,14 @@ export function createCandidateRoutes(context) {
const result = db.results.find(item => item.id === scoreAppealMatch[1] && item.published);
const registration = db.registrations.find(item => item.id === result?.registrationId && item.userId === user.id);
if (!result || !registration) return sendError(response, 404, '已发布成绩不存在或不属于当前考生');
const exam = db.exams.find(item => item.id === registration.examId);
if (exam?.archivedAt) return sendError(response, 409, '该考试已归档,成绩及复议入口已永久锁定');
if (pendingWorkflow(db, 'score_appeal', result.id)) return sendError(response, 409, '该科成绩已有待处理复议,请勿重复提交');
const body = await readJson(request);
const reason = cleanText(body.reason, 500);
if (reason.length < 5) return sendError(response, 400, '请至少填写 5 个字的复议理由');
const { instance, action } = createWorkflowSubmission(db, 'score_appeal', result.id, profile, user.id);
action.note = reason;
const exam = db.exams.find(item => item.id === registration.examId);
const subject = exam?.subjects.find(item => item.id === result.subjectId);
const log = logAction(db, user, '提交成绩复议', `${exam?.name || ''} · ${subject?.name || ''}`);
await database.createWorkflow(instance, action, log);