Passa al contenuto principale

Schema DB job

🎯 Cosa fa​

Definisce il persistence layer del dominio lavoratori/rischi: 17 tabelle documentate tutte in questa pagina.

πŸ—ΊοΈ Tabelle (17)​

Anagrafica lavoratori (3)​

TabellaRuolo
job.workersAnagrafica dipendente
job.workerJobHistorySnapshot storico mansione/ruolo/reparto
job.workerDocumentsM:N lavoratore ↔ oss.documents (documenti collegati al lavoratore)

Mansioni (4)​

TabellaRuolo
job.jobsMansioni (globali)
job.jobGroupsRaggruppamenti visuali
job.jobSubcategoriesN:N mansione ↔ sottocategoria azienda
job.workersJobsN:N lavoratore ↔ mansione

Struttura (2)​

TabellaRuolo
job.rolesRuoli aziendali (lavoratore/preposto/dirigente)
job.departmentsReparti gerarchici

Rischi (7)​

TabellaRuolo
job.risksAnagrafica rischi
job.riskLevelLivelli (int PK)
job.atecoCodesCodici ATECO gerarchici (versione corrente)
job.atecoCodesLegacyCodici ATECO di versioni precedenti con correspondingNewAteco FK verso atecoCodes(code). Usata in import aziende per convertire codici legacy nel codice corrente.
job.companiesRisksRischi per azienda
job.jobsRisksRischi per mansione
job.workersRisksRischi per lavoratore (override/esclusione)
job.workerEffectiveRisksCacheCache materializzata di vw_workerEffectiveRisks, indice clustered su (workerId, riskId). Popolata da sp_refreshEffectiveRisksForScope; Γ¨ la sorgente letta da edu.vw_workerTrainingStatus, non la vista

πŸͺŸ Viste​

VistaRuolo
job.vw_workerEffectiveRisksL'autoritΓ  sul cascade rischi: unione dei tre rami (mansione / azienda / personale) con spareggio per origine worker > job > company e gli esclusi restituiti a parte con origin = 'excluded'. Vedi logica applicativa
job.vw_workersDataVista dati lavoratori, esposta come CRUD generato /job/vw_workersData
job.vw_jobsDataVista dati mansioni, esposta come CRUD generato /job/vw_jobsData
job.vw_workerComplianceSummaryRollup per-lavoratore di compliance formativa: worstStatus (expired > missing > expiring > insufficient > ok), conteggi per status β€” incluso insufficientCount β€” e minDaysRemaining. ⚠️ okCount conta solo status = 'ok' AND hoursShortfall = 0. Filtra solo lavoratori con endDateWork IS NULL. Aggrega edu.vw_workerTrainingStatus per workerId. PK virtuale = workerId. Esposta come CRUD read-only WorkerComplianceSummary con FK manuali su workerId/companyId/roleId/departmentId.

βš™οΈ Funzioni e stored procedure​

OggettoRuolo
job.fn_getWorkerRisks(@workerId)Avvolge vw_workerEffectiveRisks e aggiunge i rischi di catalogo non assegnati con origin = 'none'. È il data source della griglia rischi effettivi
job.fn_getWorkerJobsInPeriodMansioni attive del lavoratore in un intervallo
job.sp_refreshEffectiveRisksForScopeRipopola workerEffectiveRisksCache per uno scope (@workerId / @companyId)

πŸ”— Relazioni​

Referenze esterne:

  • 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)
  • reg.companies.atecoCode β†’ job.atecoCodes(code) (in uscita da reg)
  • reg.companies.riskLevelId β†’ job.riskLevel(id) (in uscita da reg)

πŸ—‚οΈ Dettaglio tabelle​

job.workers​

  • PK: id
  • FK: companyId, companyLocationId, roleId (obbligatori); departmentId, riskLevelId, academicQualificationId (nullable)
  • Computed column: label = lastName + ' ' + firstName
  • Check: gender IN ('M','F')
  • Indici:
    • IX_workers_fiscalCode filtered su fiscalCode IS NOT NULL con INCLUDE (id, companyId, endDateWork, roleId, riskLevelId, academicQualificationId)
    • IX_workers_active filtered su endDateWork IS NULL con INCLUDE simili β€” ottimizza liste attive

job.workerJobHistory​

  • PK: id
  • FK: workerId, jobId (obbligatori); riskLevelId, roleId, departmentId nullable
  • Campi: startDate, endDate (nullable), insertTimestamp default sysdatetime(), notes

job.workerDocuments​

Associazione M:N tra un lavoratore e i documenti caricati su oss.documents.

  • PK composita: (workerId, documentId)
  • FK: workerId β†’ job.workers(id), documentId β†’ oss.documents(id)
  • Nessun metadata aggiuntivo (la categoria/anagrafica del documento vive su oss.documents).

