Add hierarchical region data to candidate and center records

This commit is contained in:
biss committed 2026-07-20 11:50:51 +08:00
1 parent 352f471ed0
commit c5446d5241
21 files changed
+387 -64

No files matched your search

+3 -1
View File
@@ -76,7 +76,9 @@ npm start
打开 <http://127.0.0.1:4173>。 打开 <http://127.0.0.1:4173>。
本地开发无需额外配置,首次运行会自动创建 `data/exam.sqlite` 和完整关系型数据库结构。当前处于开发阶段,不兼容旧版 JSON/单表数据库;已有关系型数据库会在启动时自动升级到 v6,补充分级权限、固定考生报名号、首次改密、注册开关、完整资料、学校组织管理、结构化考场、座位编排说明、批量建号申请与审批结果结构。 本地开发无需额外配置,首次运行会自动创建 `data/exam.sqlite` 和完整关系型数据库结构。当前处于开发阶段,不兼容旧版 JSON/单表数据库;已有关系型数据库会在启动时自动升级到 v7,补充分级权限、固定考生报名号、首次改密、注册开关、完整资料、学校组织管理、结构化考场、座位编排说明、批量建号申请与审批结果,以及考生/考点省市区县代码结构。
需要清空并重建本地演示库时运行 `npm run reset-db`。省市区县下拉数据位于 `src/data/china-regions.mjs`,当前版本为国家地名信息库截至 2025-12-31 的三级快照,并补入和康县(653228)与和安县(653229);从新版 CSV 更新时可运行 `node scripts/build-regions.mjs <CSV路径> src/data/china-regions.mjs`。
## 数据库配置 ## 数据库配置
+6 -2
View File
@@ -4,6 +4,7 @@ import { createCandidateViews } from './src/client/candidate-views.mjs';
import { createPublicViews } from './src/client/public-views.mjs'; import { createPublicViews } from './src/client/public-views.mjs';
import { state } from './src/client/state.mjs'; import { state } from './src/client/state.mjs';
import { badge, dateRange, formatDate, h, icons, money, passPolicyText, statusLabels } from './src/client/ui.mjs'; import { badge, dateRange, formatDate, h, icons, money, passPolicyText, statusLabels } from './src/client/ui.mjs';
import { formatRegionAddress, mountRegionSelects, updateRegionSelects } from './src/client/region-select.mjs';
const app = document.querySelector('#app'); const app = document.querySelector('#app');
const modalRoot = document.querySelector('#modalRoot'); const modalRoot = document.querySelector('#modalRoot');
@@ -247,6 +248,7 @@ document.addEventListener('change', event => {
classSelect.innerHTML = `<option value="">请选择班级</option>${classes.filter(item => item.schoolId === event.target.value).map(item => `<option value="${h(item.id)}">${h(item.name)}</option>`).join('')}`; classSelect.innerHTML = `<option value="">请选择班级</option>${classes.filter(item => item.schoolId === event.target.value).map(item => `<option value="${h(item.id)}">${h(item.name)}</option>`).join('')}`;
} }
} }
if (event.target.matches('[data-region-level]')) updateRegionSelects(event.target);
if (event.target.matches('[data-action="admin-level"]')) { if (event.target.matches('[data-action="admin-level"]')) {
const form = event.target.closest('form'); const form = event.target.closest('form');
form?.querySelector('[data-admin-school]')?.classList.toggle('hidden', event.target.value === 'super'); form?.querySelector('[data-admin-school]')?.classList.toggle('hidden', event.target.value === 'super');
@@ -437,6 +439,7 @@ function openCenterForm(center = null) {
const schools = state.pageData.schools || []; const schools = state.pageData.schools || [];
const rooms = center?.rooms?.length ? center.rooms : [{}]; const rooms = center?.rooms?.length ? center.rooms : [{}];
setModal(`<div class="modal-head"><div><span>CONTROLLED DOSSIER</span><h2>${center ? '提交考点档案变更' : '提交新考点档案'}</h2><p>表单将进入“考点考场变更审批”,通过前不会改动正式数据。</p></div><button data-action="close-modal">×</button></div><form class="modal-form center-dossier-form" data-form="center-form">${center ? `<input type="hidden" name="id" value="${h(center.id)}">` : ''}<section class="center-form-section"><h3>考点基本档案</h3>${state.user.adminLevel === 'super' ? `<label><span>所属学校 *</span><select name="schoolId" required>${schools.map(item => `<option value="${h(item.id)}" ${center?.schoolId === item.id ? 'selected' : ''}>${h(item.name)}</option>`).join('')}</select></label>` : ''}<div class="field-row"><label><span>考点代码 *</span><input name="code" required value="${h(center?.code || '')}" placeholder="如 HZ01-C01"></label><label><span>考点名称 *</span><input name="name" required value="${h(center?.name || '')}"></label></div><label><span>详细地址 *</span><input name="address" required value="${h(center?.address || '')}"></label><div class="field-row"><label><span>考点负责人</span><input name="managerName" value="${h(center?.managerName || '')}"></label><label><span>负责人手机</span><input name="managerPhone" value="${h(center?.managerPhone || '')}"></label></div><div class="field-row"><label><span>值班电话</span><input name="contact" value="${h(center?.contact || '')}"></label><label><span>应急电话</span><input name="emergencyPhone" value="${h(center?.emergencyPhone || '')}"></label></div><div class="field-row"><label><span>开放时间</span><input name="gateOpenTime" type="time" value="${h(center?.gateOpenTime || '')}"></label><label><span>档案状态</span><select name="status"><option value="active" ${center?.status !== 'inactive' ? 'selected' : ''}>启用</option><option value="inactive" ${center?.status === 'inactive' ? 'selected' : ''}>停用</option></select></label></div><label><span>交通与入场提示</span><textarea name="transport" rows="3">${h(center?.transport || '')}</textarea></label><label><span>考务备注</span><textarea name="notes" rows="2">${h(center?.notes || '')}</textarea></label></section><section class="center-form-section rooms-section"><header><div><h3>考场明细</h3><p>每个考场单独维护位置、容量、座位编排说明与类型。</p></div><button type="button" class="row-action" data-action="add-center-room">添加考场</button></header><div data-center-rooms>${rooms.map(room => centerRoomEditor(room)).join('')}</div></section><div class="approval-callout"><strong>提交即进入审批</strong><span>审批通过后,正式考点档案和全部考场明细会作为一个版本整体生效。</span></div><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">提交审批</button></div></form>`); setModal(`<div class="modal-head"><div><span>CONTROLLED DOSSIER</span><h2>${center ? '提交考点档案变更' : '提交新考点档案'}</h2><p>表单将进入“考点考场变更审批”,通过前不会改动正式数据。</p></div><button data-action="close-modal">×</button></div><form class="modal-form center-dossier-form" data-form="center-form">${center ? `<input type="hidden" name="id" value="${h(center.id)}">` : ''}<section class="center-form-section"><h3>考点基本档案</h3>${state.user.adminLevel === 'super' ? `<label><span>所属学校 *</span><select name="schoolId" required>${schools.map(item => `<option value="${h(item.id)}" ${center?.schoolId === item.id ? 'selected' : ''}>${h(item.name)}</option>`).join('')}</select></label>` : ''}<div class="field-row"><label><span>考点代码 *</span><input name="code" required value="${h(center?.code || '')}" placeholder="如 HZ01-C01"></label><label><span>考点名称 *</span><input name="name" required value="${h(center?.name || '')}"></label></div><label><span>详细地址 *</span><input name="address" required value="${h(center?.address || '')}"></label><div class="field-row"><label><span>考点负责人</span><input name="managerName" value="${h(center?.managerName || '')}"></label><label><span>负责人手机</span><input name="managerPhone" value="${h(center?.managerPhone || '')}"></label></div><div class="field-row"><label><span>值班电话</span><input name="contact" value="${h(center?.contact || '')}"></label><label><span>应急电话</span><input name="emergencyPhone" value="${h(center?.emergencyPhone || '')}"></label></div><div class="field-row"><label><span>开放时间</span><input name="gateOpenTime" type="time" value="${h(center?.gateOpenTime || '')}"></label><label><span>档案状态</span><select name="status"><option value="active" ${center?.status !== 'inactive' ? 'selected' : ''}>启用</option><option value="inactive" ${center?.status === 'inactive' ? 'selected' : ''}>停用</option></select></label></div><label><span>交通与入场提示</span><textarea name="transport" rows="3">${h(center?.transport || '')}</textarea></label><label><span>考务备注</span><textarea name="notes" rows="2">${h(center?.notes || '')}</textarea></label></section><section class="center-form-section rooms-section"><header><div><h3>考场明细</h3><p>每个考场单独维护位置、容量、座位编排说明与类型。</p></div><button type="button" class="row-action" data-action="add-center-room">添加考场</button></header><div data-center-rooms>${rooms.map(room => centerRoomEditor(room)).join('')}</div></section><div class="approval-callout"><strong>提交即进入审批</strong><span>审批通过后,正式考点档案和全部考场明细会作为一个版本整体生效。</span></div><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">提交审批</button></div></form>`);
mountRegionSelects(modalRoot, center);
} }
function openFlowDetail(id) { function openFlowDetail(id) {
@@ -450,7 +453,7 @@ function openFlowDetail(id) {
const finalBatchStep = isBatch && instance.currentStep >= instance.steps.length; const finalBatchStep = isBatch && instance.currentStep >= instance.steps.length;
const subject = isCenter ? instance.centerName : isBatch ? `${instance.schoolName} · ${instance.batchTotalCount} 个报名号` : instance.candidateName; const subject = isCenter ? instance.centerName : isBatch ? `${instance.schoolName} · ${instance.batchTotalCount} 个报名号` : instance.candidateName;
const subjectDetail = isCenter ? `${instance.requestType === 'create' ? '新增考点' : '修改档案'} · ${instance.schoolName}` : isBatch ? '按班级批量申领 · 批准后生成账号' : `${instance.examName ? `${instance.examName} · ` : ''}${instance.schoolName}`; const subjectDetail = isCenter ? `${instance.requestType === 'create' ? '新增考点' : '修改档案'} · ${instance.schoolName}` : isBatch ? '按班级批量申领 · 批准后生成账号' : `${instance.examName ? `${instance.examName} · ` : ''}${instance.schoolName}`;
const change = instance.centerChange; const change = instance.centerChange ? { ...instance.centerChange, address: formatRegionAddress(instance.centerChange) } : null;
const changeSnapshot = change ? `<section class="flow-center-snapshot"><header><div><span>申请快照</span><h3>${h(change.name)} · ${h(change.code)}</h3></div><b>${change.rooms.length} 个考场</b></header><dl><div><dt>地址</dt><dd>${h(change.address)}</dd></div><div><dt>负责人</dt><dd>${h(change.managerName || '未填写')} · ${h(change.managerPhone || '未填写')}</dd></div><div><dt>开放时间</dt><dd>${h(change.gateOpenTime || '未填写')}</dd></div><div><dt>档案状态</dt><dd>${change.centerStatus === 'active' ? '启用' : '停用'}</dd></div></dl><div>${change.rooms.map(room => `<span><strong>${h(room.name)}</strong><small>${h(room.building)} · ${h(room.capacity)} 席 · ${h(room.seatPlan || '按现场座次表编排')}</small></span>`).join('')}</div></section>` : ''; const changeSnapshot = change ? `<section class="flow-center-snapshot"><header><div><span>申请快照</span><h3>${h(change.name)} · ${h(change.code)}</h3></div><b>${change.rooms.length} 个考场</b></header><dl><div><dt>地址</dt><dd>${h(change.address)}</dd></div><div><dt>负责人</dt><dd>${h(change.managerName || '未填写')} · ${h(change.managerPhone || '未填写')}</dd></div><div><dt>开放时间</dt><dd>${h(change.gateOpenTime || '未填写')}</dd></div><div><dt>档案状态</dt><dd>${change.centerStatus === 'active' ? '启用' : '停用'}</dd></div></dl><div>${change.rooms.map(room => `<span><strong>${h(room.name)}</strong><small>${h(room.building)} · ${h(room.capacity)} 席 · ${h(room.seatPlan || '按现场座次表编排')}</small></span>`).join('')}</div></section>` : '';
const batch = instance.accountBatch; const batch = instance.accountBatch;
const batchSnapshot = batch ? `<section class="flow-batch-snapshot"><header><div><span>班级配额</span><h3>${batch.totalCount} 个待建账户</h3></div><b>${batch.quotas.length} 个班级</b></header><div>${batch.quotas.map(item => `<span><strong>${h(item.className)}</strong><small>${item.count} 人</small></span>`).join('')}</div><p>最终批准时才生成固定报名号和随机初始密码。</p></section>` : ''; const batchSnapshot = batch ? `<section class="flow-batch-snapshot"><header><div><span>班级配额</span><h3>${batch.totalCount} 个待建账户</h3></div><b>${batch.quotas.length} 个班级</b></header><div>${batch.quotas.map(item => `<span><strong>${h(item.className)}</strong><small>${item.count} 人</small></span>`).join('')}</div><p>最终批准时才生成固定报名号和随机初始密码。</p></section>` : '';
@@ -458,7 +461,8 @@ function openFlowDetail(id) {
} }
function openCandidateReview(id) { function openCandidateReview(id) {
const item = state.pageData.candidates.find(candidate => candidate.id === id); const source = state.pageData.candidates.find(candidate => candidate.id === id);
const item = { ...source, address: formatRegionAddress(source) };
const canReview = state.user.adminLevel !== 'class' && item.status === 'pending'; const canReview = state.user.adminLevel !== 'class' && item.status === 'pending';
setModal(`<div class="modal-head"><div><span>CANDIDATE REVIEW</span><h2>${canReview ? '处理' : '查看'} ${h(item.name)} 的资料</h2><p class="mono">报名号 ${h(item.candidateNumber)} · 更新于 ${formatDate(item.updatedAt,true)}</p></div><button data-action="close-modal">×</button></div><div class="review-profile"><dl><div><dt>证件号码</dt><dd class="mono">${h(item.idNumberMasked)}</dd></div><div><dt>性别 / 籍贯</dt><dd>${h(item.gender)} · ${h(item.nativePlace)}</dd></div><div><dt>联系电话</dt><dd>${h(item.phone)}</dd></div><div><dt>电子邮箱</dt><dd>${h(item.email)}</dd></div><div><dt>就读学校</dt><dd>${h(item.school)}</dd></div><div><dt>年级班级</dt><dd>${h(item.grade)}</dd></div><div><dt>家庭住址</dt><dd>${h(item.address)}</dd></div><div><dt>监护人</dt><dd>${h(item.guardianName || item.emergencyContact)} · ${h(item.guardianPhone || item.emergencyPhone)}</dd></div><div><dt>当前步骤</dt><dd>${h(item.workflow?.currentStepDetail?.name || '流程已结束')}</dd></div><div><dt>当前责任人</dt><dd>${h(item.workflow?.assignee?.displayName || '—')}</dd></div></dl></div>${canReview ? `<form class="modal-form" data-form="candidate-review"><input type="hidden" name="id" value="${h(item.id)}"><label><span>审核结论</span><select name="status"><option value="approved">通过当前步骤</option><option value="rejected">退回考生修改</option></select></label><label><span>审核意见</span><textarea name="reviewNote" rows="3" placeholder="填写核验说明或需要补充的资料">${h(item.reviewNote)}</textarea></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">确认处理</button></div></form>` : '<div class="modal-foot"><button class="solid-button" data-action="close-modal">关闭</button></div>'}`); setModal(`<div class="modal-head"><div><span>CANDIDATE REVIEW</span><h2>${canReview ? '处理' : '查看'} ${h(item.name)} 的资料</h2><p class="mono">报名号 ${h(item.candidateNumber)} · 更新于 ${formatDate(item.updatedAt,true)}</p></div><button data-action="close-modal">×</button></div><div class="review-profile"><dl><div><dt>证件号码</dt><dd class="mono">${h(item.idNumberMasked)}</dd></div><div><dt>性别 / 籍贯</dt><dd>${h(item.gender)} · ${h(item.nativePlace)}</dd></div><div><dt>联系电话</dt><dd>${h(item.phone)}</dd></div><div><dt>电子邮箱</dt><dd>${h(item.email)}</dd></div><div><dt>就读学校</dt><dd>${h(item.school)}</dd></div><div><dt>年级班级</dt><dd>${h(item.grade)}</dd></div><div><dt>家庭住址</dt><dd>${h(item.address)}</dd></div><div><dt>监护人</dt><dd>${h(item.guardianName || item.emergencyContact)} · ${h(item.guardianPhone || item.emergencyPhone)}</dd></div><div><dt>当前步骤</dt><dd>${h(item.workflow?.currentStepDetail?.name || '流程已结束')}</dd></div><div><dt>当前责任人</dt><dd>${h(item.workflow?.assignee?.displayName || '—')}</dd></div></dl></div>${canReview ? `<form class="modal-form" data-form="candidate-review"><input type="hidden" name="id" value="${h(item.id)}"><label><span>审核结论</span><select name="status"><option value="approved">通过当前步骤</option><option value="rejected">退回考生修改</option></select></label><label><span>审核意见</span><textarea name="reviewNote" rows="3" placeholder="填写核验说明或需要补充的资料">${h(item.reviewNote)}</textarea></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">确认处理</button></div></form>` : '<div class="modal-foot"><button class="solid-button" data-action="close-modal">关闭</button></div>'}`);
} }
+78 -29
View File
@@ -53,7 +53,7 @@ function buildSeedOperations(state) {
const nullable = value => value == null || value === '' ? null : value; const nullable = value => value == null || value === '' ? null : value;
add( add(
'UPDATE schema_metadata SET schema_version = 6, app_version = ?, self_registration_enabled = ?, created_at = ? WHERE id = 1', 'UPDATE schema_metadata SET schema_version = 7, app_version = ?, self_registration_enabled = ?, created_at = ? WHERE id = 1',
Number(state.meta?.version || 1), state.settings?.selfRegistrationEnabled ? 1 : 0, Number(state.meta?.version || 1), state.settings?.selfRegistrationEnabled ? 1 : 0,
state.meta?.createdAt || new Date().toISOString() state.meta?.createdAt || new Date().toISOString()
); );
@@ -90,13 +90,16 @@ function buildSeedOperations(state) {
for (const profile of state.candidateProfiles) { for (const profile of state.candidateProfiles) {
add( add(
`INSERT INTO candidate_profiles ( `INSERT INTO candidate_profiles (
id, user_id, name, gender, id_number, phone, email, school, grade, school_id, class_id, address, id, user_id, name, gender, id_number, phone, email, school, grade, school_id, class_id,
province_code, province_name, city_code, city_name, district_code, district_name, address,
emergency_contact, emergency_phone, native_place, birth_date, ethnicity, postal_code, guardian_name, emergency_contact, emergency_phone, native_place, birth_date, ethnicity, postal_code, guardian_name,
guardian_phone, profile_completed, status, review_note, reviewed_at, reviewer_id, updated_at guardian_phone, profile_completed, status, review_note, reviewed_at, reviewer_id, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
profile.id, profile.userId, profile.name, nullable(profile.gender), profile.idNumber, profile.phone, profile.id, profile.userId, profile.name, nullable(profile.gender), profile.idNumber, profile.phone,
nullable(profile.email), nullable(profile.school), nullable(profile.grade), nullable(profile.schoolId), nullable(profile.email), nullable(profile.school), nullable(profile.grade), nullable(profile.schoolId),
nullable(profile.classId), nullable(profile.address), nullable(profile.classId), nullable(profile.provinceCode), nullable(profile.provinceName),
nullable(profile.cityCode), nullable(profile.cityName), nullable(profile.districtCode), nullable(profile.districtName),
nullable(profile.address),
nullable(profile.emergencyContact), nullable(profile.emergencyPhone), nullable(profile.nativePlace), nullable(profile.birthDate), nullable(profile.emergencyContact), nullable(profile.emergencyPhone), nullable(profile.nativePlace), nullable(profile.birthDate),
nullable(profile.ethnicity), nullable(profile.postalCode), nullable(profile.guardianName), nullable(profile.guardianPhone), nullable(profile.ethnicity), nullable(profile.postalCode), nullable(profile.guardianName), nullable(profile.guardianPhone),
profile.profileCompleted ? 1 : 0, profile.status, nullable(profile.reviewNote), profile.profileCompleted ? 1 : 0, profile.status, nullable(profile.reviewNote),
@@ -171,10 +174,13 @@ function buildSeedOperations(state) {
for (const center of state.testCenters) { for (const center of state.testCenters) {
add( add(
`INSERT INTO test_centers ( `INSERT INTO test_centers (
id, school_id, code, name, address, contact, manager_name, manager_phone, emergency_phone, id, school_id, code, name, province_code, province_name, city_code, city_name, district_code, district_name,
address, contact, manager_name, manager_phone, emergency_phone,
gate_open_time, transport, status, notes, rooms, updated_at gate_open_time, transport, status, notes, rooms, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
center.id, center.schoolId, center.code, center.name, center.address, nullable(center.contact), center.id, center.schoolId, center.code, center.name,
center.provinceCode, center.provinceName, center.cityCode, center.cityName, center.districtCode, center.districtName,
center.address, nullable(center.contact),
nullable(center.managerName), nullable(center.managerPhone), nullable(center.emergencyPhone), nullable(center.managerName), nullable(center.managerPhone), nullable(center.emergencyPhone),
nullable(center.gateOpenTime), nullable(center.transport), center.status || 'active', nullable(center.notes), nullable(center.gateOpenTime), nullable(center.transport), center.status || 'active', nullable(center.notes),
center.rooms || '', center.updatedAt center.rooms || '', center.updatedAt
@@ -194,11 +200,14 @@ function buildSeedOperations(state) {
for (const request of state.centerChangeRequests) { for (const request of state.centerChangeRequests) {
add( add(
`INSERT INTO center_change_requests ( `INSERT INTO center_change_requests (
id, center_id, school_id, request_type, code, name, address, contact, manager_name, manager_phone, id, center_id, school_id, request_type, code, name,
province_code, province_name, city_code, city_name, district_code, district_name,
address, contact, manager_name, manager_phone,
emergency_phone, gate_open_time, transport, center_status, notes, status, review_note, emergency_phone, gate_open_time, transport, center_status, notes, status, review_note,
requested_by, created_at, reviewed_at requested_by, created_at, reviewed_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
request.id, nullable(request.centerId), request.schoolId, request.requestType, request.code, request.name, request.id, nullable(request.centerId), request.schoolId, request.requestType, request.code, request.name,
request.provinceCode, request.provinceName, request.cityCode, request.cityName, request.districtCode, request.districtName,
request.address, nullable(request.contact), nullable(request.managerName), nullable(request.managerPhone), request.address, nullable(request.contact), nullable(request.managerName), nullable(request.managerPhone),
nullable(request.emergencyPhone), nullable(request.gateOpenTime), nullable(request.transport), nullable(request.emergencyPhone), nullable(request.gateOpenTime), nullable(request.transport),
request.centerStatus || 'active', nullable(request.notes), request.status, nullable(request.reviewNote), request.centerStatus || 'active', nullable(request.notes), request.status, nullable(request.reviewNote),
@@ -400,6 +409,12 @@ function stateFromRows(rows) {
grade: row.grade || '', grade: row.grade || '',
schoolId: row.school_id || null, schoolId: row.school_id || null,
classId: row.class_id || null, classId: row.class_id || null,
provinceCode: row.province_code || '',
provinceName: row.province_name || '',
cityCode: row.city_code || '',
cityName: row.city_name || '',
districtCode: row.district_code || '',
districtName: row.district_name || '',
address: row.address || '', address: row.address || '',
emergencyContact: row.emergency_contact || '', emergencyContact: row.emergency_contact || '',
emergencyPhone: row.emergency_phone || '', emergencyPhone: row.emergency_phone || '',
@@ -475,6 +490,12 @@ function stateFromRows(rows) {
schoolId: row.school_id, schoolId: row.school_id,
code: row.code || '', code: row.code || '',
name: row.name, name: row.name,
provinceCode: row.province_code || '',
provinceName: row.province_name || '',
cityCode: row.city_code || '',
cityName: row.city_name || '',
districtCode: row.district_code || '',
districtName: row.district_name || '',
address: row.address, address: row.address,
contact: row.contact || '', contact: row.contact || '',
managerName: row.manager_name || '', managerName: row.manager_name || '',
@@ -509,6 +530,12 @@ function stateFromRows(rows) {
requestType: row.request_type, requestType: row.request_type,
code: row.code, code: row.code,
name: row.name, name: row.name,
provinceCode: row.province_code || '',
provinceName: row.province_name || '',
cityCode: row.city_code || '',
cityName: row.city_name || '',
districtCode: row.district_code || '',
districtName: row.district_name || '',
address: row.address, address: row.address,
contact: row.contact || '', contact: row.contact || '',
managerName: row.manager_name || '', managerName: row.manager_name || '',
@@ -730,13 +757,16 @@ function createRepository({ client, location, read, transaction, close }) {
), ),
operation( operation(
`INSERT INTO candidate_profiles ( `INSERT INTO candidate_profiles (
id, user_id, name, gender, id_number, phone, email, school, grade, school_id, class_id, address, id, user_id, name, gender, id_number, phone, email, school, grade, school_id, class_id,
province_code, province_name, city_code, city_name, district_code, district_name, address,
emergency_contact, emergency_phone, native_place, birth_date, ethnicity, postal_code, guardian_name, emergency_contact, emergency_phone, native_place, birth_date, ethnicity, postal_code, guardian_name,
guardian_phone, profile_completed, status, review_note, reviewed_at, reviewer_id, updated_at guardian_phone, profile_completed, status, review_note, reviewed_at, reviewer_id, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
profile.id, profile.userId, profile.name, optional(profile.gender), profile.idNumber, profile.phone, profile.id, profile.userId, profile.name, optional(profile.gender), profile.idNumber, profile.phone,
optional(profile.email), optional(profile.school), optional(profile.grade), optional(profile.schoolId), optional(profile.email), optional(profile.school), optional(profile.grade), optional(profile.schoolId),
optional(profile.classId), optional(profile.address), optional(profile.classId), optional(profile.provinceCode), optional(profile.provinceName),
optional(profile.cityCode), optional(profile.cityName), optional(profile.districtCode), optional(profile.districtName),
optional(profile.address),
optional(profile.emergencyContact), optional(profile.emergencyPhone), optional(profile.nativePlace), optional(profile.birthDate), optional(profile.emergencyContact), optional(profile.emergencyPhone), optional(profile.nativePlace), optional(profile.birthDate),
optional(profile.ethnicity), optional(profile.postalCode), optional(profile.guardianName), optional(profile.guardianPhone), optional(profile.ethnicity), optional(profile.postalCode), optional(profile.guardianName), optional(profile.guardianPhone),
profile.profileCompleted ? 1 : 0, profile.status, optional(profile.reviewNote), profile.profileCompleted ? 1 : 0, profile.status, optional(profile.reviewNote),
@@ -771,13 +801,16 @@ function createRepository({ client, location, read, transaction, close }) {
const operations = [ const operations = [
operation( operation(
`UPDATE candidate_profiles SET `UPDATE candidate_profiles SET
name = ?, gender = ?, id_number = ?, phone = ?, email = ?, school = ?, grade = ?, address = ?, name = ?, gender = ?, id_number = ?, phone = ?, email = ?, school = ?, grade = ?,
province_code = ?, province_name = ?, city_code = ?, city_name = ?, district_code = ?, district_name = ?, address = ?,
school_id = ?, class_id = ?, emergency_contact = ?, emergency_phone = ?, status = ?, review_note = ?, school_id = ?, class_id = ?, emergency_contact = ?, emergency_phone = ?, status = ?, review_note = ?,
native_place = ?, birth_date = ?, ethnicity = ?, postal_code = ?, guardian_name = ?, guardian_phone = ?, native_place = ?, birth_date = ?, ethnicity = ?, postal_code = ?, guardian_name = ?, guardian_phone = ?,
profile_completed = ?, reviewed_at = ?, reviewer_id = ?, updated_at = ? profile_completed = ?, reviewed_at = ?, reviewer_id = ?, updated_at = ?
WHERE id = ?`, WHERE id = ?`,
profile.name, optional(profile.gender), profile.idNumber, profile.phone, optional(profile.email), profile.name, optional(profile.gender), profile.idNumber, profile.phone, optional(profile.email),
optional(profile.school), optional(profile.grade), optional(profile.address), optional(profile.schoolId), optional(profile.school), optional(profile.grade), optional(profile.provinceCode), optional(profile.provinceName),
optional(profile.cityCode), optional(profile.cityName), optional(profile.districtCode), optional(profile.districtName),
optional(profile.address), optional(profile.schoolId),
optional(profile.classId), optional(profile.emergencyContact), optional(profile.emergencyPhone), profile.status, optional(profile.classId), optional(profile.emergencyContact), optional(profile.emergencyPhone), profile.status,
optional(profile.reviewNote), optional(profile.nativePlace), optional(profile.birthDate), optional(profile.ethnicity), optional(profile.reviewNote), optional(profile.nativePlace), optional(profile.birthDate), optional(profile.ethnicity),
optional(profile.postalCode), optional(profile.guardianName), optional(profile.guardianPhone), profile.profileCompleted ? 1 : 0, optional(profile.reviewedAt), optional(profile.postalCode), optional(profile.guardianName), optional(profile.guardianPhone), profile.profileCompleted ? 1 : 0, optional(profile.reviewedAt),
@@ -909,13 +942,16 @@ function createRepository({ client, location, read, transaction, close }) {
), ),
operation( operation(
`INSERT INTO candidate_profiles ( `INSERT INTO candidate_profiles (
id, user_id, name, gender, id_number, phone, email, school, grade, school_id, class_id, address, id, user_id, name, gender, id_number, phone, email, school, grade, school_id, class_id,
province_code, province_name, city_code, city_name, district_code, district_name, address,
emergency_contact, emergency_phone, native_place, birth_date, ethnicity, postal_code, guardian_name, emergency_contact, emergency_phone, native_place, birth_date, ethnicity, postal_code, guardian_name,
guardian_phone, profile_completed, status, review_note, reviewed_at, reviewer_id, updated_at guardian_phone, profile_completed, status, review_note, reviewed_at, reviewer_id, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
profile.id, profile.userId, profile.name, optional(profile.gender), profile.idNumber, profile.phone, profile.id, profile.userId, profile.name, optional(profile.gender), profile.idNumber, profile.phone,
optional(profile.email), profile.school, profile.grade, profile.schoolId, profile.classId, optional(profile.email), profile.school, profile.grade, profile.schoolId, profile.classId,
optional(profile.address), optional(profile.emergencyContact), optional(profile.emergencyPhone), optional(profile.provinceCode), optional(profile.provinceName), optional(profile.cityCode), optional(profile.cityName),
optional(profile.districtCode), optional(profile.districtName), optional(profile.address),
optional(profile.emergencyContact), optional(profile.emergencyPhone),
optional(profile.nativePlace), optional(profile.birthDate), optional(profile.ethnicity), optional(profile.postalCode), optional(profile.nativePlace), optional(profile.birthDate), optional(profile.ethnicity), optional(profile.postalCode),
optional(profile.guardianName), optional(profile.guardianPhone), 0, profile.status, optional(profile.reviewNote), optional(profile.guardianName), optional(profile.guardianPhone), 0, profile.status, optional(profile.reviewNote),
optional(profile.reviewedAt), optional(profile.reviewerId), profile.updatedAt optional(profile.reviewedAt), optional(profile.reviewerId), profile.updatedAt
@@ -1014,14 +1050,19 @@ function createRepository({ client, location, read, transaction, close }) {
const centerOperation = isNew const centerOperation = isNew
? operation( ? operation(
`INSERT INTO test_centers ( `INSERT INTO test_centers (
id, school_id, name, address, contact, rooms, updated_at id, school_id, code, name, province_code, province_name, city_code, city_name, district_code, district_name,
) VALUES (?, ?, ?, ?, ?, ?, ?)`, address, contact, rooms, updated_at
center.id, center.schoolId, center.name, center.address, optional(center.contact), center.rooms, center.updatedAt ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
center.id, center.schoolId, center.code, center.name,
center.provinceCode, center.provinceName, center.cityCode, center.cityName, center.districtCode, center.districtName,
center.address, optional(center.contact), center.rooms, center.updatedAt
) )
: operation( : operation(
`UPDATE test_centers SET `UPDATE test_centers SET
name = ?, address = ?, contact = ?, rooms = ?, updated_at = ? WHERE id = ?`, code = ?, name = ?, province_code = ?, province_name = ?, city_code = ?, city_name = ?,
center.name, center.address, optional(center.contact), center.rooms, center.updatedAt, center.id district_code = ?, district_name = ?, address = ?, contact = ?, rooms = ?, updated_at = ? WHERE id = ?`,
center.code, center.name, center.provinceCode, center.provinceName, center.cityCode, center.cityName,
center.districtCode, center.districtName, center.address, optional(center.contact), center.rooms, center.updatedAt, center.id
); );
await transaction([centerOperation, auditOperation(log)]); await transaction([centerOperation, auditOperation(log)]);
}, },
@@ -1029,11 +1070,14 @@ function createRepository({ client, location, read, transaction, close }) {
const operations = [ const operations = [
operation( operation(
`INSERT INTO center_change_requests ( `INSERT INTO center_change_requests (
id, center_id, school_id, request_type, code, name, address, contact, manager_name, manager_phone, id, center_id, school_id, request_type, code, name,
province_code, province_name, city_code, city_name, district_code, district_name,
address, contact, manager_name, manager_phone,
emergency_phone, gate_open_time, transport, center_status, notes, status, review_note, emergency_phone, gate_open_time, transport, center_status, notes, status, review_note,
requested_by, created_at, reviewed_at requested_by, created_at, reviewed_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
request.id, optional(request.centerId), request.schoolId, request.requestType, request.code, request.name, request.id, optional(request.centerId), request.schoolId, request.requestType, request.code, request.name,
request.provinceCode, request.provinceName, request.cityCode, request.cityName, request.districtCode, request.districtName,
request.address, optional(request.contact), optional(request.managerName), optional(request.managerPhone), request.address, optional(request.contact), optional(request.managerName), optional(request.managerPhone),
optional(request.emergencyPhone), optional(request.gateOpenTime), optional(request.transport), optional(request.emergencyPhone), optional(request.gateOpenTime), optional(request.transport),
request.centerStatus, optional(request.notes), request.status, optional(request.reviewNote), request.centerStatus, optional(request.notes), request.status, optional(request.reviewNote),
@@ -1069,10 +1113,13 @@ function createRepository({ client, location, read, transaction, close }) {
if (request.requestType === 'create') { if (request.requestType === 'create') {
operations.push(operation( operations.push(operation(
`INSERT INTO test_centers ( `INSERT INTO test_centers (
id, school_id, code, name, address, contact, manager_name, manager_phone, emergency_phone, id, school_id, code, name, province_code, province_name, city_code, city_name, district_code, district_name,
address, contact, manager_name, manager_phone, emergency_phone,
gate_open_time, transport, status, notes, rooms, updated_at gate_open_time, transport, status, notes, rooms, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
center.id, center.schoolId, center.code, center.name, center.address, optional(center.contact), center.id, center.schoolId, center.code, center.name,
center.provinceCode, center.provinceName, center.cityCode, center.cityName, center.districtCode, center.districtName,
center.address, optional(center.contact),
optional(center.managerName), optional(center.managerPhone), optional(center.emergencyPhone), optional(center.managerName), optional(center.managerPhone), optional(center.emergencyPhone),
optional(center.gateOpenTime), optional(center.transport), center.status, optional(center.notes), optional(center.gateOpenTime), optional(center.transport), center.status, optional(center.notes),
center.rooms || '', center.updatedAt center.rooms || '', center.updatedAt
@@ -1080,10 +1127,12 @@ function createRepository({ client, location, read, transaction, close }) {
} else { } else {
operations.push(operation( operations.push(operation(
`UPDATE test_centers SET `UPDATE test_centers SET
code = ?, name = ?, address = ?, contact = ?, manager_name = ?, manager_phone = ?, code = ?, name = ?, province_code = ?, province_name = ?, city_code = ?, city_name = ?,
district_code = ?, district_name = ?, address = ?, contact = ?, manager_name = ?, manager_phone = ?,
emergency_phone = ?, gate_open_time = ?, transport = ?, status = ?, notes = ?, rooms = ?, updated_at = ? emergency_phone = ?, gate_open_time = ?, transport = ?, status = ?, notes = ?, rooms = ?, updated_at = ?
WHERE id = ?`, WHERE id = ?`,
center.code, center.name, center.address, optional(center.contact), optional(center.managerName), center.code, center.name, center.provinceCode, center.provinceName, center.cityCode, center.cityName,
center.districtCode, center.districtName, center.address, optional(center.contact), optional(center.managerName),
optional(center.managerPhone), optional(center.emergencyPhone), optional(center.gateOpenTime), optional(center.managerPhone), optional(center.emergencyPhone), optional(center.gateOpenTime),
optional(center.transport), center.status, optional(center.notes), center.rooms || '', center.updatedAt, center.id optional(center.transport), center.status, optional(center.notes), center.rooms || '', center.updatedAt, center.id
)); ));
Loaded 3 of 21 files, more files were not shown because too many files have changed in this diff. Show more