Add structured admission plans and school role management

This commit is contained in:
2026-07-21 14:04:19 +08:00 Unverified
parent 1b6e860013
commit 5e0bee60e2
23 changed files with 483 additions and 82 deletions
+4 -3
View File
@@ -82,12 +82,11 @@ export function buildVolunteerPlacements(db, setting, { uid, nowIso }) {
const created = [];
for (const candidate of candidates) {
const specialtyTypes = Array.isArray(candidate.profile.specialtyTypes) ? candidate.profile.specialtyTypes : [];
for (const [index, choice] of (candidate.preference.payload?.choices || []).entries()) {
const target = categories.get(categoryKey(choice.schoolId, choice.categoryCode));
if (!target) continue;
const { category } = target;
if (category.specialtyType && !specialtyTypes.includes(category.specialtyType)) continue;
if (!candidateEligibleForCategory(candidate.profile, category)) continue;
const key = categoryKey(choice.schoolId, choice.categoryCode);
if ((occupied.get(key) || 0) >= Number(category.quota || 0)) continue;
const allocation = (category.indicatorAllocations || []).find(item => item.sourceSchoolId === candidate.profile.schoolId);
@@ -110,7 +109,8 @@ export function buildVolunteerPlacements(db, setting, { uid, nowIso }) {
id: uid('placement'), kind: 'placement', examId, userId: candidate.preference.userId, schoolId: choice.schoolId,
status: 'school_review', createdAt: nowIso(), updatedAt: nowIso(), payload: {
round, categoryCode: category.code, categoryName: category.name, preferenceOrder: index + 1,
totalScore: candidate.score, quotaBucket, schoolDecisionNote: '', withdrawalReason: '', withdrawalReviewNote: ''
totalScore: candidate.score, featureScore: Number(db.registrations.find(item => item.examId === examId && item.userId === candidate.preference.userId)?.featureScore || 0),
specialtyQualification: resolveProfileSpecialty(candidate.profile), quotaBucket, schoolDecisionNote: '', withdrawalReason: '', withdrawalReviewNote: ''
}
});
break;
@@ -125,3 +125,4 @@ export function remainingPlanQuota(db, plan) {
return { ...category, used, remaining: Math.max(0, Number(category.quota || 0) - used) };
});
}
import { candidateEligibleForCategory, resolveProfileSpecialty } from '../data/specialty-types.mjs';