Passa al contenuto principale

Tutorial — aggiungere un campo a job.workers

Esempio job-specifico del pattern di estensione CRUD. Per i passi comuni (sync DB, MCP generator, rigenerazione) vedi Aggiungere un campo su inv.invoices.

Questa pagina mostra le specificità del dominio job.

🎯 Cosa fa

Esempio: aggiungere un campo emergencyContactPhone (numero di un familiare o amico contattabile in emergenza) alla tabella job.workers.

⚠️ Prerequisiti

Stessi del tutorial generale: ambiente sviluppo + DB locale + MCP generator + branch dedicato.

👣 Passi

1. Modifica job.workers

File: TrainingHub.Database/job/Tables/workers.sql

Aggiungi il campo accanto a phone:

CREATE TABLE [job].[workers] (
-- ... campi esistenti ...
[phone] NVARCHAR (1024) NULL,
[emergencyContactPhone] NVARCHAR (128) NULL,
-- ... altri campi ...
);

GO
EXECUTE sp_addextendedproperty @name = N'MS_Description',
@value = N'Numero telefonico da contattare in caso di emergenza',
@level0type = N'SCHEMA', @level0name = N'job',
@level1type = N'TABLE', @level1name = N'workers',
@level2type = N'COLUMN', @level2name = N'emergencyContactPhone';
GO

Considerazioni job-specifiche:

  • Non toccare i campi chiave del cascade: companyId, companyLocationId, roleId, riskLevelId, departmentId, academicQualificationId. Modifiche a questi triggerano cascade QueryModifier.
  • Non interferire con la computed column label (derivata da firstName + lastName).
  • Indici: il nuovo campo non deve pesare sugli indici filtered esistenti (IX_workers_fiscalCode, IX_workers_active). Se il campo diventa frequentemente filtrato, valutare aggiunta indice dedicato.

2. Sincronizza il DB

Come altri tutorial.

3. conf.json — considerazioni job

File: TrainingHub.BackOffice/Components/CRUD/job/_conf/workers.dxgrid.conf.json

Per un campo contatto opzionale:

{
"Columns": {
"emergencyContactPhone": {
"Visible": false,
"FormInsertVisibility": "editable",
"FormUpdateVisibility": "editable"
}
},
"FormOrder": [
// ... altri campi ...
"phone",
"emergencyContactPhone",
// ...
]
}

Nascondi in grid di default (campi contatto spesso ingombranti) ma lascia editabile nel form.

4. Rigenera

Invoca mcp__3sd-generator__generator_run per workers. File rigenerati:

TrainingHub.BackOffice/Components/CRUD/job/Worker.razor
TrainingHub.BackOffice/Components/CRUD/job/Worker.razor.tt.cs
TrainingHub.BackOffice/Components/CRUD/job/Forms/WorkerForm.razor
TrainingHub.BackOffice/Components/CRUD/job/Forms/WorkerForm.razor.tt.cs

Non devono cambiare, perché sono gli escape hatch scritti a mano:

  • Worker.razor.cs — code-behind della pagina: inietta IRiskInheritanceService e ITrainingRequirementsService per i pannelli rischi e formazione. (Il cascade FK azienda → sede/reparto non è qui: sta in Forms/WorkerForm.razor.cs:14-31.)
  • Forms/WorkerForm.razor.cs — cascade FK e derivazione del sesso dal CF.

Attenzione, invece, a queste tre: sono CRUD generati su vista, con il proprio .razor.tt.cs e il proprio conf. Un generator_run le riscrive, quindi non vanno modificate a mano ma rigenerate:

ComponenteVista sorgenteConf
WorkerEffectiveRisk.razorjob.vw_workerEffectiveRisks_conf/vw_workerEffectiveRisks.dxgrid.conf.json
WorkersData.razorjob.vw_workersData_conf/vw_workersData.dxgrid.conf.json
WorkerJobHistory.razorjob.workerJobHistory_conf/workerJobHistory.dxgrid.conf.json

5. Aggiorna le viste analitiche (se rilevante)

WorkersData.razor è CRUD generato sulla vista job.vw_workersData: il campo non si aggiunge al componente, si aggiunge alla vista SQL e poi si rigenera (generator_run). Modificare il .razor a mano lo espone a essere clobberato al primo rigenerato.

Per emergencyContactPhone non è rilevante (dato individuale), ma lo sarebbe per campi aggregabili (es. anniDiServizio).

