Passa al contenuto principale

Schema DB edu

🎯 Cosa fa​

Definisce il persistence layer della formazione: ~38 tabelle nel dominio edu. Questa pagina documenta in dettaglio le core del catalogo + erogazione + docenti, allineate al redesign edu-sessions-redesign del 2026-05-08 (co-erogazione multi-corso, drop lessons/lessonsCourses, rename courseSessions β†’ trainingSessions).

πŸ—ΊοΈ Tabelle core​

TabellaRuolo
edu.categoriesTassonomia piatta dei corsi
edu.coursesDefinizione corso (FK trainingVariants, organizers)

Le tabelle edu.lessons e edu.lessonsCourses sono state droppate dal redesign 2026-05-08. La struttura didattica vive ora su trainingVariants / trainingArguments (fuori scope di questa pagina) + trainingSessionsCoursesArguments per gli argomenti effettivamente trattati nella sessione.

Erogazione​

TabellaRuolo
edu.trainingSessionsSessione formativa: blocco fisico (giornata/giornate). FK opzionali locations (aula default), trainingTopics (vincola i corsi), core.recipientGroups (notifiche). Stati: planned/open/inProgress/completed/cancelled
edu.trainingSessionsCoursesM:N session ↔ corso (co-erogazione). Ogni riga Γ¨ una "vista contrattuale" del corso erogato nella session, con finestra oraria opzionale per-corso (NULL = eredita dalla session)
edu.trainingSessionsCoursesArgumentsArgomenti didattici trattati per ogni corso erogato (FK a trainingSessionsCourses). Rinomina di appointmentArguments. Etichetta libera + FK opzionale a trainingArguments di catalogo
edu.trainingSessionsTeachersDocenti agganciati alla session (default ereditato dagli appointment se questi non hanno override)
edu.appointmentsOccorrenza fisica (finestra oraria) all'interno di una session. FK trainingSessions NOT NULL, locations override opzionale
edu.locationsAule (indirizzo inline; FK opzionale reg.companies come gestore)
edu.locationsTrainingVariantsM:N aula ↔ variant per filtrare aule abilitate alle prove pratiche (variant con requiresEquippedRoom = 1)

Docenti​

TabellaRuolo
edu.teachersAnagrafica docenti (label = computed firstName + ' ' + lastName)
edu.appointmentsTeachersM:N appuntamento ↔ docente (override su default trainingSessionsTeachers, con hourlyAmount opzionale)
edu.teacherCostsCosto orario per docente per organizzatore
edu.teacherSkillsSkill matrix docente ↔ variante normativa (trainingVariantId, score). GranularitΓ  a variant (non piΓΉ a lesson, ridenominata dal redesign 2026-05-08)

πŸͺŸ Viste​

VistaRuolo
edu.vw_appointmentsCalendarBacking della vista calendario /appointments-calendar: appuntamenti arricchiti con info session, sede, docenti aggregati, primo trainingTopic (per il colore)
edu.vw_appointmentsDataDati appuntamenti aggregati (totale ore, partecipanti, ecc.)
edu.vw_appointmentAttendeesPartecipanti per appuntamento
edu.vw_workerTrainingStatusStato formativo per coppia (lavoratore Γ— formazione richiesta): status (ok/expiring/expired/missing), daysRemaining, ultima formazione erogata, fonte del requisito (ruolo/mansione/qualifica/attrezzatura/rischio/esenzione). Esposta come CRUD read-only WorkerTrainingStatus. Aggregata da job.vw_workerComplianceSummary
edu.vw_trainingSessionAppointmentsTeachersVista filtrata di appointmentsTeachers per trainingSessionId (usata negli step del session planner)
edu.vw_workerEffectiveRisksRischi effettivi del lavoratore (JOIN diretto su workersRisks)
edu.vw_workersDataLavoratori arricchiti con dati di compliance
edu.vw_companyComplianceStatusStato compliance per azienda
edu.vw_trainingExpirationsFormazioni in scadenza
edu.vw_availableTrainingSessionsSessioni disponibili (per iscrizione)

πŸ”— Relazioni​

Referenze esterne:

  • edu.courses.organizerSlug β†’ reg.organizers(slug)
  • edu.courses.trainingVariantId β†’ edu.trainingVariants(id) (subsystem normative)
  • edu.trainingSessions.responsibleGroupId β†’ core.recipientGroups(id) (notifiche operatore)
  • edu.trainingSessions.trainingTopicId β†’ edu.trainingTopics(id)
  • edu.locations.companyId β†’ reg.companies(id) (opzionale, gestore aula)
  • edu.teacherCosts.organizerSlug β†’ reg.organizers(slug)

πŸ—‚οΈ Dettaglio tabelle​

