Passa al contenuto principale

Dominio edu — Panoramica sviluppatore

🎯 Cosa fa

Il dominio edu (education / training) è il cuore applicativo di TrainingHub: catalogo formativo, erogazione dei corsi, partecipanti, certificati, qualifiche, nomine. Ha ~38 tabelle — le più di tutto il solution.

Questa pagina copre il core A3 (11 tabelle): catalogo + erogazione + docenti. Le restanti ~27 tabelle sono elencate come "fuori scope" e saranno documentate in spec successive.

🗺️ Mappa moduli

Database — TrainingHub.Database/edu/

Core in questa documentazione (12):

AreaTabellaRuolo
Catalogoedu.categoriesCategorie tematiche (piatte)
Catalogoedu.coursesDefinizione corso (FK trainingVariants, organizers)
Erogazioneedu.trainingSessionsSessione formativa: blocco fisico (giornata/giornate). Stati: planned/open/inProgress/completed/cancelled
Erogazioneedu.trainingSessionsCoursesM:N session↔corso (co-erogazione: una giornata può ospitare più corsi con finestre orarie distinte)
Erogazioneedu.trainingSessionsTeachersM:N session↔docente (default ereditato sugli appointment)
Erogazioneedu.appointmentsOccorrenza fisica all'interno di una session (FK trainingSessions NOT NULL, locations override)
Erogazioneedu.locationsAule (indirizzo inline; FK opzionale reg.companies come gestore)
Erogazioneedu.locationsTrainingVariantsM:N aula↔variant per filtrare aule abilitate alle prove pratiche (variant con requiresEquippedRoom = 1)
Docentiedu.teachersAnagrafica docenti
Docentiedu.appointmentsTeachersM:N appuntamento↔docente (override su default trainingSessionsTeachers, con hourlyAmount override)
Docentiedu.teacherCostsCosto orario per docente per organizzatore
Docentiedu.teacherSkillsSkill matrix docente↔variant normativa (trainingVariantId, score)

Cambio modello del 2026-05-08 (edu-sessions-redesign):

  • Le tabelle edu.lessons, edu.lessonsCourses e edu.courseSessionsArguments sono state droppate.
  • edu.courseSessions è stata rinominata edu.trainingSessions con DATETIME invece di DATE e l'aggiunta di locationId ereditato dagli appointment.
  • La relazione 1:1 courseSession → course è stata sostituita dalla M:N trainingSessionsCourses per supportare la co-erogazione (più corsi nella stessa giornata fisica).
  • edu.appointmentArguments è stata rinominata edu.trainingSessionsCoursesArguments (FK a trainingSessionsCourses).
  • edu.teacherSkills.lessonId è stato sostituito da trainingVariantId (granularità a variant).
  • Spec di riferimento: docs/superpowers/specs/_archive/2026-05-08-edu-sessions-redesign-design.md.

Estensioni coperte lato utente (dettaglio dev prioritizzato come lavoro futuro):

AreaTabelle principaliPagina utente
Worker training journeyworkersTrainings, workerTrainingDetails, trainingDetailAttempts, attendances, workerCompletionsCache, certificates, enrollmentCosts, priorTrainingsScheda formativa lavoratore
Argomenti formativitrainingArguments, trainingTopics, trainingVariants, variantTopicMap, trainingTopicDependencies, trainingSessionsCoursesArguments, trainingArgumentChecks, trainingVariantsOverlaps, appointmentChecksArgomenti e varianti
Nominenominations, workersNominations, nominationsTrainingTopicsNomine e incarichi
Ruoli & qualificherolesRequiredTrainings, rolesForbiddenTrainings, qualificationsExemptions, qualificationsRequiredTrainingsRuoli e requisiti
Attrezzature & credenzialiequipments, workerEquipments, workerCredentialsAttrezzature

Lo schema DB dettagliato di queste tabelle estensione non è incluso in questa sezione: Schema DB copre solo le 11 core. Documentazione dev estesa pianificata come lavoro futuro.

Service layer — TrainingHub.Shared/Services/

