Passa al contenuto principale

Logica applicativa β€” dominio edu

🎯 Cosa fa​

La logica applicativa del dominio edu si distribuisce in tre punti:

  1. Service condivisi in TrainingHub.Shared/Services/ β€” un roster di service per orchestrazione, lettura analitica, import, compliance, fatturazione. Vivono in Shared perchΓ© usati da piΓΉ applicazioni (BackOffice, Import).
  2. QueryModifiers in BackOffice/Services/QueryModifiers/edu/ β€” hook pre/post sui CRUD di entitΓ  edu specifiche.
  3. Code-behind .razor.cs dei CRUD e delle pagine custom β€” logica UI specifica.

πŸ”§ Servizi condivisi​

Orchestrazione sessioni​

ISessionPlannerService​

Cuore del wizard di pianificazione: crea sessione + corsi erogati + appuntamenti + iscritti + docenti + stima costi, con rilevamento conflitti.

Tipi principali (in Shared/Services/SessionPlanner/):

  • SessionPlannerState β€” stato persistito del wizard (id session, data, topic, corsi pianificati, appointment IDs, iscrizioni, docenti).
  • SessionCoursePlan β€” il "draft" di un corso erogato dentro la session (orari per-corso, iscritti previsti).
  • ComplianceEnrollmentRequest / ComplianceEnrollmentResult β€” input/output per l'iscrizione bulk da scadenziario / coda richieste.
  • ConflictInfo con ConflictKind enum: teacher_double_booked, location_double_booked, enrollment_overflow, teacher_threshold, worker_double_enrolled, lesson_skipped. SeveritΓ  Warning / Info.
  • CostBreakdown β€” stima costi (Iscrizioni + Docenze + Aule) per lo step Riepilogo.
  • SessionPlannerSeedKind β€” origine del seed iniziale (Course, CourseSession, QueueRequest, ExpiringTraining, WorkerProfile, Calendar).

Uso tipico:

  • SessionPlannerPopup (wizard UI) chiama draft + step methods.
  • AppointmentsCalendar.razor.cs chiama DetectConflictsAsync dopo il salvataggio di un appuntamento e mostra toast warning.

IAppointmentsCalendarService​

Data provider della vista calendario /appointments-calendar.

public interface IAppointmentsCalendarService
{
Task<IEnumerable<appointmentsCalendar>> GetAppointmentsAsync(
DateTime periodStart, DateTime periodEnd,
Guid? courseId = null, Guid? locationId = null,
Guid? teacherId = null, Guid? trainingSessionId = null);

Task<SessionContextInfo?> GetSessionContextAsync(Guid trainingSessionId);

Task<IEnumerable<appointmentAttendee>> GetAttendeesAsync(Guid appointmentId);
}

SessionContextInfo aggrega per il pannello dettaglio: label sessione, corsi erogati con topic+color, stato, conteggi iscritti vs appuntamenti completati.

Compliance e requisiti formativi​

ITrainingExpirationService​

Calcola la compliance formativa (read prevalentemente da workerCompletionsCache):

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);

ITrainingRequirementsService​

Espone gli status formativi (workerTrainingStatus) per singolo lavoratore o per intera azienda. Cita la vista edu.vw_workerTrainingStatus.

ICompanyTrainingMatrixService​

Costruisce la matrice formativa azienda: righe = lavoratori (con role + jobs), colonne = topic richiesti dall'azienda, celle = status + giorni residui + flag aggiornamento. Restituisce TrainingMatrixData con percentuale compliance aggregata.

IRiskInheritanceService​

Propagazione livello di rischio tra entitΓ :

  • Cambio ATECO su azienda β†’ aggiorna rischio della mansione β†’ aggiorna rischio del lavoratore.
  • Invocato da CompaniesQueryModifier (dominio reg) e JobsRisksQueryModifier.
  • Logica di aggregazione vive nelle estensioni statiche del model worker (worker.UpdateRiskLevel).

Erogazione e iscrizione​

IEnrollmentService​

Iscrive un lavoratore a un corso erogato dentro una sessione, gestendo i casi di duplicato/coda d'attesa/capienza:

