Passa al contenuto principale

Dominio job β€” Panoramica sviluppatore

🎯 Cosa fa​

Il dominio job (workers & risks) gestisce anagrafica lavoratori, mansioni, ruoli aziendali, reparti e rischi. È il ponte logico tra reg (aziende) ed edu (formazione): da reg eredita il contesto azienda/sedi, verso edu determina quali corsi sono obbligatori tramite il cascade rischi.

Dominio di dimensione media: 17 tabelle, tutte in scope.

πŸ—ΊοΈ Mappa moduli​

Database β€” TrainingHub.Database/job/​

AreaTabellaRuolo
Lavoratorijob.workersAnagrafica dipendenti
Lavoratorijob.workerJobHistoryStorico mansioni/ruoli/reparti
Mansionijob.jobsMansioni (con riskLevel e inheritsCompanyRisks)
Mansionijob.jobGroupsRaggruppamenti visuali di mansioni
Mansionijob.jobSubcategoriesN:N mansione ↔ sottocategoria azienda
Mansionijob.workersJobsN:N lavoratore ↔ mansione
Ruolijob.rolesRuoli aziendali (lavoratore, preposto, dirigente)
Strutturajob.departmentsReparti gerarchici (parentId)
Rischijob.risksAnagrafica rischi
Rischijob.riskLevelLivelli (1=basso, 2=medio, 3=alto)
Rischijob.atecoCodesCodici ATECO gerarchici (parentCode) con riskLevelId suggerito
Rischijob.companiesRisksRischi aziendali
Rischijob.jobsRisksRischi per mansione
Rischijob.workersRisksOverride/esclusione rischio per lavoratore
Rischijob.workerEffectiveRisksCacheCache di vw_workerEffectiveRisks, popolata da sp_refreshEffectiveRisksForScope
Rischijob.atecoCodesLegacyMapping codici ATECO legacy β†’ 2025
Documentijob.workerDocumentsBridge lavoratore ↔ oss.documents
Compliancejob.vw_workerComplianceSummaryVista β€” rollup per-lavoratore: worstStatus (expired > missing > expiring > insufficient > ok) + conteggi per stato, incluso insufficientCount, + minDaysRemaining. Attenzione: okCount conta solo status = 'ok' AND hoursShortfall = 0. Filtrabile per companyId. Aggrega edu.vw_workerTrainingStatus

Service layer β€” TrainingHub.Shared/Services/​

FileRuolo
IRiskInheritanceService.cs / RiskInheritanceService.csSola lettura dei rischi effettivi (job.vw_workerEffectiveRisks), per lavoratore o per azienda
DataLayer/job/workers.csUpdateRiskLevel β€” materializza workers.riskLevelId; SyncSiblingsByFiscalCode β€” allinea l'anagrafica fra worker con lo stesso CF
ITrainingExpirationService.cs / TrainingExpirationService.csUsa i rischi del lavoratore per calcolare compliance (condiviso con edu)

QueryModifiers β€” TrainingHub.BackOffice/Services/QueryModifiers/job/​

Sono sette:

FileScope
CompaniesRisksQueryModifier.csHook su companiesRisks β€” UpdateRiskLevel(companyId) + refresh cache variante e rischi
WorkersQueryModifier.csHook su workers β€” UpdateRiskLevel, sync anagrafico per CF, tre stored procedure di refresh; e in PreExecutionQuery la guardia di cancellazione
WorkersJobsQueryModifier.csHook su workersJobs β€” UpdateRiskLevel + apertura/chiusura del periodo in workerJobHistory
JobsQueryModifier.csHook su jobs
JobsRisksQueryModifier.csHook su jobsRisks
WorkersRisksQueryModifier.csHook su workersRisks β€” solo refresh cache, non tocca riskLevelId
AtecoCodesQueryModifier.csHook su atecoCodes β€” propaga il livello suggerito alle aziende (reg.company.UpdateRiskLevel)

UI CRUD β€” TrainingHub.BackOffice/Components/CRUD/job/​

