This commit is contained in:
2026-07-01 11:17:17 +05:00
parent 7f5ffc8301
commit 0bf4686b9a
4 changed files with 88 additions and 6 deletions

View File

@@ -79,6 +79,10 @@ async function initDb() {
db.run('INSERT OR IGNORE INTO subjects (code, name, is_mandatory) VALUES (?, ?, 1)', ['02', 'Математика']);
saveDb();
// Миграция: перенос Русского/Математики в обязательные слоты 01/02, если они пусты
migrateMandatorySubjects();
console.log('БД инициализирована');
return db;
}
@@ -90,6 +94,50 @@ function saveDb() {
fs.writeFileSync(DB_PATH, buffer);
}
function migrateMandatorySubjects() {
if (!db) return;
// Найти русский язык с кодом ≠ 01, где слот 01 пуст (нет grade)
const rusRows = dbAll(`
SELECT DISTINCT er.student_id, er.subject_code, er.primary_score, er.grade
FROM exam_results er
JOIN subjects s ON s.code = er.subject_code
WHERE s.name = 'Русский язык' AND er.subject_code != '01'
AND NOT EXISTS (
SELECT 1 FROM exam_results er2
WHERE er2.student_id = er.student_id AND er2.subject_code = '01' AND er2.grade IS NOT NULL
)
`);
for (const row of rusRows) {
dbRun('INSERT OR REPLACE INTO exam_results (student_id, subject_code, primary_score, grade) VALUES (?, ?, ?, ?)', [row.student_id, '01', row.primary_score, row.grade]);
dbRun('DELETE FROM exam_results WHERE student_id = ? AND subject_code = ?', [row.student_id, row.subject_code]);
}
// Найти математику с кодом ≠ 02, где слот 02 пуст
const mathRows = dbAll(`
SELECT DISTINCT er.student_id, er.subject_code, er.primary_score, er.grade
FROM exam_results er
JOIN subjects s ON s.code = er.subject_code
WHERE s.name = 'Математика' AND er.subject_code != '02'
AND NOT EXISTS (
SELECT 1 FROM exam_results er2
WHERE er2.student_id = er.student_id AND er2.subject_code = '02' AND er2.grade IS NOT NULL
)
`);
for (const row of mathRows) {
dbRun('INSERT OR REPLACE INTO exam_results (student_id, subject_code, primary_score, grade) VALUES (?, ?, ?, ?)', [row.student_id, '02', row.primary_score, row.grade]);
dbRun('DELETE FROM exam_results WHERE student_id = ? AND subject_code = ?', [row.student_id, row.subject_code]);
}
// Удалить лишние subjects (не 01/02) для русского и математики, если они без результатов
dbRun(`DELETE FROM subjects WHERE (name = 'Русский язык' AND code != '01') OR (name = 'Математика' AND code != '02')`);
if (rusRows.length > 0 || mathRows.length > 0) {
console.log(`Миграция: перенесено в обязательные — русский: ${rusRows.length}, математика: ${mathRows.length}`);
saveDb();
}
}
function getDb() {
if (!db) throw new Error('БД не инициализирована.');
return db;

View File

@@ -49,7 +49,18 @@ router.post('/import', upload.single('xlsfile'), (req, res) => {
for (const r of parsed.results) {
const sid = studentIdByKey[r.student_key.toLowerCase()];
if (sid) {
dbRun('INSERT OR REPLACE INTO exam_results (student_id, subject_code, primary_score, grade) VALUES (?, ?, ?, ?)', [sid, r.subject_code, r.primary_score, r.grade]);
let code = r.subject_code;
const name = (r.subject_name || '').toLowerCase();
if (name === 'русский язык') {
const existing = dbGet("SELECT grade FROM exam_results WHERE student_id = ? AND subject_code = '01'", [sid]);
if (!existing || existing.grade === null) code = '01';
} else if (name === 'математика') {
const existing = dbGet("SELECT grade FROM exam_results WHERE student_id = ? AND subject_code = '02'", [sid]);
if (!existing || existing.grade === null) code = '02';
}
dbRun('INSERT OR REPLACE INTO exam_results (student_id, subject_code, primary_score, grade) VALUES (?, ?, ?, ?)', [sid, code, r.primary_score, r.grade]);
}
}
@@ -104,6 +115,20 @@ router.get('/students', (req, res) => {
havingClause = 'HAVING elective_count = 0';
} else if (filter === 'nodata') {
havingClause = 'HAVING mandatory_grades < 2';
} else if (filter === 'conflict') {
whereClauses.push(`s.id IN (
SELECT DISTINCT er1.student_id FROM exam_results er1
JOIN subjects s1 ON s1.code = er1.subject_code
WHERE s1.name = 'Русский язык' AND er1.subject_code != '01'
AND EXISTS (SELECT 1 FROM exam_results er2 WHERE er2.student_id = er1.student_id AND er2.subject_code = '01')
UNION
SELECT DISTINCT er1.student_id FROM exam_results er1
JOIN subjects s1 ON s1.code = er1.subject_code
WHERE s1.name = 'Математика' AND er1.subject_code != '02'
AND EXISTS (SELECT 1 FROM exam_results er2 WHERE er2.student_id = er1.student_id AND er2.subject_code = '02')
)`);
} else if (filter === 'secondyear') {
havingClause = 'HAVING elective_count = 0 AND COUNT(er.id) = 1';
}
const wherePart = whereClauses.length ? 'WHERE ' + whereClauses.join(' AND ') : '';

View File

@@ -196,8 +196,9 @@ router.get('/batch-print', requireAuth, (req, res) => {
const mathResult = results.find(r => r.subject_code === '02') || null;
const electives = results.filter(r => !r.is_mandatory);
// Автоопределение: если есть элективы — ОГЭ, иначе ГВЭ; ручной type переопределяет
const autoType = electives.length > 0 ? 'gia' : 'gve';
// Автоопределение: элективы есть → ОГЭ, 1 предмет всего → второгодник ОГЭ, иначе ГВЭ
const totalResults = results.length;
const autoType = (electives.length > 0 || totalResults === 1) ? 'gia' : 'gve';
const finalType = (type === 'gia' || type === 'gve') ? type : autoType;
const gender = detectGender(st.patronymic);

View File

@@ -57,6 +57,12 @@
<label class="filter-label filter-nodata">
<input type="radio" name="filter" value="nodata" onchange="onFilterChange()"> Нет данных
</label>
<label class="filter-label" style="background:#ffe0e0;color:#900">
<input type="radio" name="filter" value="conflict" onchange="onFilterChange()"> Конфликт
</label>
<label class="filter-label" style="background:#fff0d0;color:#860">
<input type="radio" name="filter" value="secondyear" onchange="onFilterChange()"> Второгодники
</label>
</div>
<div class="search-bar">
@@ -151,10 +157,12 @@
function renderTable(students) {
const tbody = document.querySelector('#students-table tbody');
tbody.innerHTML = students.map((s, i) => {
const hasMandatory = (s.mandatory_grades || 0) >= 2;
const isSecondYear = (s.elective_count || 0) === 0 && s.exam_count === 1;
const hasMandatory = isSecondYear ? (s.mandatory_grades || 0) >= 1 : (s.mandatory_grades || 0) >= 2;
const rowClass = !hasMandatory ? 'row-warning' : '';
const examType = (s.elective_count || 0) > 0 ? 'ОГЭ' : 'ГВЭ';
const examTypeClass = (s.elective_count || 0) > 0 ? 'badge-gia' : 'badge-gve';
const isOge = (s.elective_count || 0) > 0 || isSecondYear;
const examType = isOge ? 'ОГЭ' : 'ГВЭ';
const examTypeClass = isOge ? 'badge-gia' : 'badge-gve';
return `
<tr class="${rowClass}">
<td>${i + 1}</td>