志愿
This commit is contained in:
1 parent
97bbe07789
commit
ae528b20e9
24 files changed
+770
-30
No files matched your search
@@ -256,6 +256,21 @@ npm run seed-test-data:mysql -- --force
|
|||||||
|
|
||||||
强制模式会删除该 MySQL 数据库内现有应用数据并在同一事务中写入样例数据,但不会删除数据库或数据表。导入完成后再重新启动应用,避免导入期间出现并发写入或保留旧登录会话。不要对生产业务库执行此命令。本地需要明确使用 SQLite 时可运行 `npm run seed-test-data:sqlite`。
|
强制模式会删除该 MySQL 数据库内现有应用数据并在同一事务中写入样例数据,但不会删除数据库或数据表。导入完成后再重新启动应用,避免导入期间出现并发写入或保留旧登录会话。不要对生产业务库执行此命令。本地需要明确使用 SQLite 时可运行 `npm run seed-test-data:sqlite`。
|
||||||
|
|
||||||
|
## 中考志愿填报与招生录取
|
||||||
|
|
||||||
|
系统可按考试单独启用志愿填报,未启用的考试不会出现志愿入口。完整流程如下:
|
||||||
|
|
||||||
|
1. 超级管理员设置填报时间、最多志愿数和当前阶段;考生只有在当次成绩全部发布后才能填报。
|
||||||
|
2. 招生学校账号上传本校普通生、特长生与指标分配计划,超级管理员审核后生效;超级管理员也可代上传并直接审核。
|
||||||
|
3. 志愿只能由考生本人在开放窗口内保存或修改。班级管理员、校级管理员无权查看;超级管理员只读可见,任何管理员均无代改接口。
|
||||||
|
4. 超级管理员结束填报并执行投档。系统按总成绩降序逐个检索志愿,校验特长资格、指标池和类别剩余计划,遵循“分数优先、遵循志愿”。
|
||||||
|
5. 投档材料只发送到对应招生学校,包含必要考生资料与当次成绩,不包含考生其余志愿。学校可接收或填写特殊理由申请退档,退档由超级管理员统一审核。
|
||||||
|
6. 未完成计划可开启下一轮补录;已正式录取的考生不会被覆盖。录取结束后系统发送个人通知,并按设置自动发布脱敏公示。
|
||||||
|
|
||||||
|
公开公示固定包含报名号、姓名、考生总成绩和录取学校;证件号、手机号等重要身份信息只提供脱敏值。考生档案另含特长生类型、特长证明编号和政策资格说明。
|
||||||
|
|
||||||
|
数据结构版本为 v18,新增 `admission_records` 关系表并支持 SQLite / MySQL 自动迁移。新角色值为 `admission_school`。
|
||||||
|
|
||||||
## 手动测试数据账号
|
## 手动测试数据账号
|
||||||
|
|
||||||
测试数据脚本会提供以下账号;其中初始超级管理员也可能由正常首次建库创建,并可通过环境变量改名、改密,其余校级、班级和考生账号不会在正常启动时创建:
|
测试数据脚本会提供以下账号;其中初始超级管理员也可能由正常首次建库创建,并可通过环境变量改名、改密,其余校级、班级和考生账号不会在正常启动时创建:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { api } from './src/client/api.mjs';
|
import { api } from './src/client/api.mjs';
|
||||||
import { createAdminViews, numberSegmentMeta } from './src/client/admin-views.mjs';
|
import { createAdminViews, numberSegmentMeta } from './src/client/admin-views.mjs';
|
||||||
import { createCandidateViews } from './src/client/candidate-views.mjs';
|
import { createCandidateViews } from './src/client/candidate-views.mjs';
|
||||||
|
import { createAdmissionViews } from './src/client/admission-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';
|
||||||
@@ -59,6 +60,7 @@ const baseViewContext = { state, app, h, formatDate, dateRange, badge, money, pa
|
|||||||
const { brand, renderHome, renderAuth } = createPublicViews(baseViewContext);
|
const { brand, renderHome, renderAuth } = createPublicViews(baseViewContext);
|
||||||
const { adminNavForUser, portalShell, loadingPanel, renderCandidate, accountSecurity } = createCandidateViews({ ...baseViewContext, brand });
|
const { adminNavForUser, portalShell, loadingPanel, renderCandidate, accountSecurity } = createCandidateViews({ ...baseViewContext, brand });
|
||||||
const { renderAdmin, workflowStepEditor } = createAdminViews({ ...baseViewContext, brand, portalShell, loadingPanel, adminNavForUser, accountSecurity });
|
const { renderAdmin, workflowStepEditor } = createAdminViews({ ...baseViewContext, brand, portalShell, loadingPanel, adminNavForUser, accountSecurity });
|
||||||
|
const { renderAdmission } = createAdmissionViews({ ...baseViewContext, brand });
|
||||||
|
|
||||||
function navigate(route) {
|
function navigate(route) {
|
||||||
location.hash = route;
|
location.hash = route;
|
||||||
@@ -74,6 +76,7 @@ async function renderRoute() {
|
|||||||
else if (section === 'login' || section === 'register') renderAuth(section);
|
else if (section === 'login' || section === 'register') renderAuth(section);
|
||||||
else if (section === 'candidate') await renderCandidate(page);
|
else if (section === 'candidate') await renderCandidate(page);
|
||||||
else if (section === 'admin') await renderAdmin(page);
|
else if (section === 'admin') await renderAdmin(page);
|
||||||
|
else if (section === 'admission_school') await renderAdmission(page);
|
||||||
else navigate('home');
|
else navigate('home');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +184,35 @@ document.addEventListener('click', async event => {
|
|||||||
setModal(`<div class="modal-head"><div><span>${h(notice.category)}</span><h2>${h(notice.title)}</h2><p>${formatDate(notice.publishAt,true)} · ${h(notice.author)}</p></div><button data-action="close-modal">×</button></div><article class="notice-content">${contentHtml}</article><div class="modal-foot"><button class="ghost-button" data-action="close-modal">关闭</button></div>`); return;
|
setModal(`<div class="modal-head"><div><span>${h(notice.category)}</span><h2>${h(notice.title)}</h2><p>${formatDate(notice.publishAt,true)} · ${h(notice.author)}</p></div><button data-action="close-modal">×</button></div><article class="notice-content">${contentHtml}</article><div class="modal-foot"><button class="ghost-button" data-action="close-modal">关闭</button></div>`); return;
|
||||||
}
|
}
|
||||||
if (action === 'download-admit') { window.location.href = `/api/candidate/registrations/${target.dataset.id}/admit-card`; return; }
|
if (action === 'download-admit') { window.location.href = `/api/candidate/registrations/${target.dataset.id}/admit-card`; return; }
|
||||||
|
if (action === 'admission-plan-review') {
|
||||||
|
const reviewNote = window.prompt(target.dataset.status === 'approved' ? '填写审核意见(可留空)' : '请填写退回原因', '') ?? null;
|
||||||
|
if (reviewNote == null) return;
|
||||||
|
await api(`/api/admin/admission-plans/${target.dataset.id}`, { method: 'PATCH', body: { status: target.dataset.status, reviewNote } });
|
||||||
|
toast(target.dataset.status === 'approved' ? '招生计划已通过' : '招生计划已退回'); return renderRoute();
|
||||||
|
}
|
||||||
|
if (action === 'admission-match') {
|
||||||
|
if (!window.confirm('确认按“分数优先、遵循志愿”执行本轮投档?填报顺序将锁定。')) return;
|
||||||
|
const data = await api(`/api/admin/admissions/${target.dataset.examId}/match`, { method: 'POST' });
|
||||||
|
toast('投档完成', `${data.placementCount} 名考生已发送招生学校`); return renderRoute();
|
||||||
|
}
|
||||||
|
if (action === 'admission-finalize') {
|
||||||
|
if (!window.confirm('确认结束本次录取?系统会向考生发送通知,并按设置自动公示。')) return;
|
||||||
|
const data = await api(`/api/admin/admissions/${target.dataset.examId}/finalize`, { method: 'POST' });
|
||||||
|
await refreshPublic(); toast('录取工作已结束', `${data.admittedCount} 人正式录取`); return renderRoute();
|
||||||
|
}
|
||||||
|
if (action === 'admission-supplementary') {
|
||||||
|
const preferenceEnd = window.prompt('请输入补录志愿截止时间(例如 2026-07-25T18:00)', '');
|
||||||
|
if (!preferenceEnd) return;
|
||||||
|
await api(`/api/admin/admissions/${target.dataset.examId}/supplementary`, { method: 'POST', body: { preferenceEnd } });
|
||||||
|
toast('补录已开启', '未录取考生可以填报新一轮志愿'); return renderRoute();
|
||||||
|
}
|
||||||
|
if (action === 'withdrawal-review') {
|
||||||
|
const approved = target.dataset.approved === 'true';
|
||||||
|
const reviewNote = window.prompt(approved ? '填写批准退档意见' : '填写驳回退档意见', '');
|
||||||
|
if (reviewNote == null) return;
|
||||||
|
await api(`/api/admin/admission-withdrawals/${target.dataset.id}`, { method: 'PATCH', body: { approved, reviewNote } });
|
||||||
|
toast(approved ? '退档已批准' : '退档申请已驳回'); return renderRoute();
|
||||||
|
}
|
||||||
if (['batch-admit-download', 'admit-info-export', 'center-materials-export'].includes(action)) {
|
if (['batch-admit-download', 'admit-info-export', 'center-materials-export'].includes(action)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const examId = target.closest('.admission-export-panel')?.querySelector('[data-admission-export-exam]')?.value;
|
const examId = target.closest('.admission-export-panel')?.querySelector('[data-admission-export-exam]')?.value;
|
||||||
@@ -527,6 +559,12 @@ document.addEventListener('submit', async event => {
|
|||||||
} else if (kind === 'candidate-profile') {
|
} else if (kind === 'candidate-profile') {
|
||||||
const data = await api('/api/candidate/profile', { method: 'PUT', body: formObject(form) });
|
const data = await api('/api/candidate/profile', { method: 'PUT', body: formObject(form) });
|
||||||
state.profile = data.profile; await refreshSession(); toast('资料已提交', '管理员审核后会更新状态'); navigate('candidate/dashboard');
|
state.profile = data.profile; await refreshSession(); toast('资料已提交', '管理员审核后会更新状态'); navigate('candidate/dashboard');
|
||||||
|
} else if (kind === 'volunteer-preference') {
|
||||||
|
const choices = new FormData(form).getAll('choices').filter(Boolean).map(value => {
|
||||||
|
const [schoolId, categoryCode] = String(value).split('|'); return { schoolId, categoryCode };
|
||||||
|
});
|
||||||
|
await api(`/api/candidate/admissions/${form.examId.value}/preferences`, { method: 'PUT', body: { choices } });
|
||||||
|
toast('志愿已保存', '仅你本人可在填报截止前修改'); renderRoute();
|
||||||
} else if (kind === 'exam-registration') {
|
} else if (kind === 'exam-registration') {
|
||||||
const body = { examId: form.examId.value, subjectIds: [...form.querySelectorAll('input[name="subjectIds"]:checked')].map(input => input.value) };
|
const body = { examId: form.examId.value, subjectIds: [...form.querySelectorAll('input[name="subjectIds"]:checked')].map(input => input.value) };
|
||||||
if (!body.subjectIds.length) throw new Error('请至少选择一个报考科目');
|
if (!body.subjectIds.length) throw new Error('请至少选择一个报考科目');
|
||||||
@@ -548,6 +586,27 @@ document.addEventListener('submit', async event => {
|
|||||||
const body = formObject(form);
|
const body = formObject(form);
|
||||||
await api('/api/admin/admins', { method: 'POST', body });
|
await api('/api/admin/admins', { method: 'POST', body });
|
||||||
closeModal(); toast('管理员已创建', '权限范围已按层级绑定'); renderRoute();
|
closeModal(); toast('管理员已创建', '权限范围已按层级绑定'); renderRoute();
|
||||||
|
} else if (kind === 'admission-setting') {
|
||||||
|
const body = formObject(form); body.enabled = form.enabled.checked; body.autoPublish = form.autoPublish.checked; body.maxChoices = Number(body.maxChoices || 5);
|
||||||
|
await api(`/api/admin/admissions/${body.examId}/setting`, { method: 'PUT', body });
|
||||||
|
toast('志愿设置已保存', '考生端阶段与进度已同步'); renderRoute();
|
||||||
|
} else if (kind === 'admission-account') {
|
||||||
|
await api('/api/admin/admission-school-accounts', { method: 'POST', body: formObject(form) });
|
||||||
|
form.reset(); toast('招生学校账号已创建'); renderRoute();
|
||||||
|
} else if (kind === 'admission-plan' || kind === 'school-admission-plan') {
|
||||||
|
const body = formObject(form);
|
||||||
|
body.categories = String(body.categoriesText || '').split(/\r?\n/).map((line, index) => {
|
||||||
|
const [name, quota, specialtyType = '', indicators = ''] = line.split('|').map(item => item.trim());
|
||||||
|
const indicatorAllocations = indicators.split(/[,,]/).map(entry => { const [sourceSchoolId, count] = entry.split(':').map(item => item.trim()); return { sourceSchoolId, quota: Number(count) }; }).filter(item => item.sourceSchoolId && item.quota > 0);
|
||||||
|
return { code: `category_${index + 1}`, name, quota: Number(quota), specialtyType, indicatorAllocations };
|
||||||
|
}).filter(item => item.name && item.quota > 0);
|
||||||
|
if (!body.categories.length) throw new Error('请按示例填写至少一行有效招生计划');
|
||||||
|
await api(kind === 'admission-plan' ? '/api/admin/admission-plans' : '/api/admission/plans', { method: 'POST', body });
|
||||||
|
toast(kind === 'admission-plan' ? '招生计划已代上传并通过' : '招生计划已提交审核'); renderRoute();
|
||||||
|
} else if (kind === 'placement-review') {
|
||||||
|
const body = formObject(form);
|
||||||
|
await api(`/api/admission/placements/${body.id}`, { method: 'PATCH', body });
|
||||||
|
toast(body.decision === 'accept' ? '已接收投档考生' : '退档申请已提交超级管理员'); renderRoute();
|
||||||
} else if (kind === 'school-form') {
|
} else if (kind === 'school-form') {
|
||||||
const body = formObject(form); body.active = form.active.checked;
|
const body = formObject(form); body.active = form.active.checked;
|
||||||
await api(body.id ? `/api/admin/schools/${body.id}` : '/api/admin/schools', { method: body.id ? 'PATCH' : 'POST', body });
|
await api(body.id ? `/api/admin/schools/${body.id}` : '/api/admin/schools', { method: body.id ? 'PATCH' : 'POST', body });
|
||||||
|
|||||||
+69
-3
@@ -35,6 +35,7 @@ export const relationalTables = [
|
|||||||
'workflow_steps',
|
'workflow_steps',
|
||||||
'workflow_instances',
|
'workflow_instances',
|
||||||
'workflow_actions',
|
'workflow_actions',
|
||||||
|
'admission_records',
|
||||||
'audit_logs'
|
'audit_logs'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ function validateState(state, source = '数据库') {
|
|||||||
'testCenters', 'testRooms', 'centerChangeRequests', 'centerChangeRooms',
|
'testCenters', 'testRooms', 'centerChangeRequests', 'centerChangeRooms',
|
||||||
'admissionNumberRules', 'arrangementPlans',
|
'admissionNumberRules', 'arrangementPlans',
|
||||||
'numberRules', 'candidateAccountBatches', 'candidateAccountBatchItems',
|
'numberRules', 'candidateAccountBatches', 'candidateAccountBatchItems',
|
||||||
'workflows', 'workflowInstances', 'workflowActions', 'auditLogs'
|
'workflows', 'workflowInstances', 'workflowActions', 'admissionRecords', 'auditLogs'
|
||||||
];
|
];
|
||||||
if (!state || typeof state !== 'object' || collections.some(name => !Array.isArray(state[name]))) {
|
if (!state || typeof state !== 'object' || collections.some(name => !Array.isArray(state[name]))) {
|
||||||
throw new Error(`${source}中的应用数据格式无效`);
|
throw new Error(`${source}中的应用数据格式无效`);
|
||||||
@@ -59,7 +60,7 @@ export 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 = 17, app_version = ?, self_registration_enabled = ?, created_at = ? WHERE id = 1',
|
'UPDATE schema_metadata SET schema_version = 18, 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()
|
||||||
);
|
);
|
||||||
@@ -335,6 +336,16 @@ export function buildSeedOperations(state) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const record of state.admissionRecords) {
|
||||||
|
add(
|
||||||
|
`INSERT INTO admission_records (
|
||||||
|
id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
record.id, record.kind, record.examId, nullable(record.userId), nullable(record.schoolId), record.status,
|
||||||
|
JSON.stringify(record.payload || {}), record.createdAt, record.updatedAt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (const log of state.auditLogs) {
|
for (const log of state.auditLogs) {
|
||||||
add(
|
add(
|
||||||
'INSERT INTO audit_logs (id, actor_id, action, detail, created_at) VALUES (?, ?, ?, ?, ?)',
|
'INSERT INTO audit_logs (id, actor_id, action, detail, created_at) VALUES (?, ?, ?, ?, ?)',
|
||||||
@@ -507,6 +518,9 @@ function stateFromRows(rows) {
|
|||||||
postalCode: row.postal_code || '',
|
postalCode: row.postal_code || '',
|
||||||
guardianName: row.guardian_name || '',
|
guardianName: row.guardian_name || '',
|
||||||
guardianPhone: row.guardian_phone || '',
|
guardianPhone: row.guardian_phone || '',
|
||||||
|
specialtyTypes: (() => { try { return JSON.parse(row.specialty_types || '[]'); } catch { return []; } })(),
|
||||||
|
specialtyCertificate: row.specialty_certificate || '',
|
||||||
|
policyEligibility: row.policy_eligibility || '',
|
||||||
profileCompleted: Boolean(row.profile_completed),
|
profileCompleted: Boolean(row.profile_completed),
|
||||||
status: row.status,
|
status: row.status,
|
||||||
reviewNote: row.review_note || '',
|
reviewNote: row.review_note || '',
|
||||||
@@ -738,6 +752,17 @@ function stateFromRows(rows) {
|
|||||||
toAssigneeId: row.to_assignee_id,
|
toAssigneeId: row.to_assignee_id,
|
||||||
createdAt: row.created_at
|
createdAt: row.created_at
|
||||||
})),
|
})),
|
||||||
|
admissionRecords: rows.admissionRecords.map(row => ({
|
||||||
|
id: row.id,
|
||||||
|
kind: row.kind,
|
||||||
|
examId: row.exam_id,
|
||||||
|
userId: row.user_id || null,
|
||||||
|
schoolId: row.school_id || null,
|
||||||
|
status: row.status,
|
||||||
|
payload: (() => { try { return JSON.parse(row.payload_json || '{}'); } catch { return {}; } })(),
|
||||||
|
createdAt: row.created_at,
|
||||||
|
updatedAt: row.updated_at
|
||||||
|
})),
|
||||||
auditLogs: rows.auditLogs.map(row => ({
|
auditLogs: rows.auditLogs.map(row => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
actorId: row.actor_id,
|
actorId: row.actor_id,
|
||||||
@@ -779,6 +804,7 @@ function readSqliteRows(connection) {
|
|||||||
workflowSteps: connection.prepare('SELECT * FROM workflow_steps ORDER BY workflow_id, position, id').all(),
|
workflowSteps: connection.prepare('SELECT * FROM workflow_steps ORDER BY workflow_id, position, id').all(),
|
||||||
workflowInstances: connection.prepare('SELECT * FROM workflow_instances ORDER BY created_at DESC, id').all(),
|
workflowInstances: connection.prepare('SELECT * FROM workflow_instances ORDER BY created_at DESC, id').all(),
|
||||||
workflowActions: connection.prepare('SELECT * FROM workflow_actions ORDER BY created_at, id').all(),
|
workflowActions: connection.prepare('SELECT * FROM workflow_actions ORDER BY created_at, id').all(),
|
||||||
|
admissionRecords: connection.prepare('SELECT * FROM admission_records ORDER BY created_at, id').all(),
|
||||||
auditLogs: connection.prepare('SELECT * FROM audit_logs ORDER BY created_at DESC, id DESC').all()
|
auditLogs: connection.prepare('SELECT * FROM audit_logs ORDER BY created_at DESC, id DESC').all()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -815,6 +841,7 @@ async function readMysqlRows(connection) {
|
|||||||
workflowSteps: await query('SELECT * FROM workflow_steps ORDER BY workflow_id, position, id'),
|
workflowSteps: await query('SELECT * FROM workflow_steps ORDER BY workflow_id, position, id'),
|
||||||
workflowInstances: await query('SELECT * FROM workflow_instances ORDER BY created_at DESC, id'),
|
workflowInstances: await query('SELECT * FROM workflow_instances ORDER BY created_at DESC, id'),
|
||||||
workflowActions: await query('SELECT * FROM workflow_actions ORDER BY created_at, id'),
|
workflowActions: await query('SELECT * FROM workflow_actions ORDER BY created_at, id'),
|
||||||
|
admissionRecords: await query('SELECT * FROM admission_records ORDER BY created_at, id'),
|
||||||
auditLogs: await query('SELECT * FROM audit_logs ORDER BY created_at DESC, id DESC')
|
auditLogs: await query('SELECT * FROM audit_logs ORDER BY created_at DESC, id DESC')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -925,6 +952,7 @@ function createRepository({ client, location, read, transaction, close }) {
|
|||||||
province_code = ?, province_name = ?, city_code = ?, city_name = ?, district_code = ?, district_name = ?, address = ?,
|
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 = ?,
|
||||||
|
specialty_types = ?, specialty_certificate = ?, policy_eligibility = ?,
|
||||||
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),
|
||||||
@@ -933,7 +961,8 @@ function createRepository({ client, location, read, transaction, close }) {
|
|||||||
optional(profile.address), optional(profile.schoolId),
|
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), JSON.stringify(profile.specialtyTypes || []),
|
||||||
|
optional(profile.specialtyCertificate), optional(profile.policyEligibility), profile.profileCompleted ? 1 : 0, optional(profile.reviewedAt),
|
||||||
optional(profile.reviewerId), profile.updatedAt, profile.id
|
optional(profile.reviewerId), profile.updatedAt, profile.id
|
||||||
),
|
),
|
||||||
operation('UPDATE users SET display_name = ? WHERE id = ?', displayName, profile.userId)
|
operation('UPDATE users SET display_name = ? WHERE id = ?', displayName, profile.userId)
|
||||||
@@ -1186,6 +1215,43 @@ function createRepository({ client, location, read, transaction, close }) {
|
|||||||
auditOperation(log)
|
auditOperation(log)
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
async createAdmissionSchoolAccount(user, log) {
|
||||||
|
await transaction([
|
||||||
|
operation(
|
||||||
|
`INSERT INTO users (
|
||||||
|
id, username, password_hash, role, admin_level, school_id, class_id, active, display_name, created_at
|
||||||
|
) VALUES (?, ?, ?, 'admission_school', NULL, ?, NULL, ?, ?, ?)`,
|
||||||
|
user.id, user.username, user.passwordHash, user.schoolId, user.active === false ? 0 : 1, user.displayName, user.createdAt
|
||||||
|
),
|
||||||
|
auditOperation(log)
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
async saveAdmissionRecord(record, log = null) {
|
||||||
|
const operations = [operation('DELETE FROM admission_records WHERE id = ?', record.id), operation(
|
||||||
|
`INSERT INTO admission_records (
|
||||||
|
id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
record.id, record.kind, record.examId, optional(record.userId), optional(record.schoolId), record.status,
|
||||||
|
JSON.stringify(record.payload || {}), record.createdAt, record.updatedAt
|
||||||
|
)];
|
||||||
|
if (log) operations.push(auditOperation(log));
|
||||||
|
await transaction(operations);
|
||||||
|
},
|
||||||
|
async saveAdmissionRecords(records, log = null) {
|
||||||
|
const operations = [];
|
||||||
|
for (const record of records) {
|
||||||
|
operations.push(operation('DELETE FROM admission_records WHERE id = ?', record.id));
|
||||||
|
operations.push(operation(
|
||||||
|
`INSERT INTO admission_records (
|
||||||
|
id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
record.id, record.kind, record.examId, optional(record.userId), optional(record.schoolId), record.status,
|
||||||
|
JSON.stringify(record.payload || {}), record.createdAt, record.updatedAt
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (log) operations.push(auditOperation(log));
|
||||||
|
await transaction(operations);
|
||||||
|
},
|
||||||
async saveSchool(school, isNew, log) {
|
async saveSchool(school, isNew, log) {
|
||||||
const change = isNew
|
const change = isNew
|
||||||
? operation(
|
? operation(
|
||||||
|
|||||||
Loaded 3 of 24 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user