FileRuolo
ITrainingExpirationService.cs / TrainingExpirationService.csCalcola compliance formativa per azienda/lavoratore: attestati in scadenza, mancanti, statistiche
IRiskInheritanceService.cs / RiskInheritanceService.csPropagazione livello di rischio tra azienda, mansione e lavoratore
SessionPlanner/ISessionPlannerService.cs / SessionPlannerService.csOrchestrazione wizard 6-step (sessione + corsi + appuntamenti + iscritti + docenti + costi) e rilevamento conflitti (DetectConflictsAsync) usati anche dal calendario al salvataggio appuntamento
IAppointmentsCalendarService.cs / AppointmentsCalendarService.csData provider del calendario: GetAppointmentsAsync con filtri Corso/Sede/Docente/Sessione + range temporale, GetSessionContextAsync per il pannello dettaglio

I primi due sono in TrainingHub.Shared perché usati anche da TrainingHub.Import oltre che dalla BackOffice. Il SessionPlanner è in Shared perché i suoi tipi (SessionPlannerState, SessionCoursePlan, ComplianceEnrollmentRequest/Result) sono consumati anche dai test.

QueryModifiers — TrainingHub.BackOffice/Services/QueryModifiers/edu/

FileRuolo
TeachersQueryModifier.csHook CRUD teachers
PriorTrainingsQueryModifier.csHook CRUD priorTrainings (fuori scope)
TrainingVariantsQueryModifier.csHook CRUD trainingVariants (fuori scope)
TrainingVariantsOverlapsQueryModifier.csHook CRUD trainingVariantsOverlaps (fuori scope)
WorkerTrainingDetailsQueryModifier.csHook CRUD workerTrainingDetails (fuori scope)

UI CRUD — TrainingHub.BackOffice/Components/CRUD/edu/

~38 entità CRUD auto-generate. Pattern identico a inv e reg (vedi componenti UI inv). Le 12 core di questa spec: Category, Course, TrainingSession, TrainingSessionsCourse, TrainingSessionsTeacher, Appointment, Location, LocationsTrainingVariant, Teacher, AppointmentsTeacher, TeacherCost, TeacherSkill.

Componenti specifici non standard:

  • Pages/AppointmentsCalendar/ — pagina calendario appuntamenti scritta a mano (route /appointments-calendar). Sub-componenti: AppointmentsCalendar.razor (host), CalendarGrid.razor (vista mese/settimana/giorno), AppointmentDetailPanel.razor (pannello laterale). Integrata con SessionPlannerPopup (pulsante "Nuova Sessione") e AppointmentFormPopup (click su slot libero).
  • Components/edu/SessionPlanner/ — wizard 6-step di pianificazione sessione formativa (SessionPlannerPopup.razor + Step1Session/ Step2Courses/Step3Schedule/Step4Teachers/Step5Workers/Step6Review).
  • CRUD/edu/AppointmentsCalendar.razor — versione CRUD-generata legacy della vista calendario; mantenuta finché i conf JSON non vengono ripuliti, da droppare poi.
  • AppointmentsData.razor — vista dati appuntamenti aggregata
  • ActiveWorker.razor, AllCompletion.razor, AllRequiredTraining.razor — componenti analitici cross-dominio

🔧 API pubblica

ITrainingExpirationService

public interface ITrainingExpirationService
{
Task<IEnumerable<trainingExpiration>> GetExpiringTrainingsAsync(
Guid? companyId = null,
string? status = null);
Task<ComplianceStats> GetComplianceStatsAsync(Guid? companyId = null);
}

public record ComplianceStats(
int TotalWorkers,
int CompliantWorkers,
int ExpiredCount,
int ExpiringCount,
int MissingCount);

Usato da Company.razor.cs (dominio reg) per il pannello di compliance aggregata. Lo stato status filtra su ok, expiring, expired, missing (vedi WorkerTrainingStatusValue in Enums.cs).

IRiskInheritanceService

Propaga rischi azienda → mansione → lavoratore. Invocato da CompaniesQueryModifier (dominio reg) in cascade su update aziende.

🧩 Pattern chiave

Vista calendario custom