Task<EnrollmentOutcome> EnrollWorkerAsync(
Guid workerId, Guid trainingSessionId, Guid courseId,
bool acceptWaitlist = false, string? notes = null,
CancellationToken cancellationToken = default);

public enum EnrollmentResult
{ Enrolled, Waitlisted, Duplicate, Full, CourseNotInSession }

Usato dallo step "Iscritti" del session planner e dalla gestione coda richieste.

IAttendanceService​

Gestione presenze per appuntamento: inizializza, salva draft, upload documento firmato, chiusura digitale. Espone AttendanceViewModel per la UI.

ITrainingCompletionService​

EvaluateForAppointmentAsync(appointmentId): alla chiusura di un appuntamento valuta i workerTrainingDetails correlati e, se tutti gli appuntamenti del trainingSession sono chiusi, calcola percentuale presenza cumulativa per worker e imposta lo status (Completed/Failed) + completeDate. Idempotente.

Docenti e firma​

ITeacherEngagementService​

Engagement (incarico docente) per coppia (trainingSession, teacher):

  • CreateOrRecalcAsync β€” crea o ricalcola fingerprint dell'engagement; idempotente se il fingerprint non cambia.
  • SignClickThroughAsync / SignUploadAsync β€” firma click-through (genera PDF "timbrato" e lo salva su oss.documents) o caricamento PDF giΓ  firmato fuori dall'app. Concorrenza ottimistica via fingerprintExpected (StaleEngagementException).
  • CancelEngagementAsync β€” revoca incarico / sessione annullata; la version corrente, se signed, passa a superseded.

ITeacherAreaService​

Area docente self-service: visibilitΓ  sessioni assegnate, download lettere, conferma engagement.

Import e dati pregressi​

IPriorTrainingService​

Import Excel di crediti formativi pregressi: parse + validate + bulk insert in priorTrainings. Espone LoadSystemDataAsync per caching reference data.

ITrainingImportService​

Import generale di registri formativi (storico). Vedi TrainingImportService.{cs,Models.cs,Parser.cs,Processing.cs} per la struttura a sub-file.

Notifiche e alert​

IAlertService​

Generazione alert applicativi (banner / counter UI). Lavora di concerto con le librerie 3SD Mola/Ploc per la persistenza dei trigger.

🧩 QueryModifiers edu​

FileScopeCosa fa
AppointmentsQueryModifier.csappointmentsHook su CRUD appuntamenti
AppointmentsTeachersQueryModifier.csappointmentsTeachersHook M:N appuntamento↔docente, propaga aggiornamenti su teacherEngagements
TrainingSessionsQueryModifier.cstrainingSessionsHook sessione (validazione cross-corso, eventi notifica)
TeachersQueryModifier.csteachersHook anagrafica docente
WorkerTrainingDetailsQueryModifier.csworkerTrainingDetailsRefresh di workerCompletionsCache dopo modifiche; invocazione TrainingCompletionService
PriorTrainingsQueryModifier.cspriorTrainingsHook su formazione pregressa
TrainingVariantsQueryModifier.cstrainingVariantsHook su varianti normative
TrainingVariantsOverlapsQueryModifier.cstrainingVariantsOverlapsHook su sovrapposizioni varianti

Registrati in Program.cs come IQueryModifier<T> con Brighela.SimpleCRUD.

🧩 Cache workerCompletionsCache​

Tabella materializzata che aggrega i completamenti formativi per lavoratore con stato (ok, expiring, expired, missing).

  • Aggiornata da QueryModifiers dopo modifiche a workerTrainingDetails, priorTrainings, trainingVariants, e analoghi.
  • Letta da ITrainingExpirationService / ITrainingRequirementsService / ICompanyTrainingMatrixService per performance: evita join pesanti a runtime.

🧩 Code-behind pattern​

Pagine page-level scritte a mano​

  • Pages/AppointmentsCalendar/* β€” vista calendario page-level (filtri, viste mese/settimana/giorno, colori da trainingTopics.color, conflict detection post-save).
  • Components/edu/SessionPlanner/SessionPlannerPopup* β€” wizard 6-step (Step1Session..Step6Review come partial classes).

Forms con cascade​

  • CourseForm.razor.cs β€” cascade su trainingVariantId β†’ pre-popola campi del corso.
  • LocationForm.razor.cs β€” combobox headquarters (reg) per associazione aula β†’ sede.
  • AppointmentForm.razor.cs β€” solo dati appuntamento (data, aula, link). Il "contenuto" viene dai trainingSessionsCoursesArguments.

Viste analitiche cross-dominio​

ActiveWorker.razor.cs, AllCompletion.razor.cs, AllRequiredTraining.razor.cs β€” code-behind che caricano dati aggregati dai servizi ITrainingExpirationService e IRiskInheritanceService.

πŸ“¦ Dipendenze​

Runtime:

  • ISimpleCRUDService β€” CRUD base
  • Service Shared elencati sopra
  • Librerie 3SD Mola / Ploc / TiraPloc per notifiche e trigger
  • Oss.Documents per allegati (engagement PDF, document upload)

Cross-dominio interni:

  • edu.locations ↔ reg.companies (gestore aula opzionale)
  • edu.courses, edu.teacherCosts ↔ reg.organizers
  • workerCompletionsCache ↔ job.worker (dominio lavoratori)
  • edu.trainingSessions.responsibleGroupId ↔ core.recipientGroups

πŸ“ File chiave​

  • Shared/Services/SessionPlanner/ β€” 9 file (ISessionPlannerService.cs, SessionPlannerService.cs, state/plan/result/conflict/cost types, SessionPlannerSeedKind.cs)
  • Shared/Services/{IAppointmentsCalendarService, ITrainingExpirationService, IRiskInheritanceService, ITrainingRequirementsService, IEnrollmentService, ICompanyTrainingMatrixService}.cs + implementazioni
  • Shared/Services/Attendance/IAttendanceService.cs
  • Shared/Services/TrainingCompletion/ITrainingCompletionService.cs
  • Shared/Services/Engagement/{ITeacherEngagementService.cs, EngagementFingerprint.cs}
  • Shared/Services/TeacherArea/ITeacherAreaService.cs
  • Shared/Services/PriorTraining/IPriorTrainingService.cs
  • Shared/Services/Pricing/PricingHelpers.cs
  • Shared/Services/BusinessRules/{IBusinessRule.cs, BusinessRuleResult.cs, Rules/}
  • BackOffice/Services/AppointmentsCalendarService.cs (impl)
  • BackOffice/Services/QueryModifiers/edu/*.cs β€” 8 modifier

⚠️ Debito tecnico​

  • Cache workerCompletionsCache coerenza. Aggiornato da piΓΉ hook in path diversi; in caso di fallimento parziale il cache puΓ² divergere dalla realtΓ . Valutare rebuild periodico o flag "stale" per detect.
  • Service in Shared ma logica edu-centrica. Il bound fra TrainingHub.Shared e TrainingHub.DataLayer.edu Γ¨ stretto: Shared dipende dal DataLayer di dominio. Accettabile per dimensione attuale ma genera coupling.
  • QueryModifiers sparsi senza visione d'insieme. 8 modifier in edu/ + alcuni in reg/, job/, inv/. Manca documentazione centrale "chi aggiorna cosa quando".
  • Test contract drift sui mock SQL. Tutti i test usano It.IsAny<string>() per i parametri SQL: un typo su nome colonna o parametro non viene catturato. Migrare a Match.Create<string>(s => s.Contains(...)) o smoke runtime test. (Vedi BACKLOG.md.)
  • Race condition SuggestTeachersAsync BulkInsert. Click concorrente su due tab puΓ² causare PK violation su appointmentsTeachers. Wrap in transaction o INSERT WHERE NOT EXISTS per riga. (Vedi BACKLOG.md.)
  • ITrainingExpirationService con parametri string. Il parametro status Γ¨ stringa invece di enum WorkerTrainingStatusValue: meno type-safe.
  • Business Rules Engine aggregator. Le regole WorkerFinalRiskLevelRule e TrainingExemptionRule sono in BusinessRules/Rules/ ma l'aggregator IBusinessRuleEngine e la sostituzione delle query SQL inline restano fuori scope. (Vedi BACKLOG.md.)

πŸ”— Vedi anche​