feat: implementacao de fechamento automatico de pauta e unificacao relacional de frequencia
This commit is contained in:
parent
9a09d7852a
commit
bafd1a6292
|
|
@ -47,4 +47,5 @@
|
|||
18. **Automated Messaging (Cron Jobs)**: The system uses `node-cron` for independent message scheduling (Preventive vs. Overdue). Overdue logic MUST implement safety checks using `overdue_warnings_count` and `last_overdue_warning_at` to avoid spamming the student. Immediate webhook triggers for `PAYMENT_OVERDUE` are disabled in favor of scheduled routines.
|
||||
19. **Numerical Data Integrity**: When retrieving data from PostgreSQL `NUMERIC` or `DECIMAL` columns, values MUST be explicitly cast to `Number()` in the backend before being sent to the frontend to prevent crashes when using `.toFixed()` in React.
|
||||
20. **Exam Duplication**: The system supports cloning evaluations. Duplicated exams MUST default to `draft` status and include `(Cópia)` in the title to allow verification before deployment to new classes.
|
||||
21. **Automatic Attendance Closure**: The system implements an automatic closure routine (`processAutoAbsences`) that generates physical "Absence" records in the PostgreSQL database for any past lesson where a student has no presence or justification. This routine is triggered during data save operations to ensure retroactive consistency between lessons and records.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
- [x] **Migração Relacional de Frequência:** Portal migrado para ler frequências diretamente da tabela SQL `frequencias`. **VERIFICADO.**
|
||||
- [x] **Sincronização Bidirecional (Frequência):** Garantido que justificativas enviadas pelo Portal atualizem instantaneamente a tabela relacional via `ON CONFLICT`.
|
||||
- [x] **Auto-Migração de Esquema:** Implementada lógica de auto-correção de colunas (`ALTER TABLE`) na rotina de sincronização do banco de dados (`database.js`).
|
||||
- [x] **Fechamento Automático de Pauta:** Implementada rotina `processAutoAbsences` que gera registros físicos de falta para aulas passadas sem registro, garantindo consistência entre Portal e Manager.
|
||||
- [ ] Próximo Passo: Expandir a migração relacional para o módulo de "Minhas Aulas" no Portal para manter a consistência arquitetural.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,71 @@ export async function getSchoolData() {
|
|||
return rows[0]?.data || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Percorre as aulas concluídas e gera registros de falta para alunos que não compareceram.
|
||||
* Isso transforma faltas "virtuais" em registros físicos no banco de dados.
|
||||
*/
|
||||
export async function processAutoAbsences(data) {
|
||||
if (!data.lessons || !data.students || !data.attendance) return data;
|
||||
|
||||
const now = new Date();
|
||||
let updated = false;
|
||||
|
||||
// Cache de alunos por turma para performance
|
||||
const studentsByClass = {};
|
||||
|
||||
data.lessons.forEach(lesson => {
|
||||
const lessonEndStr = `${lesson.date}T${lesson.endTime || '23:59'}:00`;
|
||||
const lessonEnd = new Date(lessonEndStr);
|
||||
|
||||
if (now > lessonEnd && lesson.status !== 'cancelled') {
|
||||
if (!studentsByClass[lesson.classId]) {
|
||||
studentsByClass[lesson.classId] = data.students.filter(s => s.classId === lesson.classId && s.status === 'active');
|
||||
}
|
||||
|
||||
studentsByClass[lesson.classId].forEach(student => {
|
||||
const hasRecord = data.attendance.some(a =>
|
||||
a.studentId === student.id &&
|
||||
(a.lessonId === lesson.id || (a.date && a.date.startsWith(lesson.date)))
|
||||
);
|
||||
|
||||
if (!hasRecord) {
|
||||
data.attendance.push({
|
||||
id: `auto-abs-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
|
||||
studentId: student.id,
|
||||
classId: lesson.classId,
|
||||
lessonId: lesson.id,
|
||||
date: `${lesson.date}T${lesson.startTime || '00:00'}:00`,
|
||||
type: 'absence',
|
||||
verified: true,
|
||||
autoGenerated: true
|
||||
});
|
||||
updated = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (updated) {
|
||||
data.lastUpdated = new Date().toISOString();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function saveSchoolData(data) {
|
||||
// Aplicar fechamento de pauta automático antes de salvar
|
||||
const dataWithAbsences = await processAutoAbsences(data);
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO school_data (id, data, updated_at)
|
||||
VALUES (1, $1, NOW())
|
||||
ON CONFLICT (id) DO UPDATE SET data = $1, updated_at = NOW()`,
|
||||
[JSON.stringify(data)]
|
||||
[JSON.stringify(dataWithAbsences)]
|
||||
);
|
||||
|
||||
// Sincronizar tabelas relacionais em background para não travar o salvamento
|
||||
syncJsonToRelationalTables(dataWithAbsences).catch(err =>
|
||||
console.error('[Database:Sync] Erro na sincronização automática:', err)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -172,9 +172,9 @@ export default function Frequencia() {
|
|||
|
||||
// Stats calculation (aligned with list logic)
|
||||
const totalCourseLessons = lessons.length;
|
||||
const presences = attendance.filter(a => a.type === 'presence' || a.verified === true).length;
|
||||
const absences = attendance.filter(a => a.type === 'absence' && !a.verified && !a.justification).length;
|
||||
const justified = attendance.filter(a => !!a.justification).length;
|
||||
const presences = attendance.filter(a => a.type === 'presence').length;
|
||||
const absences = attendance.filter(a => a.type === 'absence' && !a.justification).length;
|
||||
const justified = attendance.filter(a => a.type === 'absence' && !!a.justification).length;
|
||||
const completedLessons = processedItems.filter(item => item.isCompleted && item.lesson.status !== 'cancelled').length;
|
||||
const pendingLessons = processedItems.filter(item => !item.isCompleted && item.lesson.status !== 'cancelled').length;
|
||||
const percentage = totalCourseLessons > 0 ? Math.round((presences / totalCourseLessons) * 100) : 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue