Logica applicativa β dominio edu
π― Cosa faβ
La logica applicativa del dominio edu si distribuisce in tre
punti:
- 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). - QueryModifiers in
BackOffice/Services/QueryModifiers/edu/β hook pre/post sui CRUD di entitΓ edu specifiche. - Code-behind
.razor.csdei 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.ConflictInfoconConflictKindenum: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.cschiamaDetectConflictsAsyncdopo 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(dominioreg) eJobsRisksQueryModifier. - 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 suoss.documents) o caricamento PDF giΓ firmato fuori dall'app. Concorrenza ottimistica viafingerprintExpected(StaleEngagementException).CancelEngagementAsyncβ revoca incarico / sessione annullata; la version corrente, se signed, passa asuperseded.
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β
| File | Scope | Cosa fa |
|---|---|---|
AppointmentsQueryModifier.cs | appointments | Hook su CRUD appuntamenti |
AppointmentsTeachersQueryModifier.cs | appointmentsTeachers | Hook M:N appuntamentoβdocente, propaga aggiornamenti su teacherEngagements |
TrainingSessionsQueryModifier.cs | trainingSessions | Hook sessione (validazione cross-corso, eventi notifica) |
TeachersQueryModifier.cs | teachers | Hook anagrafica docente |
WorkerTrainingDetailsQueryModifier.cs | workerTrainingDetails | Refresh di workerCompletionsCache dopo modifiche; invocazione TrainingCompletionService |
PriorTrainingsQueryModifier.cs | priorTrainings | Hook su formazione pregressa |
TrainingVariantsQueryModifier.cs | trainingVariants | Hook su varianti normative |
TrainingVariantsOverlapsQueryModifier.cs | trainingVariantsOverlaps | Hook 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/ICompanyTrainingMatrixServiceper 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 datrainingTopics.color, conflict detection post-save).Components/edu/SessionPlanner/SessionPlannerPopup*β wizard 6-step (Step1Session..Step6Review come partial classes).
Forms con cascadeβ
CourseForm.razor.csβ cascade sutrainingVariantIdβ 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 daitrainingSessionsCoursesArguments.
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/TiraPlocper notifiche e trigger Oss.Documentsper allegati (engagement PDF, document upload)
Cross-dominio interni:
edu.locationsβreg.companies(gestore aula opzionale)edu.courses,edu.teacherCostsβreg.organizersworkerCompletionsCacheβ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+ implementazioniShared/Services/Attendance/IAttendanceService.csShared/Services/TrainingCompletion/ITrainingCompletionService.csShared/Services/Engagement/{ITeacherEngagementService.cs, EngagementFingerprint.cs}Shared/Services/TeacherArea/ITeacherAreaService.csShared/Services/PriorTraining/IPriorTrainingService.csShared/Services/Pricing/PricingHelpers.csShared/Services/BusinessRules/{IBusinessRule.cs, BusinessRuleResult.cs, Rules/}BackOffice/Services/AppointmentsCalendarService.cs(impl)BackOffice/Services/QueryModifiers/edu/*.csβ 8 modifier
β οΈ Debito tecnicoβ
- Cache
workerCompletionsCachecoerenza. 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.SharedeTrainingHub.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 inreg/,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 aMatch.Create<string>(s => s.Contains(...))o smoke runtime test. (VediBACKLOG.md.) - Race condition
SuggestTeachersAsyncBulkInsert. Click concorrente su due tab puΓ² causare PK violation suappointmentsTeachers. Wrap in transaction o INSERT WHERE NOT EXISTS per riga. (VediBACKLOG.md.) -
ITrainingExpirationServicecon parametri string. Il parametrostatusè stringa invece di enumWorkerTrainingStatusValue: meno type-safe. - Business Rules Engine aggregator. Le regole
WorkerFinalRiskLevelRuleeTrainingExemptionRulesono inBusinessRules/Rules/ma l'aggregatorIBusinessRuleEnginee la sostituzione delle query SQL inline restano fuori scope. (VediBACKLOG.md.)
π Vedi ancheβ
- Panoramica dominio
- Schema DB β viste e tabelle materializzate
- Componenti UI β calendario e session planner
- Dominio
reg: logica applicativa β cascade reg β edu via RiskInheritance