Pages/AppointmentsCalendar/AppointmentsCalendar.razor è una vista calendario degli appuntamenti scritta a mano (non CRUD-generata). Caratteristiche:

  • Viste Mese / Settimana / Giorno con navigazione previous/Oggi/next.
  • Filtri Corso / Sede / Docente / Sessione (combobox riempiti da SimpleCRUD.GetListAsync). Il filtro Corso restringe a cascata l'elenco Sessioni via sub-query su edu.trainingSessionsCourses.
  • Colore appuntamento dal primo trainingTopic (alfabetico) della session, derivato via edu.trainingSessionsCourses → edu.courses → edu.trainingVariants.trainingTopicId → edu.trainingTopics.color. Background pastello via color-mix CSS, fallback grigio se topic senza colore. Legenda topic dinamica + legenda stati.
  • Click su slot libero → apre AppointmentFormPopup per nuovo appuntamento; click su appuntamento → AppointmentDetailPanel con azioni Edit / Duplica / Modifica sessione.
  • Pulsante "Nuova Sessione" apre SessionPlannerPopup; al confirm ricarica gli appuntamenti del periodo.
  • Dopo il salvataggio di un appuntamento chiama ISessionPlannerService.DetectConflictsAsync e mostra toast warning per ogni ConflictKind rilevato (docente sovrapposto, sede occupata, iscritti oltre capienza, soglia docente, ecc.).
  • Query param ?teacherId=<guid> pre-popola il filtro docente (landing da Calendario docente).

Pattern utile da replicare per entità con forte dimensione temporale.

Cache materializzata

workerCompletionsCache (fuori scope) è una tabella materializzata che aggrega i completamenti formativi per lavoratore. Refreshata da QueryModifiers dopo operazioni che invalidano il cache (vedi retrospettiva per riferimenti a lavori recenti sul cache).

Service di dominio per orchestrazione complessa

Per i CRUD semplici la logica vive in QueryModifier (TeachersQueryModifier) e in .razor.cs custom. Per i flussi complessi sono stati estratti:

  • ISessionPlannerService — orchestrazione wizard 6-step (sessione + corsi + appuntamenti + iscritti + docenti + costi) + rilevamento conflitti (DetectConflictsAsync ritorna ConflictKind enum con teacher_double_booked, location_double_booked, enrollment_overflow, teacher_threshold, worker_double_enrolled, lesson_skipped).
  • IAppointmentsCalendarService — data provider per la vista calendario.
  • ITrainingExpirationService — compliance/scadenze.

📦 Dipendenze

  • Brighela.SimpleCRUD — CRUD base + QueryModifier
  • Oss.Filters, Tabiot.Blazor.*, DevExpress.Blazor — UI stack comune

Cross-dominio:

  • edu.courses.organizerSlugreg.organizers(slug)
  • edu.locations.companyIdreg.companies(id) (opzionale)
  • edu.teacherCosts.organizerSlugreg.organizers(slug)
  • edu.courses.trainingVariantIdedu.trainingVariants(id) (fuori scope)

📁 File chiave

  • Database/edu/Tables/*.sql (~38)
  • BackOffice/Components/CRUD/edu/*.razor{,.cs,.tt.cs}
  • BackOffice/Services/QueryModifiers/edu/*.cs
  • Shared/Services/ITrainingExpirationService.cs, IRiskInheritanceService.cs (+ implementazioni)
  • Shared/Services/SessionPlanner/ISessionPlannerService.cs, SessionPlannerService.cs, SessionPlannerState.cs, SessionCoursePlan.cs, ComplianceEnrollmentRequest/Result.cs
  • Shared/Services/IAppointmentsCalendarService.cs + BackOffice/Services/AppointmentsCalendarService.cs
  • BackOffice/Components/Pages/AppointmentsCalendar/ — vista calendario custom (AppointmentsCalendar.razor, CalendarGrid.razor, AppointmentDetailPanel.razor)
  • BackOffice/Components/edu/SessionPlanner/ — wizard 6-step (SessionPlannerPopup.razor + Step1..Step6 partial classes)

⚠️ Domande aperte / debito tecnico

  • trainingVariantId obbligatorio ma variante fuori scope. Ogni corso richiede una variante normativa; la gestione delle varianti è in un altro sottodominio complesso (overlap, dipendenze). Creare corso senza aver prima configurato variante è bloccato. Ordine logico di setup da documentare.
  • appointments.courseSessionId nullable. Risolto in edu-sessions-redesign 2026-05-08: ora è trainingSessionId NOT NULL.
  • Cache workerCompletionsCache non atomica. Aggiornata da trigger/hook vari: coerenza in caso di failure non chiara.
  • Validazione vincoli propedeutici lessonsCourses. Risolto in edu-sessions-redesign 2026-05-08 droppando lessonsCourses (i non è chiaro dove (in quale service/query) queste vengano applicate al flusso iscrizione-completamento.
  • Auto-generazione appuntamenti da sessione. Coperto dal SessionPlannerService (wizard 6-step): la sessione viene creata come bozza, i corsi erogati e gli appuntamenti vengono aggiunti via gli step del wizard.

🔗 Vedi anche