This commit is contained in:
2026-07-21 07:42:18 +08:00 Unverified
parent 1287544c41
commit e3a4ec81db
7 changed files with 139 additions and 32 deletions
+22 -10
View File
@@ -722,7 +722,14 @@ export function createAdminRoutes(context) {
if (!requirePermission(user, response, 'registrations.read')) return true;
const registrations = db.registrations.filter(registration => registrationInScope(db, user, registration)).map(registration => {
const profile = db.candidateProfiles.find(item => item.userId === registration.userId);
return { ...examRegistrationView(db, registration), candidate: profile ? { ...profile, idNumber: maskId(profile.idNumber) } : null };
const schoolClass = db.classes.find(item => item.id === profile?.classId);
return {
...examRegistrationView(db, registration),
candidate: profile ? { ...profile, idNumber: maskId(profile.idNumber) } : null,
schoolName: db.schools.find(item => item.id === profile?.schoolId)?.name || profile?.school || '',
gradeName: schoolClass?.grade || '',
className: schoolClass?.name || profile?.grade || ''
};
});
return sendJson(response, 200, { ok: true, registrations });
}
@@ -737,6 +744,7 @@ export function createAdminRoutes(context) {
...view,
candidate: profile ? { ...profile, idNumber: maskId(profile.idNumber) } : null,
schoolName: db.schools.find(item => item.id === profile?.schoolId)?.name || profile?.school || '',
gradeName: db.classes.find(item => item.id === profile?.classId)?.grade || '',
className: db.classes.find(item => item.id === profile?.classId)?.name || profile?.grade || '',
amountDue: Number(view.subjects.reduce((sum, subject) => sum + Number(subject.fee || 0), 0).toFixed(2)),
paidByName: db.users.find(item => item.id === registration.paidBy)?.displayName || ''
@@ -745,30 +753,34 @@ export function createAdminRoutes(context) {
return sendJson(response, 200, {
ok: true,
scopeLabel: adminScopeLabel(db, user),
canConfirmPayment: user.adminLevel === 'class',
canConfirmPayment: hasPermission(user, 'payments.write'),
canUpdatePayment: hasPermission(user, 'payments.write'),
registrations
});
}
const paymentMatch = pathname.match(/^\/api\/admin\/payments\/([^/]+)$/);
if (request.method === 'PATCH' && paymentMatch) {
if (user.adminLevel !== 'class') return sendError(response, 403, '缴费确认只能由考生所属班级负责人办理');
if (!requirePermission(user, response, 'payments.write')) return true;
const body = await readJson(request);
const registration = db.registrations.find(item => item.id === paymentMatch[1]);
if (!registration || !registrationInScope(db, user, registration)) return sendError(response, 404, '缴费记录不存在或不在本班范围内');
if (!registration || !registrationInScope(db, user, registration)) return sendError(response, 404, '缴费记录不存在或不在当前管理范围内');
if (registration.status !== 'approved') return sendError(response, 409, '报名审核通过后才能确认缴费');
if (db.exams.find(item => item.id === registration.examId)?.archivedAt) return sendError(response, 409, '该考试已归档,缴费记录已冻结');
if (registration.paymentStatus === 'paid') return sendError(response, 409, '该考生已经确认缴费,请勿重复操作');
registration.paymentStatus = 'paid';
registration.paidAt = nowIso();
registration.paidBy = user.id;
const nextStatus = body.status || 'paid';
if (!['paid', 'unpaid'].includes(nextStatus)) return sendError(response, 400, '缴费状态无效');
if (registration.paymentStatus === nextStatus) return sendError(response, 409, `该考生已经是${nextStatus === 'paid' ? '已缴费' : '待缴费'}状态`);
registration.paymentStatus = nextStatus;
registration.paidAt = nextStatus === 'paid' ? nowIso() : null;
registration.paidBy = nextStatus === 'paid' ? user.id : null;
const profile = db.candidateProfiles.find(item => item.userId === registration.userId);
const exam = db.exams.find(item => item.id === registration.examId);
await database.updateRegistrationPayment(
registration,
logAction(db, user, '确认考生缴费', `${profile?.name || registration.registrationNumber} · ${exam?.name || registration.examId}`)
logAction(db, user, nextStatus === 'paid' ? '标记考生已缴费' : '撤销考生缴费确认', `${profile?.name || registration.registrationNumber} · ${exam?.name || registration.examId}`)
);
return sendJson(response, 200, {
ok: true,
payment: { registrationId: registration.id, status: registration.paymentStatus, paidAt: registration.paidAt, paidBy: registration.paidBy, paidByName: user.displayName }
payment: { registrationId: registration.id, status: registration.paymentStatus, paidAt: registration.paidAt, paidBy: registration.paidBy, paidByName: nextStatus === 'paid' ? user.displayName : '' }
});
}
if (request.method === 'GET' && pathname === '/api/admin/admission-arrangements') {