EntitΓ  CRUD standard:

  • Worker, WorkerJobHistory, WorkersData (vista aggregata)
  • Job, JobGroup, JobSubcategory, JobsData
  • WorkersJob (associazione)
  • Role, Department
  • Risk, RiskLevel, AtecoCode
  • CompaniesRisk, JobsRisk, WorkersRisk
  • WorkerEffectiveRisk β€” vista calcolata dei rischi effettivi del lavoratore (cascade applicato)
  • WorkerComplianceSummary β€” CRUD auto-generato sulla view vw_workerComplianceSummary (read-only). FK manuali su worker/company/role/department, badge stato in colonna worstStatus. Embed nel wizard CompanyFormPopup come step "ConformitΓ  formativa". Pattern descritto in Architettura β€” view β†’ CRUD

πŸ”§ API pubblica​

IRiskInheritanceService​

Legge i rischi effettivi, non li propaga. Due sole query su job.vw_workerEffectiveRisks (IRiskInheritanceService.cs:5-9): GetEffectiveRisksAsync(workerId) e GetEffectiveRisksByCompanyAsync(companyId). Consumato da Worker.razor.cs, Company.razor.cs e Program.cs.

worker.UpdateRiskLevel β€” la propagazione vera​

TrainingHub.Shared/DataLayer/job/workers.cs:7:

public static async Task<int> UpdateRiskLevel(
ISimpleCRUDService simpleCRUD,
Guid? jobId = null, Guid? companyId = null, Guid? workerId = null);

Un UPDATE che scrive workers.riskLevelId come massimo fra le mansioni, con fallback sul livello azienda. Invocato da reg/CompaniesQueryModifier.cs:78,82, job/CompaniesRisksQueryModifier.cs:28, job/WorkersQueryModifier.cs:72,74, job/WorkersJobsQueryModifier.cs:62, job/JobsQueryModifier.cs:34,40, job/JobsRisksQueryModifier.cs:28 e Shared/Services/TrainingImportService.Processing.cs:884.

La propagazione ATECO β†’ azienda Γ¨ un passo a monte e distinto: job/AtecoCodesQueryModifier.cs:29, che chiama reg.company.UpdateRiskLevel.

Dettaglio in logica applicativa.

ITrainingExpirationService​

Calcola compliance formativa combinando rischi del lavoratore (dominio job) con formazione ricevuta (dominio edu). Dettaglio in Logica applicativa edu.

🧩 Pattern chiave​

Cascade rischi​

