Passa al contenuto principale

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.

ProgettoRuolo
TrainingHub.BackOfficeBlazor Server web app (UI principale)
TrainingHub.Databaseprogetto SQL Server SDK-style (Microsoft.Build.Sql), deploy DACPAC
TrainingHub.Importconsole app per import massivi da Excel, avviata da CLI
TrainingHub.Sharedshared 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.UnitTeststest unitari (xUnit v3 + NSubstitute + Shouldly)
TrainingHub.IntegrationTeststest 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:

RottaComponentePolicy
/appointments-calendarPages/AppointmentsCalendar/AppointmentsCalendar.razorpage-appointments-calendar_R
/worker/{workerId:guid}/profilePages/WorkerProfile/WorkerProfile.razorpage-job-workers_R
/dashboard/compliancePages/ComplianceDashboard/CompanyProspect.razorpage-dashboard-compliance_R
/company-training-overviewPages/ComplianceDashboard/CompaniesTrainingOverview.razorpage-dashboard-compliance_R
/fin/project-detail/{id}Pages/ProjectDetail/ProjectDetail.razorpage-fin-projects_R
/fin/project-phases/{id}Pages/ProjectPhases/ProjectPhases.razorpage-fin-projects_R
/fin/hours-calendarPages/HoursCalendar/HoursCalendar.razorpage-fin-calendarEntries_R
/fin/expenses-matrixPages/ExpensesWorksheet/ExpensesWorksheet.razorpage-fin-expenses_R
/import/training-coursesPages/Import/TrainingCoursesImport.razorpage-import-training-courses_R
/import/employee-verificationPages/Import/EmployeeVerificationImport.razorpage-import-training-courses_R
/tools/company-data-cleanupPages/Import/CompanyDataCleanup.razor
/teacher-area/… (6 rotte)Pages/TeacherArea/*.razorpage-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:

  1. 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).
  2. Allineare il database (deploy DACPAC).
  3. MCP 3sd-generator (generator_run con defaultsCompiler) produce il model.conf.json e il dxgrid.conf.json partendo dalla view.
  4. Configurare a mano nel dxgrid.conf.json:
    • FK manuali sulle colonne *Id (la view non ha vincoli FK): FkSchema, FkTableName, FkClassName, eventuale CellLink + CellPolicy.
    • AllowInsert, AllowUpdate, AllowDelete, AllowClone = false.
    • IgnoreFormPopup = true + IgnoreForm = true (evita di generare popup/form per un'entità non scrivibile).
  5. Rigenerare: si ottiene un <Entity>.razor standard 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:

HookPosizione resaQuando usarlo
FormPopup.MarkupBeforeForm / MarkupAfterFormPrima/dopo <EditForm> nel popupWizard, banner contestuali
FormPopup.MarkupEndDopo </DxPopup>Popup secondari (anteprima, conferme)
Columns.{name}.RenderFragmentParameters.CellDisplayTemplateCella della gridBadge stato, link condizionali
Form.TabPages.Tabs.{tab}.Groups.{group}.Columns.{col}.TemplateSostituisce l'editor del campo nel formWrap 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.