优化2 patch2
This commit is contained in:
@@ -276,6 +276,7 @@ export function createAdminRoutes(context) {
|
||||
const plan = existing || { id: uid('plan'), kind: 'plan', examId: exam.id, schoolId: school.id, createdAt: now };
|
||||
Object.assign(plan, { userId: user.id, status: 'approved', updatedAt: now, payload: { categories, note: cleanText(body.note, 500), submittedBy: user.displayName, reviewedBy: user.displayName, reviewedAt: now, reviewNote: '超级管理员代上传并审核通过' } });
|
||||
await database.saveAdmissionRecord(plan, logAction(db, user, '代上传招生计划', `${school.name} · ${exam.name}`));
|
||||
await cache.invalidate('public');
|
||||
return sendJson(response, existing ? 200 : 201, { ok: true, plan });
|
||||
}
|
||||
const planReviewMatch = pathname.match(/^\/api\/admin\/admission-plans\/([^/]+)$/);
|
||||
@@ -289,6 +290,7 @@ export function createAdminRoutes(context) {
|
||||
plan.updatedAt = nowIso();
|
||||
plan.payload = { ...plan.payload, reviewNote: cleanText(body.reviewNote, 500), reviewedBy: user.displayName, reviewedAt: plan.updatedAt };
|
||||
await database.saveAdmissionRecord(plan, logAction(db, user, body.status === 'approved' ? '审核通过招生计划' : '退回招生计划', plan.id));
|
||||
await cache.invalidate('public');
|
||||
return sendJson(response, 200, { ok: true, plan });
|
||||
}
|
||||
const actionMatch = pathname.match(/^\/api\/admin\/admissions\/([^/]+)\/(match|finalize|supplementary)$/);
|
||||
@@ -350,7 +352,7 @@ export function createAdminRoutes(context) {
|
||||
if (resource === 'results' && !hasPermission(user, 'results.read')) return sendError(response, 403, '当前账号不能导出成绩');
|
||||
const requestUrl = new URL(request.url, `http://${request.headers.host || '127.0.0.1'}`);
|
||||
const template = requestUrl.searchParams.get('template') === '1';
|
||||
const rows = template ? [] : excelRowsForResource(db, user, resource, requestUrl.searchParams);
|
||||
const rows = template && resource !== 'results' ? [] : excelRowsForResource(db, user, resource, requestUrl.searchParams);
|
||||
const subtitle = user.adminLevel === 'super' ? '全部数据范围' : adminScopeLabel(db, user);
|
||||
const buffer = Buffer.from(await buildWorkbook(resource, rows, { template, subtitle }));
|
||||
return sendWorkbook(response, buffer, `${excelResourceNames[resource]}-${template ? '导入模板' : '导出'}-${new Date().toISOString().slice(0, 10)}.xlsx`);
|
||||
@@ -1389,7 +1391,8 @@ export function createAdminRoutes(context) {
|
||||
const view = examRegistrationView(db, item);
|
||||
const profile = db.candidateProfiles.find(profileItem => profileItem.userId === item.userId);
|
||||
const account = db.users.find(accountItem => accountItem.id === item.userId);
|
||||
return { ...view, candidateName: profile?.name || account?.displayName || '', candidateNumber: account?.candidateNumber || item.registrationNumber || '', schoolName: profile?.school || '', className: db.classes.find(classItem => classItem.id === profile?.classId)?.name || profile?.grade || '' };
|
||||
const specialty = resolveProfileSpecialty(profile || {});
|
||||
return { ...view, candidateName: profile?.name || account?.displayName || '', candidateNumber: account?.candidateNumber || item.registrationNumber || '', schoolName: profile?.school || '', className: db.classes.find(classItem => classItem.id === profile?.classId)?.name || profile?.grade || '', specialtyCategory: specialty.category, specialtyType: specialty.type, specialtyLabel: specialtyLabel(specialty.category, specialty.type) || '普通生' };
|
||||
}) : [];
|
||||
return sendJson(response, 200, { ok: true, results, appeals, registrations, exams, resultCache: { enabled: cache.enabled, status: cache.status } });
|
||||
}
|
||||
@@ -1399,6 +1402,53 @@ export function createAdminRoutes(context) {
|
||||
const result = await commitResultImport(db, user, body.rows);
|
||||
return sendJson(response, 200, { ok: true, ...result });
|
||||
}
|
||||
if (request.method === 'POST' && pathname === '/api/admin/results/bulk') {
|
||||
if (!requirePermission(user, response, '*')) return true;
|
||||
const body = await readJson(request);
|
||||
const exam = db.exams.find(item => item.id === cleanText(body.examId, 64) && !item.archivedAt);
|
||||
const subject = exam?.subjects.find(item => item.id === cleanText(body.subjectId, 64));
|
||||
if (!exam || !subject) return sendError(response, 400, '请选择有效且未归档的考试科目');
|
||||
const sourceRows = [];
|
||||
const seen = new Set();
|
||||
for (const [index, row] of (Array.isArray(body.rows) ? body.rows : []).entries()) {
|
||||
const registration = db.registrations.find(item => item.id === row.registrationId && item.examId === exam.id && item.status === 'approved' && item.subjectIds.includes(subject.id));
|
||||
if (!registration || seen.has(registration.id)) return sendError(response, 400, `第 ${index + 1} 条考生成绩无效或重复`);
|
||||
seen.add(registration.id);
|
||||
const account = db.users.find(item => item.id === registration.userId);
|
||||
sourceRows.push({
|
||||
__row: index + 3,
|
||||
candidateNumber: account?.candidateNumber || registration.registrationNumber || '',
|
||||
examCode: exam.code,
|
||||
subjectName: subject.name,
|
||||
score: row.score,
|
||||
published: body.published === true
|
||||
});
|
||||
}
|
||||
if (!sourceRows.length) return sendError(response, 400, '没有需要保存的成绩');
|
||||
const result = await commitResultImport(db, user, sourceRows);
|
||||
return sendJson(response, 200, { ok: true, published: body.published === true, ...result });
|
||||
}
|
||||
if (request.method === 'POST' && pathname === '/api/admin/feature-scores/bulk') {
|
||||
if (!requirePermission(user, response, '*')) return true;
|
||||
const body = await readJson(request);
|
||||
const exam = db.exams.find(item => item.id === cleanText(body.examId, 64) && !item.archivedAt);
|
||||
if (!exam) return sendError(response, 400, '请选择有效且未归档的考试');
|
||||
const entries = [];
|
||||
const seen = new Set();
|
||||
for (const [index, row] of (Array.isArray(body.rows) ? body.rows : []).entries()) {
|
||||
const registration = db.registrations.find(item => item.id === row.registrationId && item.examId === exam.id && item.status === 'approved');
|
||||
const featureScore = Number(row.featureScore);
|
||||
if (!registration || seen.has(registration.id)) return sendError(response, 400, `第 ${index + 1} 条考生记录无效或重复`);
|
||||
if (!Number.isFinite(featureScore) || featureScore < 0 || featureScore > 1000) return sendError(response, 400, `第 ${index + 1} 条特征分必须在 0—1000 之间`);
|
||||
seen.add(registration.id);
|
||||
registration.featureScore = Number(featureScore.toFixed(2));
|
||||
const profile = db.candidateProfiles.find(item => item.userId === registration.userId);
|
||||
entries.push({ registration, log: logAction(db, user, '批量登记特征分', `${profile?.name || registration.userId} · ${exam.name} · ${registration.featureScore}`) });
|
||||
}
|
||||
if (!entries.length) return sendError(response, 400, '没有需要保存的特征分');
|
||||
await database.updateFeatureScores(entries);
|
||||
return sendJson(response, 200, { ok: true, count: entries.length });
|
||||
}
|
||||
const featureScoreMatch = pathname.match(/^\/api\/admin\/registrations\/([^/]+)\/feature-score$/);
|
||||
if (request.method === 'PATCH' && featureScoreMatch) {
|
||||
if (!requirePermission(user, response, '*')) return true;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { noticeForClient } from '../security/notice-content.mjs';
|
||||
import { admissionRecords, admissionSetting, publicAdmissionRows, sourceSchoolQualificationStatus } from '../services/volunteer-admission.mjs';
|
||||
import { specialtyLabel } from '../data/specialty-types.mjs';
|
||||
|
||||
export function createPublicRoutes(context) {
|
||||
const {
|
||||
@@ -65,13 +66,34 @@ export function createPublicRoutes(context) {
|
||||
if (pathname === '/api/public/announcements') {
|
||||
const payload = await cache.remember('public', 'admission-announcements', async () => {
|
||||
const db = await readDb();
|
||||
const plans = admissionRecords(db, 'plan').filter(item => item.status === 'approved').map(item => ({
|
||||
id: item.id,
|
||||
examId: item.examId,
|
||||
examName: db.exams.find(exam => exam.id === item.examId)?.name || '',
|
||||
schoolName: db.schools.find(school => school.id === item.schoolId)?.name || '',
|
||||
publishedAt: item.payload?.reviewedAt || item.updatedAt,
|
||||
note: item.payload?.note || '',
|
||||
rows: (item.payload?.categories || []).map(category => ({
|
||||
code: category.code,
|
||||
name: category.name,
|
||||
quota: Number(category.quota || 0),
|
||||
specialtyCategory: category.specialtyCategory || '',
|
||||
specialtyType: category.specialtyType || '',
|
||||
specialtyLabel: specialtyLabel(category.specialtyCategory, category.specialtyType) || '普通 / 政策类',
|
||||
indicatorQuota: (category.indicatorAllocations || []).reduce((sum, allocation) => sum + Number(allocation.quota || 0), 0),
|
||||
indicatorAllocations: (category.indicatorAllocations || []).map(allocation => ({
|
||||
sourceSchoolName: db.schools.find(school => school.id === allocation.sourceSchoolId)?.name || allocation.sourceSchoolId,
|
||||
quota: Number(allocation.quota || 0)
|
||||
}))
|
||||
}))
|
||||
})).sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt));
|
||||
const qualifications = admissionRecords(db, 'qualification_publication').filter(item => item.status === 'published' && sourceSchoolQualificationStatus(db, item.examId, item.schoolId).complete).map(item => ({
|
||||
id: item.id, examId: item.examId, examName: db.exams.find(exam => exam.id === item.examId)?.name || '', schoolName: db.schools.find(school => school.id === item.schoolId)?.name || '', publishedAt: item.payload?.publishedAt || item.updatedAt,
|
||||
rows: (item.payload?.rows || []).map(row => ({ registrationNumber: row.registrationNumber, name: row.name, eligible: row.eligible === true, specialtyLabel: row.specialtyLabel || '普通生' }))
|
||||
})).sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt));
|
||||
const admissions = admissionRecords(db, 'setting').filter(item => item.status === 'completed' && item.payload?.autoPublish !== false).map(setting => ({ id: setting.id, examId: setting.examId, examName: db.exams.find(item => item.id === setting.examId)?.name || '', publishedAt: setting.payload?.completedAt || setting.updatedAt, rows: publicAdmissionRows(db, setting.examId) })).sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt));
|
||||
const cutoffs = admissionRecords(db, 'cutoff_publication').filter(item => item.status === 'published' && admissionSetting(db, item.examId)?.payload?.autoPublish !== false).map(item => ({ id: item.id, examId: item.examId, examName: db.exams.find(exam => exam.id === item.examId)?.name || '', publishedAt: item.payload?.publishedAt || item.updatedAt, rows: item.payload?.rows || [] })).sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt));
|
||||
return { ok: true, qualifications, admissions, cutoffs };
|
||||
return { ok: true, plans, qualifications, admissions, cutoffs };
|
||||
});
|
||||
return sendJson(response, 200, payload);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user