job.jobs​

  • PK: id
  • FK: riskLevelId (nullable = fallback azienda), jobGroupId (nullable)
  • Campo chiave: inheritsCompanyRisks (default 1)

job.workersJobs​

  • PK: id
  • FK: workerId, jobId
  • Unique: UQ_workersJobs_workerId_jobId su (workerId, jobId)
  • Indice: IX_workersJobs_jobId
  • Nessun altro campo (associazione pura; le date sono in workerJobHistory)

job.departments​

  • PK: id
  • FK: companyId (obbligatoria), parentId (self, nullable β€” gerarchia)
  • Campo: active (default 1)

job.risks​

  • PK: id
  • Campo: label

job.riskLevel​

  • PK: id (INT, non GUID)
  • Campo: label

job.atecoCodes​

  • PK: code (string β€” natural key)
  • FK: parentCode (self, nullable), riskLevelId
  • Campi: label, selectable, position

job.companiesRisks​

  • PK: PK_companiesRisks, composita clustered (companyId, riskId)
  • FK: companyId, riskId, riskLevelId (nullable)
  • Campi: notes, onlyForExposedJobs (BIT NOT NULL, default 0) β€” se 1 il rischio Γ¨ d'attivitΓ : resta condizione per le mansioni con jobsRisks.onlyIfCompanyHasRisk = 1 ma non si propaga dal ramo company alle mansioni con jobs.inheritsCompanyRisks = 1. A 0 Γ¨ un rischio diffuso e si comporta come sempre.

job.jobsRisks​

  • PK: composita (jobId, riskId)
  • FK: riskLevelId nullable
  • Campo: onlyIfCompanyHasRisk (BIT NOT NULL, default 0) β€” se 1, il rischio entra nei rischi efficaci del lavoratore solo quando job.companiesRisks contiene la coppia (azienda del lavoratore, stesso rischio). Applicato nel ramo job di job.vw_workerEffectiveRisks, quindi ortogonale a jobs.inheritsCompanyRisks, che governa il solo ramo company.

job.workersRisks​

  • PK: composita (workerId, riskId)
  • FK: riskLevelId nullable
  • Campi: excluded (default 0), notes

job.jobSubcategories​

  • PK: id
  • FK: jobId, companySubcategoryId
  • Associazione N:N senza metadata

job.jobGroups, job.roles​

Anagrafiche semplici: PK + label.

πŸ“ File chiave​

  • TrainingHub.Database/job/Tables/*.sql (17)
  • TrainingHub.Database/job/Views/*.sql (4)
  • TrainingHub.Database/job/Functions/{fn_getWorkerRisks,fn_getWorkerJobsInPeriod}.sql
  • TrainingHub.Database/job/Stored Procedures/sp_refreshEffectiveRisksForScope.sql
  • Indici significativi: IX_workers_fiscalCode, IX_workers_active (entrambi filtered per minimizzare dimensione)

⚠️ Debito tecnico​

  • workers.riskLevelId materializzato β€” nessun test di regressione. I test ci sono, in TrainingHub.UnitTests/Services/QueryModifiers/: WorkersQueryModifierTests.cs:60, JobsQueryModifierTests.cs:30, JobsRisksQueryModifierTests.cs:23-41, CompaniesQueryModifierTests.cs:24, piΓΉ WorkersJobsQueryModifierTests.cs e CompaniesRisksQueryModifierTests.cs: asseriscono lo scope del ricalcolo.
  • atecoCodes.code come natural key. Performance OK, ma modifiche al codice (es. rinomina ATECO) sono bloccate da FK. Se serve rename, procedere via nuovo codice + migrazione.
  • Mansioni globali senza companyId. Condivisione fra aziende crea accoppiamento: modifica a una mansione impatta tutti i clienti che la usano. Valutare pattern mansione template + istanza.
  • Indici mancanti su FK chiave. Ci sono tutti: IX_workerJobHistory_workerId (workerJobHistory.sql:23), UQ_workersJobs_workerId_jobId β€” che copre workerId come colonna guida β€” e IX_workersJobs_jobId (workersJobs.sql:9,12); workersRisks ha la PK composita con workerId in testa.
  • Constraint check su riskLevelId. riskLevel ha PK INT (1, 2, 3) ma qualsiasi intero Γ¨ accettato in FK. Valori fuori range (es. 5) resterebbero orfani se non c'Γ¨ il record. Solo integrity FK li blocca, ma non semantica.
  • workersRisks.excluded = 1 + riskLevelId != NULL. Stato semanticamente ambiguo: lavoratore escluso con livello impostato? Aggiungere check o validazione.

πŸ”— Vedi anche​