edu.courses​

  • PK: id
  • FK: trainingVariantId β†’ edu.trainingVariants(id), organizerSlug β†’ reg.organizers(slug)
  • Campi: code, label, description, active (default 1), elearning (default 0), inCatalog (default 1), maxPeople, extraInstructorThreshold
  • Check: maxPeople > 0, extraInstructorThreshold > 0

edu.trainingSessions​

Giornata (o giornate consecutive) di erogazione formativa, puΓ² ospitare uno o piΓΉ corsi in co-erogazione.

  • PK: id
  • FK:
    • locationId β†’ edu.locations(id) (opzionale, aula default)
    • responsibleGroupId β†’ core.recipientGroups(id) (opzionale, gruppo destinatari notifiche operatore)
    • trainingTopicId β†’ edu.trainingTopics(id) (opzionale; se valorizzato, vincola i corsi della sessione a quel topic)
  • Campi: startDateTime (required, DATETIME), endDateTime (nullable: NULL = single-block), maxPeople (capienza pratica della giornata), status, notes (1024 char)
  • Check:
    • endDateTime IS NULL OR endDateTime >= startDateTime
    • maxPeople > 0
    • status IN ('planned','open','inProgress','completed','cancelled')
  • Indici: IX_trainingSessions_startDateTime, IX_trainingSessions_locationId (filtered), IX_trainingSessions_trainingTopicId (filtered), IX_trainingSessions_responsibleGroupId (filtered)

edu.trainingSessionsCourses​

M:N session ↔ corso: ogni riga rappresenta un corso erogato dentro la session, con finestra oraria opzionale per-corso (per co-erogazione di livelli diversi nello stesso giorno).

  • PK: id
  • FK: trainingSessionId β†’ edu.trainingSessions(id), courseId β†’ edu.courses(id)
  • Unique: (trainingSessionId, courseId) (un corso compare al piΓΉ una volta per session)
  • Campi: startDateTime, endDateTime (entrambi nullable: NULL = eredita dalla session). Vincolo CK_trainingSessionsCourses_window forza la coppia consistente.
  • Indice: IX_trainingSessionsCourses_courseId

edu.trainingSessionsCoursesArguments​

Argomenti didattici trattati per ciascun corso erogato (rinomina di appointmentArguments dal redesign 2026-05-08).

  • PK: id
  • FK: trainingSessionsCoursesId β†’ edu.trainingSessionsCourses(id), trainingArgumentId β†’ edu.trainingArguments(id) (opzionale, riferimento a catalogo)
  • Campi: label (string libero), description

edu.trainingSessionsTeachers​

Default docenti agganciati alla session: ereditato dagli appointment che non hanno override su appointmentsTeachers.

  • PK composita: (trainingSessionId, teacherId)
  • FK: a trainingSessions e teachers
  • Indice: IX_trainingSessionsTeachers_teacherId

edu.appointments​