6. Considera impatto su cascade rischi

Per un campo puramente informativo come emergencyContactPhone:

  • Non triggera cascade.
  • Il CompaniesQueryModifier (reg) continuerà a chiamare worker.UpdateRiskLevel al cambio azienda anche se il nuovo campo non è coinvolto, ma non è un problema: l'UPDATE porta il predicato WHERE ISNULL(W.riskLevelId,-1) <> ISNULL(...) (Shared/DataLayer/job/workers.cs:28), quindi se nulla cambia non scrive righe. Costa una scansione, non una scrittura.

Per un campo che invece influenza il cascade (es. un nuovo riskFactorId):

  • Aggiornare la vista job.vw_workerEffectiveRisks (è lì che vive il cascade) e, se serve, worker.UpdateRiskLevel per la colonna materializzata. Non IRiskInheritanceService, che è di sola lettura.
  • Aggiungere QueryModifier dedicato se serve hook su cambi.
  • Ricalcolare workers.riskLevelId materializzato.

7. Considera impatto su workerJobHistory

Se il campo deve essere storicizzato (es. un dato che cambia nel tempo e serve audit):

  • Aggiungi il campo anche a workerJobHistory con lo stesso tipo.
  • Aggiorna la logica di snapshot che popola workerJobHistory al cambio mansione per copiare anche il nuovo campo.

Per emergencyContactPhone non serve.

8. Considera impatto su import Excel

TrainingHub.Import importa anche workers. Il nuovo campo:

  • Non viene popolato automaticamente — l'Excel deve avere la colonna e il mapping va aggiornato.
  • Campi ottimali nullable (come questo) non rompono l'import esistente.

9. Build, test, commit

dotnet build TrainingHub.slnx
dotnet test TrainingHub.UnitTests

git add TrainingHub.Database/job/Tables/workers.sql
git commit -m "feat(db): add emergencyContactPhone to job.workers"

git add TrainingHub.BackOffice/Components/CRUD/job/_conf/workers.dxgrid.conf.json
git commit -m "feat(job): show emergencyContactPhone in worker form"

git add TrainingHub.BackOffice/Components/CRUD/job/Worker.razor \
TrainingHub.BackOffice/Components/CRUD/job/Worker.razor.tt.cs \
TrainingHub.BackOffice/Components/CRUD/job/Forms/WorkerForm.razor \
TrainingHub.BackOffice/Components/CRUD/job/Forms/WorkerForm.razor.tt.cs
git commit -m "regen job.workers"

⚡ Casi particolari job

  • Nuovo campo cascade-sensitive. Se il campo ha impatto su riskLevelId del lavoratore (tipicamente improbabile — il cascade gira su rischi e ATECO, non su campi worker), aggiorna la vista vw_workerEffectiveRisks e worker.UpdateRiskLevel, poi valida il ricalcolo.
  • Nuovo campo su jobs (mansione). Se tocca riskLevelId o inheritsCompanyRisks, servirà un refresh massivo di workers.riskLevelId: costoso su dataset grandi.
  • Nuovo rischio. Aggiungere una voce a risks è banale (label + id). Impatta i flussi di assegnazione e può richiedere aggiornamento di eventuali viste pre-calcolate su compliance.
  • Nuovo codice ATECO. ATECO ha parentCode (gerarchia) + flag selectable: inserire come figlio di un codice esistente e impostare selectable coerentemente.
  • Nuovo livello di rischio. riskLevel.id è INT. Aggiungere un 4° livello (es. "Critico"): potrebbe richiedere aggiornamenti a tabelle con riskLevelId nullable (oggi interpretato come "eredita parent") e a UI che mostrano livelli.

⚠️ Domande aperte

  • Impatto su import Excel. Il mapping TrainingHub.Import va aggiornato manualmente. Checklist dei mapping da toccare per ogni tabella modificata.
  • Discriminazione cascade per campo. Problema ricorrente (già in reg): modificare un campo non cascade-sensitive (es. note) triggera comunque refresh globale di workers.riskLevelId. Ottimizzazione futura.
  • workerJobHistory sincronizzato automaticamente. Se aggiungi un campo storicizzabile a workers, la logica di snapshot va aggiornata in più punti (form custom, hook DB, trigger). Manca pattern centralizzato.

🔗 Vedi anche