准考证编排
This commit is contained in:
1 parent
4c7fafc03d
commit
9566542414
14 files changed
+846
-86
No files matched your search
@@ -48,7 +48,10 @@
|
|||||||
- 创建考试并结构化配置科目日期、时间、费用、满分与单科合格分,自动汇总总分
|
- 创建考试并结构化配置科目日期、时间、费用、满分与单科合格分,自动汇总总分
|
||||||
- 支持固定总分线、总分得分率、排名前百分比、单科均达线及不判定五类合格策略
|
- 支持固定总分线、总分得分率、排名前百分比、单科均达线及不判定五类合格策略
|
||||||
- 通知发布、草稿、撤回及首页置顶
|
- 通知发布、草稿、撤回及首页置顶
|
||||||
- 为审核通过的报名生成准考证号、考点、考场和座位
|
- 按整场考试预检并批量编排准考证,支持班内、校内、县区内、市内和省内五级混编
|
||||||
|
- 预置“县区编号+考场号+座位号”“县区号+考场号+流水号”“考点学校代码+考场号+座位号”“考生学校代码+考场号+座位号”四种号码规则
|
||||||
|
- 多科目考生固定在同一考点,各科独立分配考场和座位;同科目组合优先相邻编排
|
||||||
|
- 编排前校验科目时间冲突、考点容量、档案完整性和号码唯一性,默认保留备用考场并支持稳定种子复现
|
||||||
- 按科目满分校验单科成绩、按得分率计算等级,并在全部科目发布后汇总总分与合格结果
|
- 按科目满分校验单科成绩、按得分率计算等级,并在全部科目发布后汇总总分与合格结果
|
||||||
- 管理员与考生接口权限隔离
|
- 管理员与考生接口权限隔离
|
||||||
|
|
||||||
@@ -77,7 +80,7 @@ npm start
|
|||||||
|
|
||||||
打开 <http://127.0.0.1:4173>。
|
打开 <http://127.0.0.1:4173>。
|
||||||
|
|
||||||
本地开发无需额外配置,首次运行会自动创建 `data/exam.sqlite` 和完整关系型数据库结构。当前处于开发阶段,数据库结构版本为 v8,不兼容旧数据;升级本分支后请运行 `npm run reset-db` 重建开发库。v8 增加班级审批节点、成绩复议流程、严格校班匹配和多管理员均分。
|
本地开发无需额外配置,首次运行会自动创建 `data/exam.sqlite` 和完整关系型数据库结构。当前处于开发阶段,数据库结构版本为 v9,不兼容旧准考证编排数据;启动时会替换旧准考证相关表,也可运行 `npm run reset-db` 重建完整开发库。v9 增加整场编排方案、五级混编、可扩展号码规则和逐科考场座位。
|
||||||
|
|
||||||
需要清空并重建本地演示库时运行 `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`。
|
需要清空并重建本地演示库时运行 `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`。
|
||||||
|
|
||||||
@@ -109,7 +112,8 @@ GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER ON exam_information.* TO 'ex
|
|||||||
|
|
||||||
- `schools`、`school_classes`、`users`、`candidate_profiles`
|
- `schools`、`school_classes`、`users`、`candidate_profiles`
|
||||||
- `exams`、`exam_subjects`
|
- `exams`、`exam_subjects`
|
||||||
- `registrations`、`registration_subjects`、`admit_cards`
|
- `registrations`、`registration_subjects`
|
||||||
|
- `admission_number_rules`、`exam_arrangement_plans`、`admit_cards`、`admit_card_subjects`
|
||||||
- `results`、`notices`、`audit_logs`
|
- `results`、`notices`、`audit_logs`
|
||||||
- `test_centers`、`test_rooms`、`center_change_requests`、`center_change_rooms`
|
- `test_centers`、`test_rooms`、`center_change_requests`、`center_change_rooms`
|
||||||
- `number_rules`、`number_rule_segments`
|
- `number_rules`、`number_rule_segments`
|
||||||
@@ -151,7 +155,7 @@ npm start
|
|||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
测试使用独立临时 SQLite 数据库,覆盖固定报名号跨考试复用、首次登录强制改密、完整资料补录、自主注册开关、三级管理员数据范围、本校班级与班级管理员管理、多级审批、同级转交、校级按班级批量申领与终审原子建号、结构化考点考场及变更审批、多资源 Excel 导入导出、多科目报名、准考证、成绩复议、校班严格匹配和多人均分。
|
测试使用独立临时 SQLite 数据库,覆盖固定报名号跨考试复用、首次登录强制改密、完整资料补录、自主注册开关、三级管理员数据范围、本校班级与班级管理员管理、多级审批、同级转交、校级按班级批量申领与终审原子建号、结构化考点考场及变更审批、多资源 Excel 导入导出、多科目报名、五级准考证混编、四种号码规则、多科目同考点、成绩复议、校班严格匹配和多人均分。
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
|
|||||||
@@ -179,10 +179,23 @@ document.addEventListener('click', async event => {
|
|||||||
if (action === 'generate-admit') {
|
if (action === 'generate-admit') {
|
||||||
const registration = state.pageData.registrations.find(item => item.id === target.dataset.id);
|
const registration = state.pageData.registrations.find(item => item.id === target.dataset.id);
|
||||||
if (registration.admitCard) return openAdmitPreview(registration);
|
if (registration.admitCard) return openAdmitPreview(registration);
|
||||||
const testCenter = prompt('请输入考点名称:', '海州市第一中学');
|
return toast('尚未编排', '请使用页面上方的整场编排控制台');
|
||||||
if (!testCenter) return;
|
}
|
||||||
await api(`/api/admin/registrations/${target.dataset.id}/admit-card`, { method: 'POST', body: { testCenter } });
|
if (action === 'preview-arrangement') {
|
||||||
toast('准考证已生成', '考生可在开放时间内下载'); return renderRoute();
|
const form = target.closest('form');
|
||||||
|
const body = formObject(form);
|
||||||
|
const output = form.parentElement.querySelector('[data-arrangement-preview]');
|
||||||
|
target.disabled = true;
|
||||||
|
target.textContent = '正在预检…';
|
||||||
|
try {
|
||||||
|
const data = await api(`/api/admin/exams/${body.examId}/admission-arrangement/preview`, { method: 'POST', body });
|
||||||
|
const summary = data.summary;
|
||||||
|
output.innerHTML = `<div class="arrangement-preview ${data.warnings.length ? 'warning' : 'ok'}"><strong>${data.warnings.length ? '预检完成,有提示' : '预检通过,可以生成'}</strong><span>${summary.candidateCount} 人 · ${summary.centerCount} 个考点 · ${summary.subjectAssignmentCount} 个科次座位 · ${summary.subjectCombinationCount} 种科目组合</span>${data.warnings.length ? `<ul>${data.warnings.map(item => `<li>${h(item)}</li>`).join('')}</ul>` : '<small>多科目同考点、容量、科目时间和号码唯一性检查均已通过。</small>'}</div>`;
|
||||||
|
} finally {
|
||||||
|
target.disabled = false;
|
||||||
|
target.textContent = '仅预检,不写入';
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (action === 'toggle-exam') {
|
if (action === 'toggle-exam') {
|
||||||
await api(`/api/admin/exams/${target.dataset.id}`, { method: 'PATCH', body: { status: target.dataset.status } });
|
await api(`/api/admin/exams/${target.dataset.id}`, { method: 'PATCH', body: { status: target.dataset.status } });
|
||||||
@@ -367,6 +380,10 @@ document.addEventListener('submit', async event => {
|
|||||||
})).sort((a, b) => a.position - b.position);
|
})).sort((a, b) => a.position - b.position);
|
||||||
await api('/api/admin/number-rules', { method: 'POST', body: { id: raw.id, name: raw.name, separator: raw.separator, segments } });
|
await api('/api/admin/number-rules', { method: 'POST', body: { id: raw.id, name: raw.name, separator: raw.separator, segments } });
|
||||||
toast('报名号规则已启用', '后续创建的考生账户将按此规则生成固定号码'); renderRoute();
|
toast('报名号规则已启用', '后续创建的考生账户将按此规则生成固定号码'); renderRoute();
|
||||||
|
} else if (kind === 'admission-arrangement') {
|
||||||
|
const body = formObject(form);
|
||||||
|
const data = await api(`/api/admin/exams/${body.examId}/admission-arrangement`, { method: 'POST', body });
|
||||||
|
toast('整场编排已生成', `${data.summary.candidateCount} 名考生 · ${data.summary.subjectAssignmentCount} 个科次座位`); renderRoute();
|
||||||
} else if (kind === 'workflow-design') {
|
} else if (kind === 'workflow-design') {
|
||||||
const names = [...form.querySelectorAll('[name="stepName"]')];
|
const names = [...form.querySelectorAll('[name="stepName"]')];
|
||||||
const levels = [...form.querySelectorAll('[name="stepLevel"]')];
|
const levels = [...form.querySelectorAll('[name="stepLevel"]')];
|
||||||
@@ -542,7 +559,12 @@ function openExamForm(exam = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openAdmitPreview(reg) {
|
function openAdmitPreview(reg) {
|
||||||
setModal(`<div class="modal-head"><div><span>ADMISSION CARD</span><h2>准考证已生成</h2><p>${h(reg.candidate?.name)} · ${h(reg.exam.name)}</p></div><button data-action="close-modal">×</button></div><div class="admit-preview"><strong>${h(reg.admitCard.number)}</strong><dl><div><dt>考点</dt><dd>${h(reg.admitCard.testCenter)}</dd></div><div><dt>考场</dt><dd>${h(reg.admitCard.room)}</dd></div><div><dt>座位号</dt><dd>${h(reg.admitCard.seat)}</dd></div><div><dt>生成时间</dt><dd>${formatDate(reg.admitCard.generatedAt,true)}</dd></div></dl><p>考生可在 ${dateRange(reg.exam.admitDownloadStart, reg.exam.admitDownloadEnd)} 下载。</p></div><div class="modal-foot"><button class="solid-button" data-action="close-modal">完成</button></div>`);
|
const assignments = new Map((reg.admitCard.assignments || []).map(item => [item.subjectId, item]));
|
||||||
|
const rows = reg.subjects.map(subject => {
|
||||||
|
const assignment = assignments.get(subject.id) || {};
|
||||||
|
return `<tr><td>${h(subject.name)}</td><td>${h(subject.date)} ${h(subject.start)}</td><td>${h(assignment.roomName || assignment.room || '待定')}</td><td>${h(assignment.seat || '—')}</td></tr>`;
|
||||||
|
}).join('');
|
||||||
|
setModal(`<div class="modal-head"><div><span>ADMISSION CARD</span><h2>准考证编排明细</h2><p>${h(reg.candidate?.name)} · ${h(reg.exam.name)}</p></div><button data-action="close-modal">×</button></div><div class="admit-preview"><strong>${h(reg.admitCard.number)}</strong><dl><div><dt>固定考点</dt><dd>${h(reg.admitCard.testCenter)}</dd></div><div><dt>生成时间</dt><dd>${formatDate(reg.admitCard.generatedAt,true)}</dd></div></dl><table><thead><tr><th>科目</th><th>时间</th><th>考场</th><th>座位</th></tr></thead><tbody>${rows}</tbody></table><p>考生可在 ${dateRange(reg.exam.admitDownloadStart, reg.exam.admitDownloadEnd)} 下载。</p></div><div class="modal-foot"><button class="solid-button" data-action="close-modal">完成</button></div>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('hashchange', renderRoute);
|
window.addEventListener('hashchange', renderRoute);
|
||||||
|
|||||||
+139
-24
@@ -16,7 +16,10 @@ export const relationalTables = [
|
|||||||
'exam_subjects',
|
'exam_subjects',
|
||||||
'registrations',
|
'registrations',
|
||||||
'registration_subjects',
|
'registration_subjects',
|
||||||
|
'admission_number_rules',
|
||||||
|
'exam_arrangement_plans',
|
||||||
'admit_cards',
|
'admit_cards',
|
||||||
|
'admit_card_subjects',
|
||||||
'results',
|
'results',
|
||||||
'test_centers',
|
'test_centers',
|
||||||
'test_rooms',
|
'test_rooms',
|
||||||
@@ -37,6 +40,7 @@ function validateState(state, source = '数据库') {
|
|||||||
const collections = [
|
const collections = [
|
||||||
'schools', 'classes', 'users', 'candidateProfiles', 'notices', 'exams', 'registrations', 'results',
|
'schools', 'classes', 'users', 'candidateProfiles', 'notices', 'exams', 'registrations', 'results',
|
||||||
'testCenters', 'testRooms', 'centerChangeRequests', 'centerChangeRooms',
|
'testCenters', 'testRooms', 'centerChangeRequests', 'centerChangeRooms',
|
||||||
|
'admissionNumberRules', 'arrangementPlans',
|
||||||
'numberRules', 'candidateAccountBatches', 'candidateAccountBatchItems',
|
'numberRules', 'candidateAccountBatches', 'candidateAccountBatchItems',
|
||||||
'workflows', 'workflowInstances', 'workflowActions', 'auditLogs'
|
'workflows', 'workflowInstances', 'workflowActions', 'auditLogs'
|
||||||
];
|
];
|
||||||
@@ -53,7 +57,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 = 8, app_version = ?, self_registration_enabled = ?, created_at = ? WHERE id = 1',
|
'UPDATE schema_metadata SET schema_version = 9, 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()
|
||||||
);
|
);
|
||||||
@@ -150,15 +154,6 @@ function buildSeedOperations(state) {
|
|||||||
for (const subjectId of registration.subjectIds) {
|
for (const subjectId of registration.subjectIds) {
|
||||||
add('INSERT INTO registration_subjects (registration_id, subject_id) VALUES (?, ?)', registration.id, subjectId);
|
add('INSERT INTO registration_subjects (registration_id, subject_id) VALUES (?, ?)', registration.id, subjectId);
|
||||||
}
|
}
|
||||||
if (registration.admitCard) {
|
|
||||||
add(
|
|
||||||
`INSERT INTO admit_cards (
|
|
||||||
registration_id, card_number, test_center, room, seat, generated_at
|
|
||||||
) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
||||||
registration.id, registration.admitCard.number, registration.admitCard.testCenter,
|
|
||||||
registration.admitCard.room, registration.admitCard.seat, registration.admitCard.generatedAt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const result of state.results) {
|
for (const result of state.results) {
|
||||||
@@ -197,6 +192,46 @@ function buildSeedOperations(state) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const rule of state.admissionNumberRules) {
|
||||||
|
add(
|
||||||
|
`INSERT INTO admission_number_rules (
|
||||||
|
id, code, name, description, separator, segments_json, example, active, created_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
rule.id, rule.code, rule.name, rule.description, rule.separator || '', JSON.stringify(rule.segments || []),
|
||||||
|
rule.example || '', rule.active === false ? 0 : 1, rule.createdAt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const plan of state.arrangementPlans) {
|
||||||
|
add(
|
||||||
|
`INSERT INTO exam_arrangement_plans (
|
||||||
|
id, exam_id, number_rule_id, mixing_scope, random_seed, candidate_count, center_count,
|
||||||
|
subject_assignment_count, subject_combination_count, same_school_center_rate, warnings_json, generated_by, generated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
plan.id, plan.examId, plan.numberRuleId, plan.mixingScope, plan.randomSeed,
|
||||||
|
Number(plan.candidateCount || 0), Number(plan.centerCount || 0), Number(plan.subjectAssignmentCount || 0),
|
||||||
|
Number(plan.subjectCombinationCount || 0), Number(plan.sameSchoolCenterRate || 0), JSON.stringify(plan.warnings || []),
|
||||||
|
nullable(plan.generatedBy), plan.generatedAt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const registration of state.registrations.filter(item => item.admitCard)) {
|
||||||
|
const card = registration.admitCard;
|
||||||
|
add(
|
||||||
|
`INSERT INTO admit_cards (
|
||||||
|
registration_id, plan_id, card_number, center_id, test_center, generated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
|
registration.id, card.planId, card.number, nullable(card.centerId), card.testCenter, card.generatedAt
|
||||||
|
);
|
||||||
|
for (const assignment of card.assignments || []) add(
|
||||||
|
`INSERT INTO admit_card_subjects (
|
||||||
|
registration_id, subject_id, room_id, room, room_code, exam_room_code, seat, subject_signature
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
registration.id, assignment.subjectId, nullable(assignment.roomId), assignment.roomName || assignment.room,
|
||||||
|
assignment.roomCode, assignment.examRoomCode, assignment.seat, assignment.subjectSignature || ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (const request of state.centerChangeRequests) {
|
for (const request of state.centerChangeRequests) {
|
||||||
add(
|
add(
|
||||||
`INSERT INTO center_change_requests (
|
`INSERT INTO center_change_requests (
|
||||||
@@ -328,13 +363,39 @@ function stateFromRows(rows) {
|
|||||||
subjectIds.push(row.subject_id);
|
subjectIds.push(row.subject_id);
|
||||||
registrationSubjects.set(row.registration_id, subjectIds);
|
registrationSubjects.set(row.registration_id, subjectIds);
|
||||||
}
|
}
|
||||||
const admitCards = new Map(rows.admitCards.map(row => [row.registration_id, {
|
const subjectPositions = new Map(rows.subjects.map(row => [row.id, Number(row.position)]));
|
||||||
number: row.card_number,
|
const admitAssignments = new Map();
|
||||||
testCenter: row.test_center,
|
for (const row of rows.admitCardSubjects) {
|
||||||
room: row.room,
|
const assignments = admitAssignments.get(row.registration_id) || [];
|
||||||
seat: row.seat,
|
assignments.push({
|
||||||
generatedAt: row.generated_at
|
subjectId: row.subject_id,
|
||||||
}]));
|
roomId: row.room_id,
|
||||||
|
roomName: row.room,
|
||||||
|
room: row.room,
|
||||||
|
roomCode: row.room_code,
|
||||||
|
examRoomCode: row.exam_room_code,
|
||||||
|
seat: row.seat,
|
||||||
|
subjectSignature: row.subject_signature || ''
|
||||||
|
});
|
||||||
|
admitAssignments.set(row.registration_id, assignments);
|
||||||
|
}
|
||||||
|
for (const assignments of admitAssignments.values()) assignments.sort((left, right) =>
|
||||||
|
(subjectPositions.get(left.subjectId) || 0) - (subjectPositions.get(right.subjectId) || 0)
|
||||||
|
);
|
||||||
|
const admitCards = new Map(rows.admitCards.map(row => {
|
||||||
|
const assignments = admitAssignments.get(row.registration_id) || [];
|
||||||
|
const primary = assignments[0] || {};
|
||||||
|
return [row.registration_id, {
|
||||||
|
planId: row.plan_id,
|
||||||
|
number: row.card_number,
|
||||||
|
centerId: row.center_id,
|
||||||
|
testCenter: row.test_center,
|
||||||
|
room: primary.roomName || '',
|
||||||
|
seat: primary.seat || '',
|
||||||
|
assignments,
|
||||||
|
generatedAt: row.generated_at
|
||||||
|
}];
|
||||||
|
}));
|
||||||
const ruleSegments = new Map();
|
const ruleSegments = new Map();
|
||||||
for (const row of rows.numberRuleSegments) {
|
for (const row of rows.numberRuleSegments) {
|
||||||
const segments = ruleSegments.get(row.rule_id) || [];
|
const segments = ruleSegments.get(row.rule_id) || [];
|
||||||
@@ -567,6 +628,32 @@ function stateFromRows(rows) {
|
|||||||
status: row.status,
|
status: row.status,
|
||||||
notes: row.notes || ''
|
notes: row.notes || ''
|
||||||
})),
|
})),
|
||||||
|
admissionNumberRules: rows.admissionNumberRules.map(row => ({
|
||||||
|
id: row.id,
|
||||||
|
code: row.code,
|
||||||
|
name: row.name,
|
||||||
|
description: row.description,
|
||||||
|
separator: row.separator || '',
|
||||||
|
segments: Array.isArray(row.segments_json) ? row.segments_json : JSON.parse(row.segments_json || '[]'),
|
||||||
|
example: row.example || '',
|
||||||
|
active: Boolean(row.active),
|
||||||
|
createdAt: row.created_at
|
||||||
|
})),
|
||||||
|
arrangementPlans: rows.arrangementPlans.map(row => ({
|
||||||
|
id: row.id,
|
||||||
|
examId: row.exam_id,
|
||||||
|
numberRuleId: row.number_rule_id,
|
||||||
|
mixingScope: row.mixing_scope,
|
||||||
|
randomSeed: row.random_seed,
|
||||||
|
candidateCount: Number(row.candidate_count),
|
||||||
|
centerCount: Number(row.center_count),
|
||||||
|
subjectAssignmentCount: Number(row.subject_assignment_count),
|
||||||
|
subjectCombinationCount: Number(row.subject_combination_count),
|
||||||
|
sameSchoolCenterRate: Number(row.same_school_center_rate),
|
||||||
|
warnings: Array.isArray(row.warnings_json) ? row.warnings_json : JSON.parse(row.warnings_json || '[]'),
|
||||||
|
generatedBy: row.generated_by,
|
||||||
|
generatedAt: row.generated_at
|
||||||
|
})),
|
||||||
numberRules: rows.numberRules.map(row => ({
|
numberRules: rows.numberRules.map(row => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
@@ -649,7 +736,10 @@ function readSqliteRows(connection) {
|
|||||||
subjects: connection.prepare('SELECT * FROM exam_subjects ORDER BY exam_id, position, id').all(),
|
subjects: connection.prepare('SELECT * FROM exam_subjects ORDER BY exam_id, position, id').all(),
|
||||||
registrations: connection.prepare('SELECT * FROM registrations ORDER BY created_at, id').all(),
|
registrations: connection.prepare('SELECT * FROM registrations ORDER BY created_at, id').all(),
|
||||||
registrationSubjects: connection.prepare('SELECT * FROM registration_subjects ORDER BY registration_id, subject_id').all(),
|
registrationSubjects: connection.prepare('SELECT * FROM registration_subjects ORDER BY registration_id, subject_id').all(),
|
||||||
|
admissionNumberRules: connection.prepare('SELECT * FROM admission_number_rules ORDER BY created_at, id').all(),
|
||||||
|
arrangementPlans: connection.prepare('SELECT * FROM exam_arrangement_plans ORDER BY generated_at DESC, id').all(),
|
||||||
admitCards: connection.prepare('SELECT * FROM admit_cards ORDER BY registration_id').all(),
|
admitCards: connection.prepare('SELECT * FROM admit_cards ORDER BY registration_id').all(),
|
||||||
|
admitCardSubjects: connection.prepare('SELECT * FROM admit_card_subjects ORDER BY registration_id, subject_id').all(),
|
||||||
results: connection.prepare('SELECT * FROM results ORDER BY id').all(),
|
results: connection.prepare('SELECT * FROM results ORDER BY id').all(),
|
||||||
testCenters: connection.prepare('SELECT * FROM test_centers ORDER BY school_id, name, id').all(),
|
testCenters: connection.prepare('SELECT * FROM test_centers ORDER BY school_id, name, id').all(),
|
||||||
testRooms: connection.prepare('SELECT * FROM test_rooms ORDER BY center_id, code, id').all(),
|
testRooms: connection.prepare('SELECT * FROM test_rooms ORDER BY center_id, code, id').all(),
|
||||||
@@ -682,7 +772,10 @@ async function readMysqlRows(connection) {
|
|||||||
subjects: await query('SELECT * FROM exam_subjects ORDER BY exam_id, position, id'),
|
subjects: await query('SELECT * FROM exam_subjects ORDER BY exam_id, position, id'),
|
||||||
registrations: await query('SELECT * FROM registrations ORDER BY created_at, id'),
|
registrations: await query('SELECT * FROM registrations ORDER BY created_at, id'),
|
||||||
registrationSubjects: await query('SELECT * FROM registration_subjects ORDER BY registration_id, subject_id'),
|
registrationSubjects: await query('SELECT * FROM registration_subjects ORDER BY registration_id, subject_id'),
|
||||||
|
admissionNumberRules: await query('SELECT * FROM admission_number_rules ORDER BY created_at, id'),
|
||||||
|
arrangementPlans: await query('SELECT * FROM exam_arrangement_plans ORDER BY generated_at DESC, id'),
|
||||||
admitCards: await query('SELECT * FROM admit_cards ORDER BY registration_id'),
|
admitCards: await query('SELECT * FROM admit_cards ORDER BY registration_id'),
|
||||||
|
admitCardSubjects: await query('SELECT * FROM admit_card_subjects ORDER BY registration_id, subject_id'),
|
||||||
results: await query('SELECT * FROM results ORDER BY id'),
|
results: await query('SELECT * FROM results ORDER BY id'),
|
||||||
testCenters: await query('SELECT * FROM test_centers ORDER BY school_id, name, id'),
|
testCenters: await query('SELECT * FROM test_centers ORDER BY school_id, name, id'),
|
||||||
testRooms: await query('SELECT * FROM test_rooms ORDER BY center_id, code, id'),
|
testRooms: await query('SELECT * FROM test_rooms ORDER BY center_id, code, id'),
|
||||||
@@ -1154,16 +1247,38 @@ function createRepository({ client, location, read, transaction, close }) {
|
|||||||
operations.push(auditOperation(log));
|
operations.push(auditOperation(log));
|
||||||
await transaction(operations);
|
await transaction(operations);
|
||||||
},
|
},
|
||||||
async createAdmitCard(registrationId, admitCard, log) {
|
async saveAdmissionArrangement(plan, cards, log) {
|
||||||
await transaction([
|
const operations = [
|
||||||
|
operation('DELETE FROM exam_arrangement_plans WHERE exam_id = ?', plan.examId),
|
||||||
operation(
|
operation(
|
||||||
|
`INSERT INTO exam_arrangement_plans (
|
||||||
|
id, exam_id, number_rule_id, mixing_scope, random_seed, candidate_count, center_count,
|
||||||
|
subject_assignment_count, subject_combination_count, same_school_center_rate,
|
||||||
|
warnings_json, generated_by, generated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
plan.id, plan.examId, plan.numberRuleId, plan.mixingScope, plan.randomSeed,
|
||||||
|
Number(plan.candidateCount), Number(plan.centerCount), Number(plan.subjectAssignmentCount),
|
||||||
|
Number(plan.subjectCombinationCount), Number(plan.sameSchoolCenterRate), JSON.stringify(plan.warnings || []),
|
||||||
|
optional(plan.generatedBy), plan.generatedAt
|
||||||
|
)
|
||||||
|
];
|
||||||
|
for (const card of cards) {
|
||||||
|
operations.push(operation(
|
||||||
`INSERT INTO admit_cards (
|
`INSERT INTO admit_cards (
|
||||||
registration_id, card_number, test_center, room, seat, generated_at
|
registration_id, plan_id, card_number, center_id, test_center, generated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?)`,
|
) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
registrationId, admitCard.number, admitCard.testCenter, admitCard.room, admitCard.seat, admitCard.generatedAt
|
card.registrationId, plan.id, card.number, optional(card.centerId), card.testCenter, card.generatedAt
|
||||||
),
|
));
|
||||||
auditOperation(log)
|
for (const assignment of card.assignments) operations.push(operation(
|
||||||
]);
|
`INSERT INTO admit_card_subjects (
|
||||||
|
registration_id, subject_id, room_id, room, room_code, exam_room_code, seat, subject_signature
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
card.registrationId, assignment.subjectId, optional(assignment.roomId), assignment.roomName,
|
||||||
|
assignment.roomCode, assignment.examRoomCode, assignment.seat, assignment.subjectSignature
|
||||||
|
));
|
||||||
|
}
|
||||||
|
operations.push(auditOperation(log));
|
||||||
|
await transaction(operations);
|
||||||
},
|
},
|
||||||
async createExam(exam, log) {
|
async createExam(exam, log) {
|
||||||
const operations = [operation(
|
const operations = [operation(
|
||||||
|
|||||||
Loaded 3 of 14 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user