Occorrenza fisica (finestra oraria) all'interno di una session.

  • PK: id
  • FK:
    • trainingSessionId β†’ edu.trainingSessions(id) (NOT NULL dal redesign 2026-05-08)
    • locationId β†’ edu.locations(id) (nullable, override dell'aula default della session)
  • Campi: startDateTime, endDateTime (entrambi required), link (NVARCHAR MAX), notes (NVARCHAR MAX)
  • Check: endDateTime > startDateTime
  • Indici: IX_appointments_trainingSessionId, IX_appointments_locationId (filtered), IX_appointments_startDateTime

lessonId Γ¨ stato droppato. L'appuntamento non riferisce piΓΉ una lezione specifica: il "contenuto" Γ¨ derivato dai trainingSessionsCourses della session e dai relativi trainingSessionsCoursesArguments.

edu.locations​

Aule / spazi di erogazione. EntitΓ  autosufficiente: l'indirizzo Γ¨ inline sull'aula stessa, non rimandato a una sede.

  • PK: id
  • FK: companyId β†’ reg.companies(id) (opzionale: gestore aula esterno; NULL = aula 3SD)
  • Campi anagrafici: shortName, label (required), description, instructions, active, digital, capacity
  • Tariffa: unitCost, costMode ('hourly' | 'daily'), coppia consistente (entrambi NULL = aula gratuita) garantita da CK_locations_costPair
  • Contatti / fatturazione: email, vatCode (P.IVA gestore), invoicingLabel
  • Indirizzo geocoded: formattedAddress, country, province, city, zipCode, address, streetNumber, latitude, longitude

edu.locationsTrainingVariants​

M:N aula ↔ variante normativa: utilizzata per filtrare le aule abilitate alle prove pratiche di una variante (requiresEquippedRoom = 1).

edu.teachers​

  • PK: id
  • Campi: firstName, lastName, label (computed column: firstName || ' ' || lastName), active, notes

edu.appointmentsTeachers​

  • PK composita: (appointmentId, teacherId)
  • FK: a appointments e teachers
  • Campi: hourlyAmount (nullable: override sul costo standard da teacherCosts)
  • Indice: IX_appointmentsTeachers_teacherId

edu.teacherCosts​

  • PK composita: (teacherId, organizerSlug)
  • FK: teacherId β†’ edu.teachers(id), organizerSlug β†’ reg.organizers(slug)
  • Campi: hourlyAmount (required)

edu.teacherSkills​

Skill matrix docente ↔ variante normativa: abilitazione del formatore per quella variante (granularitΓ  a variant dal redesign 2026-05-08, non piΓΉ a lesson).

  • PK composita: (teacherId, trainingVariantId)
  • FK: teacherId β†’ edu.teachers(id), trainingVariantId β†’ edu.trainingVariants(id)
  • Campi: score (nullable), active (default 1)
  • Indice: IX_teacherSkills_trainingVariantId (filtered su active = 1)

edu.trainingTopics​

Argomento didattico di alto livello (es. "Antincendio", "Primo soccorso"). Usato come vincolo opzionale su trainingSessions e come fonte del colore mostrato nel calendario.

  • PK: id
  • FK: categoryId β†’ edu.categories(id), riskId β†’ job.risks(id) (opzionale)
  • Campi: code, label, description, requireUpdates, regulated (default 1), fullRetrainingYears, ats (default 0), color (NVARCHAR(16), nullable; hex es. #e53935, usato dal calendario per il colore degli appuntamenti)
  • Check: fullRetrainingYears > 0
  • Indice: IX_trainingTopics_riskId (filtered)

edu.categories​

  • PK: id
  • Campi: label

πŸ“š Tabelle fuori scope​

Worker training journey (5)​

workersTrainings, workerTrainingDetails, trainingDetailAttempts, attendances, workerCompletionsCache β€” registrazione iscrizioni, presenze, esiti, materializzazione compliance.

Argomenti formativi (8)​

trainingArguments, trainingTopics, trainingVariants, variantTopicMap, trainingTopicDependencies, trainingArgumentChecks, trainingVariantsOverlaps, appointmentChecks β€” macchina normativa degli argomenti formativi, varianti, sovrapposizioni.

Nomine (3)​

nominations, workersNominations, nominationsTrainingTopics β€” incarichi di ruolo (es. RSPP, ASPP, preposto) con vincoli formativi.

Certificati & qualifiche (5)​

certificates, enrollmentCosts, priorTrainings, qualificationsExemptions, qualificationsRequiredTrainings β€” attestati, costi iscrizione, formazione pregressa, esenzioni e requisiti.

Ruoli vs corsi (2)​

rolesRequiredTrainings, rolesForbiddenTrainings β€” matrice ruoli e corsi richiesti/vietati.

Attrezzature (3)​

equipments, workerEquipments, workerCredentials β€” attrezzature e credenziali necessarie.

πŸ“ File chiave​

  • TrainingHub.Database/edu/Tables/*.sql β€” ~38 tabelle
  • Indici espliciti: vedi sezione "Dettaglio tabelle"

⚠️ Debito tecnico​

  • appointments.courseSessionId nullable. Risolto in edu-sessions-redesign 2026-05-08: ora Γ¨ trainingSessionId NOT NULL.
  • Drop appointments.lessonId. Risolto dallo stesso redesign: il contenuto deriva da trainingSessionsCourses + trainingSessionsCoursesArguments.
  • teacherSkills.lessonId. Sostituito da trainingVariantId per allinearsi alla granularitΓ  varianti.
  • Nessun check su sovrapposizione appuntamenti. A livello DB i constraint restano informativi, ma a livello applicativo ISessionPlannerService.DetectConflictsAsync rileva: docente sovrapposto, sede occupata, iscritti oltre capienza, soglia docente, lavoratore in piΓΉ sessioni, lezione mancante. Esposto come toast warning dal calendario e dal wizard.
  • edu.categories senza FK visibili da courses. La correlazione categoria ↔ corso passa per trainingVariants: da documentare chiaramente nel subsystem varianti.
  • courses.maxPeople vs trainingSessions.maxPeople. Due limiti distinti β€” validazione di coerenza non applicata a DB, solo in service SessionPlanner (conflict enrollment_overflow).
  • teachers.label computed. Colonna calcolata da firstName + lastName: efficiente ma non indicizzabile. Se serve ricerca, aggiungere indice full-text o persisted computed column.
  • Cache workerCompletionsCache non atomica. Aggiornata da trigger/hook vari: coerenza in caso di failure non chiara.

πŸ”— Vedi anche​