准考证优化
This commit is contained in:
+71
-1
@@ -4,7 +4,7 @@ import { resolve } from 'node:path';
|
||||
import assert from 'node:assert/strict';
|
||||
import ExcelJS from 'exceljs';
|
||||
import { relationalTables } from '../database.mjs';
|
||||
import { buildWorkbook } from '../excel.mjs';
|
||||
import { buildCenterMaterialsWorkbook, buildWorkbook } from '../excel.mjs';
|
||||
|
||||
const root = resolve(process.cwd());
|
||||
const port = 4182;
|
||||
@@ -91,9 +91,15 @@ try {
|
||||
WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
|
||||
ORDER BY name
|
||||
`).all().map(row => row.name);
|
||||
const schemaVersion = inspector.prepare('SELECT schema_version FROM schema_metadata WHERE id = 1').get().schema_version;
|
||||
const admitCardColumns = inspector.prepare('PRAGMA table_info(admit_cards)').all().map(row => row.name);
|
||||
const admitSubjectColumns = inspector.prepare('PRAGMA table_info(admit_card_subjects)').all().map(row => row.name);
|
||||
inspector.close();
|
||||
assert.deepEqual(tableNames, [...relationalTables].sort(), '业务数据必须按关系模型分表存储');
|
||||
assert.ok(!tableNames.includes('app_state'), '不得使用单表 JSON 状态存储');
|
||||
assert.equal(schemaVersion, 10, '准考证详细位置快照应使用 v10 数据结构');
|
||||
assert.ok(['center_code', 'center_address'].every(column => admitCardColumns.includes(column)), '准考证应保存考点代码与详细地址快照');
|
||||
assert.ok(['building', 'floor'].every(column => admitSubjectColumns.includes(column)), '分科考场应保存楼栋楼层快照');
|
||||
|
||||
const publicHome = await anonymous.request('/api/public/home');
|
||||
assert.equal(publicHome.response.status, 200);
|
||||
@@ -406,8 +412,14 @@ try {
|
||||
const generateAdmit = await admin.request(`/api/admin/exams/${exam.id}/admission-arrangement`, { method: 'POST', body: arrangementBody });
|
||||
const generatedCard = generateAdmit.data.cards.find(item => item.registrationId === registrationId);
|
||||
assert.ok(generatedCard.number, '管理员应能按整场考试批量生成准考证号');
|
||||
assert.ok(generatedCard.centerCode && generatedCard.centerAddress, '准考证应包含考点代码和完整地址');
|
||||
assert.equal(new Set(generatedCard.assignments.map(item => item.centerId)).size, 1, '同一考生所有科目必须固定在同一考点');
|
||||
assert.equal(generatedCard.assignments.length, submitRegistration.data.registration.subjectIds.length, '每个报考科目都必须有独立考场座位');
|
||||
assert.ok(generatedCard.assignments.every(item => item.examRoomCode && item.roomName && item.roomCode && item.building && item.floor), '逐科安排必须同时保存考试考场序号、物理场地和楼栋楼层');
|
||||
const codesByPhysicalRoom = Map.groupBy
|
||||
? Map.groupBy(generateAdmit.data.cards.flatMap(card => card.assignments), item => item.roomId)
|
||||
: generateAdmit.data.cards.flatMap(card => card.assignments).reduce((groups, item) => groups.set(item.roomId, [...(groups.get(item.roomId) || []), item]), new Map());
|
||||
assert.ok([...codesByPhysicalRoom.values()].every(items => new Set(items.map(item => item.examRoomCode)).size === 1), '同一场考试内,同一物理考场跨科目必须保持同一个考试考场序号');
|
||||
assert.equal((await admin.request(`/api/admin/registrations/${registrationId}/admit-card`, { method: 'POST', body: {} })).response.status, 410, '旧单人生成入口应明确停用');
|
||||
|
||||
const candidateRegistrations = await candidate.request('/api/candidate/registrations');
|
||||
@@ -417,8 +429,66 @@ try {
|
||||
const downloadAdmit = await candidate.request(`/api/candidate/registrations/${registrationId}/admit-card`);
|
||||
assert.equal(downloadAdmit.response.status, 200, '规定时间内应可下载准考证');
|
||||
assert.match(downloadAdmit.data, /测试考生新名/);
|
||||
assert.match(downloadAdmit.data, /考试考场序号/);
|
||||
assert.match(downloadAdmit.data, /考场通用名称 \/ 场地代码/);
|
||||
assert.match(downloadAdmit.data, new RegExp(generatedCard.assignments[0].building));
|
||||
assert.match(downloadAdmit.data, new RegExp(generatedCard.assignments[0].floor));
|
||||
assert.match(downloadAdmit.response.headers.get('content-disposition') || '', /attachment/);
|
||||
|
||||
const schoolAdmitContext = await schoolAdmin.request('/api/admin/admission-arrangements');
|
||||
assert.equal(schoolAdmitContext.response.status, 200, '校级管理员应有校内准考证入口');
|
||||
assert.equal(schoolAdmitContext.data.canArrange, false, '校级管理员不能执行整场编排');
|
||||
assert.ok(schoolAdmitContext.data.registrations.every(item => item.candidate.schoolId === 'school_hz1'), '校级准考证台账必须限制为本校考生');
|
||||
const classAdmitContext = await classAdmin.request('/api/admin/admission-arrangements');
|
||||
assert.equal(classAdmitContext.response.status, 200, '班级管理员应有本班准考证入口');
|
||||
assert.ok(classAdmitContext.data.registrations.every(item => item.candidate.classId === 'class_hz1_302'), '班级准考证台账必须限制为本班考生');
|
||||
|
||||
const schoolBatchCards = await schoolAdmin.request(`/api/admin/admission-exports/admit-cards?examId=${exam.id}`);
|
||||
assert.equal(schoolBatchCards.response.status, 200, '校级管理员应可批量下载校内准考证');
|
||||
assert.match(schoolBatchCards.response.headers.get('content-disposition') || '', /attachment/);
|
||||
assert.match(schoolBatchCards.data, /考试考场序号/);
|
||||
const classBatchCards = await classAdmin.request(`/api/admin/admission-exports/admit-cards?examId=${exam.id}`);
|
||||
assert.equal(classBatchCards.response.status, 200, '班级管理员应可批量下载本班准考证');
|
||||
assert.match(classBatchCards.data, /测试考生新名/);
|
||||
|
||||
const classInfoExport = await classAdmin.request(`/api/admin/admission-exports/info?examId=${exam.id}`);
|
||||
assert.equal(classInfoExport.response.status, 200, '班级管理员应可导出本班准考证信息台账');
|
||||
const classInfoBook = new ExcelJS.Workbook();
|
||||
await classInfoBook.xlsx.load(classInfoExport.data);
|
||||
const classInfoSheet = classInfoBook.getWorksheet('准考证信息');
|
||||
assert.ok(classInfoSheet, '准考证信息导出应有独立工作表');
|
||||
assert.equal(classInfoSheet.rowCount, generatedCard.assignments.length + 2, '准考证信息应按考生逐科导出');
|
||||
assert.equal(classInfoSheet.getCell('D3').value, '测试考生新名');
|
||||
assert.equal(classInfoSheet.getCell('O3').value, generatedCard.assignments[0].examRoomCode, '信息台账应明确导出考试考场序号');
|
||||
assert.equal(classInfoSheet.getCell('P3').value, generatedCard.assignments[0].roomName, '信息台账应另列考场通用名称');
|
||||
|
||||
const schoolMaterials = await schoolAdmin.request(`/api/admin/admission-exports/center-materials?examId=${exam.id}`);
|
||||
assert.equal(schoolMaterials.response.status, 200, '校级管理员应可导出本校维护考点的现场物料');
|
||||
const materialsBook = new ExcelJS.Workbook();
|
||||
await materialsBook.xlsx.load(schoolMaterials.data);
|
||||
assert.deepEqual(materialsBook.worksheets.map(sheet => sheet.name), ['桌贴', '门贴', '考场签名单'], '考点物料应包含桌贴、门贴和考场签名单三张表');
|
||||
const deskStickerSheet = materialsBook.getWorksheet('桌贴');
|
||||
assert.ok(deskStickerSheet.rowCount >= 8, '每张方形桌贴应占用完整标签卡片区域');
|
||||
assert.equal(deskStickerSheet.pageSetup.orientation, 'portrait', '方形桌贴应使用 A4 纵向打印');
|
||||
assert.equal(deskStickerSheet.getRow(1).height, 32.5, '桌贴应使用固定行高形成方形标签');
|
||||
assert.equal(deskStickerSheet.getColumn(1).width, deskStickerSheet.getColumn(3).width, '桌贴卡片各列应等宽');
|
||||
assert.ok(deskStickerSheet.model.merges.includes('A1:C1'), '第一张桌贴应跨三列形成完整方形卡片');
|
||||
assert.match(String(deskStickerSheet.getCell('A3').value), /考试考场序号/);
|
||||
assert.equal(deskStickerSheet.getCell('A4').text, generatedCard.assignments[0].examRoomCode, '桌贴应突出考试考场序号');
|
||||
assert.match(String(deskStickerSheet.getCell('A5').value), new RegExp(generatedCard.assignments[0].seat), '桌贴应突出座位号');
|
||||
assert.match(String(materialsBook.getWorksheet('门贴').getCell('A1').value), /考场门贴/);
|
||||
assert.equal((await classAdmin.request(`/api/admin/admission-exports/center-materials?examId=${exam.id}`)).response.status, 403, '班级管理员不得导出考点级现场物料');
|
||||
|
||||
const paginationRows = Array.from({ length: 7 }, (_, index) => ({
|
||||
examCode: 'EX-QA', examName: '桌贴分页测试', subjectName: '语文', subjectDate: '2026-10-01', subjectTime: '09:00—11:00',
|
||||
centerCode: 'C01', centerName: '测试考点', examRoomCode: '001', roomName: '第一教室', roomCode: 'R01', building: '教学楼', floor: '1 层',
|
||||
seat: String(index + 1).padStart(2, '0'), cardNumber: `CARD${index + 1}`, candidateName: `考生${index + 1}`, schoolName: '测试学校', className: '测试班'
|
||||
}));
|
||||
const paginationBook = new ExcelJS.Workbook();
|
||||
await paginationBook.xlsx.load(await buildCenterMaterialsWorkbook(paginationRows, '分页测试'));
|
||||
assert.equal(paginationBook.getWorksheet('桌贴').pageSetup.printArea, 'A1:G48', '超过六张桌贴时应扩展为两页固定打印区域');
|
||||
assert.equal(paginationBook.getWorksheet('桌贴').getRow(32).height, 32.5, '第二页桌贴仍应保持相同方形标签高度');
|
||||
|
||||
const futureExamResponse = await admin.request('/api/admin/exams', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
|
||||
Reference in New Issue
Block a user