Add hierarchical region data to candidate and center records
This commit is contained in:
+22
-8
@@ -12,6 +12,7 @@ import { adminLevelNames, adminScopeLabel, createPermissionGuard, hasPermission,
|
||||
import { createSessionManager } from './src/security/session.mjs';
|
||||
import { readBodyBuffer, readJson, sendError, sendJson, sendWorkbook } from './src/http/responses.mjs';
|
||||
import { createSeedDatabase } from './src/data/seed.mjs';
|
||||
import { resolveRegion } from './src/data/region-service.mjs';
|
||||
|
||||
const root = resolve(process.cwd());
|
||||
const port = Number(process.env.PORT || 4173);
|
||||
@@ -26,7 +27,9 @@ const staticFiles = new Set([
|
||||
'/src/client/candidate-views.mjs',
|
||||
'/src/client/public-views.mjs',
|
||||
'/src/client/state.mjs',
|
||||
'/src/client/ui.mjs'
|
||||
'/src/client/ui.mjs',
|
||||
'/src/client/region-select.mjs',
|
||||
'/src/data/china-regions.mjs'
|
||||
]);
|
||||
const mimeTypes = {
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
@@ -201,8 +204,9 @@ function parseCenterChange(db, body, schoolId, center = null) {
|
||||
const code = cleanText(body.code, 30).toUpperCase();
|
||||
const name = cleanText(body.name, 100);
|
||||
const address = cleanText(body.address, 200);
|
||||
const region = resolveRegion(body);
|
||||
const rooms = Array.isArray(body.rooms) ? body.rooms : [];
|
||||
if (!code || !name || !address) throw Object.assign(new Error('请填写考点代码、名称和详细地址'), { status: 400 });
|
||||
if (!code || !name || !address || !region) throw Object.assign(new Error('请填写考点代码、名称、省市区县和详细地址'), { status: 400 });
|
||||
if (!rooms.length) throw Object.assign(new Error('请至少配置一个结构化考场'), { status: 400 });
|
||||
const duplicateCenter = db.testCenters.some(item => item.code.toUpperCase() === code && item.id !== center?.id)
|
||||
|| db.centerChangeRequests.some(item => item.status === 'pending' && item.code.toUpperCase() === code && item.centerId !== center?.id);
|
||||
@@ -227,7 +231,7 @@ function parseCenterChange(db, body, schoolId, center = null) {
|
||||
});
|
||||
return {
|
||||
center: {
|
||||
schoolId, code, name, address, contact: cleanText(body.contact, 80), managerName: cleanText(body.managerName, 50),
|
||||
schoolId, code, name, ...region, address, contact: cleanText(body.contact, 80), managerName: cleanText(body.managerName, 50),
|
||||
managerPhone: cleanText(body.managerPhone, 30), emergencyPhone: cleanText(body.emergencyPhone, 30),
|
||||
gateOpenTime: cleanText(body.gateOpenTime, 20), transport: cleanText(body.transport, 500),
|
||||
centerStatus: body.status === 'inactive' ? 'inactive' : 'active', notes: cleanText(body.notes, 1000)
|
||||
@@ -345,14 +349,18 @@ function excelRowsForResource(db, user, resource, searchParams) {
|
||||
if (resource === 'candidates') return db.candidateProfiles.filter(profile => profileInScope(user, profile)).map(profile => ({
|
||||
candidateNumber: db.users.find(item => item.id === profile.userId)?.candidateNumber || '', name: profile.name, gender: profile.gender,
|
||||
idNumber: profile.idNumber.startsWith('PENDING-') ? '' : profile.idNumber, phone: profile.phone, email: profile.email,
|
||||
nativePlace: profile.nativePlace, address: profile.address, className: db.classes.find(item => item.id === profile.classId)?.name || profile.grade,
|
||||
nativePlace: profile.nativePlace, provinceCode: profile.provinceCode, provinceName: profile.provinceName,
|
||||
cityCode: profile.cityCode, cityName: profile.cityName, districtCode: profile.districtCode, districtName: profile.districtName,
|
||||
address: profile.address, className: db.classes.find(item => item.id === profile.classId)?.name || profile.grade,
|
||||
ethnicity: profile.ethnicity, birthDate: profile.birthDate, postalCode: profile.postalCode, guardianName: profile.guardianName, guardianPhone: profile.guardianPhone
|
||||
}));
|
||||
if (resource === 'centers') return db.testCenters.filter(center => schools.some(school => school.id === center.schoolId)).flatMap(center => {
|
||||
const rooms = db.testRooms.filter(room => room.centerId === center.id);
|
||||
return (rooms.length ? rooms : [{}]).map(room => ({
|
||||
schoolCode: db.schools.find(school => school.id === center.schoolId)?.code || '', centerCode: center.code, centerName: center.name,
|
||||
address: center.address, managerName: center.managerName, managerPhone: center.managerPhone, contact: center.contact,
|
||||
provinceCode: center.provinceCode, provinceName: center.provinceName, cityCode: center.cityCode, cityName: center.cityName,
|
||||
districtCode: center.districtCode, districtName: center.districtName, address: center.address,
|
||||
managerName: center.managerName, managerPhone: center.managerPhone, contact: center.contact,
|
||||
emergencyPhone: center.emergencyPhone, gateOpenTime: center.gateOpenTime, transport: center.transport,
|
||||
centerStatus: center.status === 'inactive' ? '停用' : '启用', centerNotes: center.notes,
|
||||
roomCode: room.code || '', roomName: room.name || '', building: room.building || '', floor: room.floor || '', capacity: room.capacity || '',
|
||||
@@ -436,9 +444,12 @@ async function importExcelResource(db, user, resource, rows) {
|
||||
if (!schoolClass) throw excelImportError(row, '班级名称无效');
|
||||
const required = ['name', 'gender', 'idNumber', 'phone'];
|
||||
if (required.some(key => !cleanText(row[key], 200))) throw excelImportError(row, '姓名、性别、证件号码和手机号必填');
|
||||
const region = resolveRegion(row);
|
||||
if (!region) throw excelImportError(row, '省、市或区县代码无效,或上下级不匹配');
|
||||
Object.assign(profile, {
|
||||
name: cleanText(row.name, 50), gender: cleanText(row.gender, 10), idNumber: cleanText(row.idNumber, 40), phone: cleanText(row.phone, 30),
|
||||
email: cleanText(row.email, 100), nativePlace: cleanText(row.nativePlace, 100), address: cleanText(row.address, 200), classId: schoolClass.id,
|
||||
email: cleanText(row.email, 100), nativePlace: cleanText(row.nativePlace, 100), ...region,
|
||||
address: cleanText(row.address, 200), classId: schoolClass.id,
|
||||
grade: schoolClass.name, ethnicity: cleanText(row.ethnicity, 30), birthDate: cleanText(row.birthDate, 20), postalCode: cleanText(row.postalCode, 20),
|
||||
guardianName: cleanText(row.guardianName, 50), guardianPhone: cleanText(row.guardianPhone, 30), profileCompleted: true, status: 'pending', reviewNote: '', reviewedAt: null, reviewerId: null, updatedAt: nowIso()
|
||||
});
|
||||
@@ -458,7 +469,9 @@ async function importExcelResource(db, user, resource, rows) {
|
||||
if (existing && existing.schoolId !== school.id) throw excelImportError(first, '考点代码已属于其他学校');
|
||||
if (existing && db.centerChangeRequests.some(item => item.centerId === existing.id && item.status === 'pending')) throw excelImportError(first, '该考点已有待审批变更');
|
||||
const body = {
|
||||
schoolId: school.id, code: centerCode, name: first.centerName, address: first.address, managerName: first.managerName,
|
||||
schoolId: school.id, code: centerCode, name: first.centerName,
|
||||
provinceCode: first.provinceCode, cityCode: first.cityCode, districtCode: first.districtCode,
|
||||
address: first.address, managerName: first.managerName,
|
||||
managerPhone: first.managerPhone, contact: first.contact, emergencyPhone: first.emergencyPhone, gateOpenTime: first.gateOpenTime,
|
||||
transport: first.transport, status: first.centerStatus === '停用' ? 'inactive' : 'active', notes: first.centerNotes,
|
||||
rooms: centerRows.map(row => ({ code: row.roomCode, name: row.roomName, building: row.building, floor: row.floor, capacity: Number(row.capacity), seatPlan: row.seatPlan,
|
||||
@@ -554,7 +567,8 @@ const routeContext = {
|
||||
hasExcelResource,
|
||||
parseWorkbook,
|
||||
adminLevelNames,
|
||||
permissionsByLevel
|
||||
permissionsByLevel,
|
||||
resolveRegion
|
||||
};
|
||||
const handlePublic = createPublicRoutes(routeContext);
|
||||
const handleAuth = createAuthRoutes(routeContext);
|
||||
|
||||
Reference in New Issue
Block a user