Files
gia-gve-certificates/src/routes/pages.js
2026-07-01 11:17:17 +05:00

236 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const express = require('express');
const { dbGet, dbAll } = require('../db');
const router = express.Router();
const requireAuth = (req, res, next) => {
if (!req.session.user) return res.redirect('/login');
next();
};
router.get('/login', (req, res) => {
if (req.session.user) return res.redirect('/');
res.render('login');
});
router.get('/', requireAuth, (req, res) => {
res.render('index', { user: req.session.user });
});
router.get('/student/:id', requireAuth, (req, res) => {
const student = dbGet('SELECT * FROM students WHERE id = ?', [req.params.id]);
if (!student) return res.status(404).send('Ученик не найден');
const results = dbAll(`
SELECT er.*, s.name as subject_name, s.is_mandatory, s.code as subject_code
FROM exam_results er
JOIN subjects s ON s.code = er.subject_code
WHERE er.student_id = ?
ORDER BY s.is_mandatory DESC, s.name
`, [req.params.id]);
const mandatoryResults = results.filter(r => r.is_mandatory);
const electiveResults = results.filter(r => !r.is_mandatory);
const rusResult = mandatoryResults.find(r => r.subject_code === '01') || null;
const mathResult = mandatoryResults.find(r => r.subject_code === '02') || null;
res.render('student', {
student,
electiveResults,
rusResult,
mathResult,
user: req.session.user
});
});
router.get('/import', requireAuth, (req, res) => {
res.render('import', { user: req.session.user });
});
router.get('/certificate/:id', requireAuth, (req, res) => {
const { id } = req.params;
const type = req.query.type || 'gia';
const subjectCodes = req.query.subjects ? req.query.subjects.split(',') : [];
const student = dbGet('SELECT * FROM students WHERE id = ?', [id]);
if (!student) return res.status(404).send('Ученик не найден');
const rusResult = dbGet(`
SELECT er.*, s.name as subject_name
FROM exam_results er JOIN subjects s ON s.code = er.subject_code
WHERE er.student_id = ? AND er.subject_code = '01'
`, [id]);
const mathResult = dbGet(`
SELECT er.*, s.name as subject_name
FROM exam_results er JOIN subjects s ON s.code = er.subject_code
WHERE er.student_id = ? AND er.subject_code = '02'
`, [id]);
let electives = [];
if (type === 'gia' && subjectCodes.length > 0) {
const placeholders = subjectCodes.map(() => '?').join(',');
electives = dbAll(`
SELECT er.*, s.name as subject_name
FROM exam_results er JOIN subjects s ON s.code = er.subject_code
WHERE er.student_id = ? AND er.subject_code IN (${placeholders})
`, [id, ...subjectCodes]);
}
const today = new Date();
const certNumber = `${today.getFullYear()}${String(id).padStart(4, '0')}`;
const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
const dateStr = `«${String(today.getDate()).padStart(2, '0')}» ${months[today.getMonth()]} ${today.getFullYear()} года`;
const gender = detectGender(student.patronymic);
const fioDative = formatNameDative(student, gender);
const verbEnding = gender === 'm' ? 'обучался' : 'обучалась';
const verbEnding2 = gender === 'm' ? 'получил' : 'получила';
res.render('certificate', {
type,
student: { ...student, fioDative, gender },
certNumber,
dateStr,
verbEnding,
verbEnding2,
rusResult: rusResult || null,
mathResult: mathResult || null,
electives,
allSubjects: type === 'gia'
? [
rusResult || { subject_name: 'Русский язык', primary_score: null, grade: null },
mathResult || { subject_name: 'Математика', primary_score: null, grade: null },
...electives
]
: [
rusResult || { subject_name: 'Русский язык', grade: null },
mathResult || { subject_name: 'Математика', grade: null }
]
});
});
function detectGender(patronymic) {
if (!patronymic) return 'm';
const last = patronymic.toLowerCase();
if (/вна|чна|нична$/.test(last)) return 'f';
return 'm';
}
function formatNameDative(student, gender) {
const l = student.last_name || '';
const f = student.first_name || '';
const p = student.patronymic || '';
if (gender === 'f') {
return `${declineFemaleLast(l)} ${declineFemaleFirst(f)} ${declineFemalePatr(p)}`;
}
return `${declineMaleLast(l)} ${declineMaleFirst(f)} ${declineMalePatr(p)}`;
}
function declineFemaleLast(name) {
if (!name) return '';
if (name.endsWith('ая')) return name.slice(0, -2) + 'ой';
if (name.endsWith('яя')) return name.slice(0, -2) + 'ей';
if (name.endsWith('а')) return name.slice(0, -1) + 'ой';
if (name.endsWith('я')) return name.slice(0, -1) + 'е';
if (name.endsWith('ова') || name.endsWith('ева') || name.endsWith('ина')) return name.slice(0, -1) + 'ой';
return name + 'ой';
}
function declineFemaleFirst(name) {
if (!name) return '';
if (name.endsWith('а') || name.endsWith('я')) return name.slice(0, -1) + 'е';
return name + 'е';
}
function declineFemalePatr(name) {
if (!name) return '';
if (name.endsWith('на')) return name.slice(0, -1) + 'е';
return name + 'е';
}
function declineMaleLast(name) {
if (!name) return '';
if (name.endsWith('ов') || name.endsWith('ев') || name.endsWith('ин') || name.endsWith('ёв')) return name + 'у';
if (name.endsWith('ий') || name.endsWith('ый')) return name.slice(0, -2) + 'ому';
if (name.endsWith('ой')) return name.slice(0, -2) + 'ому';
return name + 'у';
}
function declineMaleFirst(name) {
if (!name) return '';
if (name.endsWith('й') || name.endsWith('ь')) return name.slice(0, -1) + 'ю';
if (name.endsWith('а') || name.endsWith('я')) return name.slice(0, -1) + 'е';
return name + 'у';
}
function declineMalePatr(name) {
if (!name) return '';
if (name.endsWith('ич')) return name + 'у';
if (name.endsWith('на')) return name.slice(0, -1) + 'е';
return name + 'у';
}
router.get('/batch-print', requireAuth, (req, res) => {
const { class: cls, parallel, type } = req.query;
let students;
if (cls) {
students = dbAll('SELECT * FROM students WHERE class = ? ORDER BY last_name, first_name', [cls]);
} else if (parallel) {
students = dbAll('SELECT * FROM students WHERE class LIKE ? ORDER BY class, last_name, first_name', [parallel + '%']);
} else {
students = dbAll('SELECT * FROM students ORDER BY class, last_name, first_name');
}
const today = new Date();
const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
const dateStr = `«${String(today.getDate()).padStart(2, '0')}» ${months[today.getMonth()]} ${today.getFullYear()} года`;
const studentData = students.map(st => {
const results = dbAll(`
SELECT er.*, s.name as subject_name, s.is_mandatory
FROM exam_results er JOIN subjects s ON s.code = er.subject_code
WHERE er.student_id = ?
`, [st.id]);
const rusResult = results.find(r => r.subject_code === '01') || null;
const mathResult = results.find(r => r.subject_code === '02') || null;
const electives = results.filter(r => !r.is_mandatory);
// Автоопределение: элективы есть → ОГЭ, 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);
const fioDative = formatNameDative(st, gender);
let allSubjects;
if (finalType === 'gia') {
allSubjects = [
rusResult || { subject_name: 'Русский язык', primary_score: null, grade: null },
mathResult || { subject_name: 'Математика', primary_score: null, grade: null },
...electives
];
} else {
allSubjects = [
rusResult || { subject_name: 'Русский язык', grade: null },
mathResult || { subject_name: 'Математика', grade: null }
];
}
return {
student: { ...st, fioDative, gender },
certNumber: `${today.getFullYear()}${String(st.id).padStart(4, '0')}`,
dateStr,
verbEnding: gender === 'm' ? 'обучался' : 'обучалась',
verbEnding2: gender === 'm' ? 'получил' : 'получила',
allSubjects,
type: finalType
};
});
res.render('batch-certificate', { students: studentData });
});
module.exports = router;