Architettura
Cosa fa
TrainingHub è una piattaforma .NET 10 / Blazor Server per la gestione della formazione sulla sicurezza sul lavoro. È single-tenant per istanza: si deploya un'istanza per cliente (un database/app pair per cliente), nessuna tabella applicativa ha colonna tenant e view/service non filtrano per tenant.
Componenti principali
La soluzione (TrainingHub.slnx) dichiara sei progetti.
| Progetto | Ruolo |
|---|---|
TrainingHub.BackOffice | Blazor Server web app (UI principale) |
TrainingHub.Database | progetto SQL Server SDK-style (Microsoft.Build.Sql), deploy DACPAC |
TrainingHub.Import | console app per import massivi da Excel, avviata da CLI |
TrainingHub.Shared | shared project: model del DataLayer, costanti, validazioni e circa venticinque aree di servizi di dominio (Services/FundedTraining/, SessionPlanner/, ExpensesWorksheet/, Calendar/, Pricing/, Import/, TrainingExpirationService.cs…). Non è solo "models e costanti": ci vive logica consumata sia dal BackOffice sia dall'Import — per esempio IFundedEligibilityService, iniettato da due QueryModifier |
TrainingHub.UnitTests | test unitari (xUnit v3 + NSubstitute + Shouldly) |
TrainingHub.IntegrationTests | test che richiedono SQL Server in Docker: rule set delle scadenze formative, sync ruoli/claim |
Pagine scritte a mano (non CRUD generate)
Oltre alle griglie generate, il BackOffice ha un insieme di pagine page-level costruite a mano. Questo è l'inventario:
| Rotta | Componente | Policy |
|---|---|---|
/appointments-calendar | Pages/AppointmentsCalendar/AppointmentsCalendar.razor | page-appointments-calendar_R |
/worker/{workerId:guid}/profile | Pages/WorkerProfile/WorkerProfile.razor | page-job-workers_R |
/dashboard/compliance | Pages/ComplianceDashboard/CompanyProspect.razor | page-dashboard-compliance_R |
/company-training-overview | Pages/ComplianceDashboard/CompaniesTrainingOverview.razor | page-dashboard-compliance_R |
/fin/project-detail/{id} | Pages/ProjectDetail/ProjectDetail.razor | page-fin-projects_R |
/fin/project-phases/{id} | Pages/ProjectPhases/ProjectPhases.razor | page-fin-projects_R |
/fin/hours-calendar | Pages/HoursCalendar/HoursCalendar.razor | page-fin-calendarEntries_R |
/fin/expenses-matrix | Pages/ExpensesWorksheet/ExpensesWorksheet.razor | page-fin-expenses_R |
/import/training-courses | Pages/Import/TrainingCoursesImport.razor | page-import-training-courses_R |
/import/employee-verification | Pages/Import/EmployeeVerificationImport.razor | page-import-training-courses_R |
/tools/company-data-cleanup | Pages/Import/CompanyDataCleanup.razor | — |
/teacher-area/… (6 rotte) | Pages/TeacherArea/*.razor | page-teacher-* |
Framework 3SD
L'app usa l'ecosistema interno 3SD: Scarnas, Brighela, Oss, Servel, Ploc, Oster, Mulet, DeFa, KSet, Mola, Tabiot.
Pattern ricorrenti
View SQL → CRUD auto-generato (read-only)
Per esporre dati aggregati o derivati senza scrivere componenti UI custom si usa il pattern vista SQL + CRUD generato:
- Creare una vista in
TrainingHub.Database/<schema>/Views/vw_<nome>.sql, esponendo le colonne con i loro identificatori*Id(no*Label: il generator costruisce le FK). - Allineare il database (deploy DACPAC).
- MCP
3sd-generator(generator_runcondefaultsCompiler) produce ilmodel.conf.jsone ildxgrid.conf.jsonpartendo dalla view. - Configurare a mano nel
dxgrid.conf.json:- FK manuali sulle colonne
*Id(la view non ha vincoli FK):FkSchema,FkTableName,FkClassName, eventualeCellLink+CellPolicy. AllowInsert,AllowUpdate,AllowDelete,AllowClone=false.IgnoreFormPopup = true+IgnoreForm = true(evita di generare popup/form per un'entità non scrivibile).
- FK manuali sulle colonne
- Rigenerare: si ottiene un
<Entity>.razorstandard usabile come grid stand-alone o come step nei wizard.
Esempi attuali:
edu.vw_workerTrainingStatus— stato per-formazione di ogni lavoratore.job.vw_workerComplianceSummary— rollup per-lavoratore (stato peggiore + conteggi). Usata come step "Conformità formativa" nel wizard azienda.
Persistenza markup custom in conf.json
Il generator rigenera .razor e .razor.tt.cs ad ogni run: ciò che
viene scritto a mano nel .razor viene perso. I dxgrid.conf.json
offrono diversi hook per persistere markup custom:
| Hook | Posizione resa | Quando usarlo |
|---|---|---|
FormPopup.MarkupBeforeForm / MarkupAfterForm | Prima/dopo <EditForm> nel popup | Wizard, banner contestuali |
FormPopup.MarkupEnd | Dopo </DxPopup> | Popup secondari (anteprima, conferme) |
Columns.{name}.RenderFragmentParameters.CellDisplayTemplate | Cella della grid | Badge stato, link condizionali |
Form.TabPages.Tabs.{tab}.Groups.{group}.Columns.{col}.Template | Sostituisce l'editor del campo nel form | Wrap con badge, override editor |
Regola pratica: prima di scrivere markup custom direttamente nel
.razor, verificare se esiste un hook in conf.json; se manca, va
aggiunto al generator piuttosto che accettare la perdita su regen.
⚠️ Domande aperte / debito tecnico
Nessuna voce aperta al momento.