Codice ATECO
└── riskLevelId suggerito
└── azienda.riskLevelId (default)
β”œβ”€β”€ jobs.riskLevelId (opzionale β€” override per mansione)
β”‚ β”œβ”€β”€ jobs.inheritsCompanyRisks
β”‚ β”‚ └── aggiunge i rischi companiesRisks ai rischi jobsRisks
β”‚ β”‚ └── tranne quelli con companiesRisks.onlyForExposedJobs = 1 (rischio d'attivitΓ : vale solo per le mansioni che lo dichiarano)
β”‚ └── jobsRisks.onlyIfCompanyHasRisk (indipendente da inheritsCompanyRisks β€” il rischio mansione entra solo se l'azienda ha lo stesso rischio)
└── workers.riskLevelId (staticizzato per lavoratore)
└── workersRisks (override singolo rischio + esclusioni)

Propagazione automatica:

  • Cambio ATECO azienda β†’ CompaniesQueryModifier β†’ UpdateRiskLevel
  • Cambio riskLevel azienda β†’ idem
  • Cambio companiesRisks β†’ CompaniesRisksQueryModifier β†’ ricalcolo
  • Cambio mansione lavoratore β†’ staticizzazione workers.riskLevelId

Storico mansioni​

workerJobHistory Γ¨ uno snapshot storico della sola mansione. WorkersJobsQueryModifier apre il periodo sull'Insert di workersJobs (righe 67-77) e lo chiude sul DeleteSingle (29-51), registrando i valori correnti di riskLevelId, roleId, departmentId.

⚠️ I cambi di ruolo e reparto passano da job.workers e WorkersQueryModifier (:64-93) non tocca workerJobHistory: non lasciano traccia nello storico. Entrambe le operazioni sono best-effort β€” un errore nell'audit non blocca l'operazione CRUD.

Vista WorkerEffectiveRisk​

Componente UI non-CRUD che mostra i rischi finali del lavoratore applicando il cascade (rischi azienda + mansione + override lavoratore, meno esclusioni). Utile per DVR e per determinare formazione richiesta.

Gerarchia departments​

Tabella auto-referenziante: departments.parentId crea un albero reparti (es. "Produzione" β†’ "Linea A" β†’ "Turno notte"). UI deve supportare visualizzazione ad albero.

Codici ATECO gerarchici​

atecoCodes.parentCode simile ai reparti: ATECO hanno gerarchia (es. "F - Costruzioni" β†’ "F.41 - Costruzione di edifici" β†’ "F.41.1 - Sviluppo di progetti immobiliari"). Flag selectable indica quali codici possono essere assegnati direttamente alle aziende (foglie o nodi selezionabili).

πŸ“¦ Dipendenze​

  • Brighela.SimpleCRUD β€” CRUD base + QueryModifier
  • Stack UI comune (DevExpress, Tabiot, Oss)

Cross-dominio in entrata (FK da altri domini a job):

  • reg.companies.riskLevelId β†’ job.riskLevel(id)
  • reg.companies.atecoCode β†’ job.atecoCodes(code)
  • edu.courses.trainingVariantId non tocca job, ma varianti formative (fuori scope edu) mappano verso rischi

Cross-dominio in uscita (FK da job):

  • job.workers.companyId β†’ reg.companies(id)
  • job.workers.companyLocationId β†’ reg.companyLocations(id)
  • job.workers.academicQualificationId β†’ reg.academicQualifications(id)
  • job.departments.companyId β†’ reg.companies(id)
  • job.jobSubcategories.companySubcategoryId β†’ reg.companySubcategories(id)

πŸ“ File chiave​

  • Database/job/Tables/*.sql (17)
  • Database/job/Views/vw_workerEffectiveRisks.sql β€” l'autoritΓ  sul cascade
  • Database/job/Functions/fn_getWorkerRisks.sql
  • BackOffice/Services/QueryModifiers/job/*.cs (7)
  • BackOffice/Components/CRUD/job/Worker.razor{,.cs,.tt.cs} + Forms/WorkerForm.razor
  • BackOffice/Components/CRUD/job/WorkerEffectiveRisk.razor β€” vista rischi effettivi calcolati
  • Shared/Services/IRiskInheritanceService.cs + impl

⚠️ Domande aperte / debito tecnico​

  • Regola calcolo rischio finale del lavoratore. È il massimo fra le mansioni, con fallback sul livello dell'azienda: ISNULL(MAX(J.riskLevelId), C.riskLevelId) (Shared/DataLayer/job/workers.cs:19-27). Gli override personali (workersRisks) valgono per i rischi effettivi, non per questa colonna.
  • Storico mansioni β€” trigger automatico. Automatico: OpenPeriodAsync sull'Insert di workersJobs, ClosePeriodAsync sul DeleteSingle (WorkersJobsQueryModifier.cs:44,71), entrambe idempotenti e best-effort. Copre solo la mansione, non ruolo e reparto.
  • workers.riskLevelId staticizzato β€” quando viene refreshato? Da otto punti: WorkersQueryModifier.cs:72,74, WorkersJobsQueryModifier.cs:62, JobsQueryModifier.cs:34,40, JobsRisksQueryModifier.cs:28, CompaniesRisksQueryModifier.cs:28, reg/CompaniesQueryModifier.cs:78,82, TrainingImportService.Processing.cs:884. Dettaglio in logica applicativa.
  • Refresh massivo senza companyId. Non Γ¨ un problema: l'UPDATE filtra su WHERE ISNULL(W.riskLevelId,-1) <> ISNULL(...) (workers.cs:28), quindi tocca solo le righe fuori sync. È anzi il meccanismo di rebuild.
  • Nessuna validazione del checksum del codice fiscale. WorkerForm.razor.cs:10 controlla solo Length >= 16. Vedi il debito in logica applicativa.
  • Mansioni globali vs per-azienda. Oggi jobs Γ¨ tabella globale: non ha companyId. Un'azienda "Acme" e "Beta" che hanno un "Saldatore" condividono lo stesso record. Se una vuole personalizzare rischi solo per la propria "Saldatore" deve creare una mansione custom. Pattern da documentare.
  • atecoCodes.selectable β€” semantica. Chi puΓ² deselezionare? Sono ATECO pre-popolati dal sistema o l'utente puΓ² aggiungerne?

πŸ”— Vedi anche​