Passa al contenuto principale

Dominio reg β€” Panoramica sviluppatore

🎯 Cosa fa​

Il dominio reg (registry) contiene l'anagrafica aziende clienti e fornitori: Γ¨ base dati referenziata da quasi tutti gli altri domini (inv, edu, job, iso, bi). Questa pagina copre tutte le 17 tabelle del dominio raggruppate per area funzionale.

πŸ—ΊοΈ Mappa moduli​

Database β€” TrainingHub.Database/reg/​

Core anagrafica (4):

FileRuolo
Tables/companies.sqlAnagrafica aziende (cliente / fornitore)
Tables/companyContacts.sqlCanali di contatto azienda (email, telefono, PEC)
Tables/companyLocations.sqlSedi del cliente con label descrittiva e locationTag di classificazione
Tables/headquarters.sqlSedi TrainingHub / ente formatore β€” indipendente dalle aziende clienti

Classificazione aziendale (3):

FileRuolo
Tables/companySubcategories.sqlSottocategorie custom per raggruppare aziende
Tables/companyTags.sqlN:N azienda ↔ tag di classificazione
Tables/companiesPreferences.sqlPreferenze configurabili per azienda (flag booleani via tag)

Ruoli esterni (4):

FileRuolo
Tables/externalRspps.sqlAnagrafica RSPP esterni (consulenti compliance)
Tables/companiesExternalRspps.sqlIncarichi azienda ↔ RSPP con periodo validitΓ 
Tables/territorialReferents.sqlAnagrafica RLS territoriali β€” Rappresentanti dei Lavoratori per la Sicurezza designati a livello territoriale (art. 48 D.Lgs. 81/08)
Tables/companiesTerritorialReferents.sqlIncarichi azienda ↔ RLS territoriale con periodo validitΓ 

Anagrafiche lookup (4):

FileRuolo
Tables/organizers.sqlEnti organizzatori di corsi
Tables/ccnl.sqlContratti Collettivi Nazionali di Lavoro
Tables/legalNatures.sqlForme societarie (SRL, SPA, ditta individuale, ecc.)
Tables/academicQualifications.sqlTitoli di studio

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

FileRuolo
QueryModifiers/reg/CompaniesQueryModifier.csHook su CRUD companies. Su Insert: crea la sede predefinita dall'indirizzo (EnsureDefaultForCompanyAsync, :68-69). Su update: worker.UpdateRiskLevel + EXEC edu.sp_refreshEffectiveVariantForScope (:79), e imported = false. Su DeleteSingle: veto e cascata via EntityDeletionGuard (:95-107).
QueryModifiers/reg/CompanyTagsQueryModifier.csHook su companyTags
QueryModifiers/reg/CompanyLocationTagsQueryModifier.csHook su companyLocationTags: risolve l'azienda dalla sede e rinfresca la variante efficace su quello scope (:29-36)

Non esiste un IRegistryService dedicato: la logica di dominio Γ¨ distribuita fra:

  • Il QueryModifier (hook pre/post su CRUD standard)
  • I code-behind custom .razor.cs dei singoli CRUD

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

15 entitΓ  CRUD auto-generate. Pattern identico al dominio inv (vedi componenti UI inv per il pattern generale). Core di questa spec:

  • Company (con code-behind custom ricco di inject/servizi correlati)
  • CompanyContact
  • CompanyLocation (sede del cliente, classificata via locationTag)
  • Headquarter

Configurazione CRUD β€” _conf/*.dxgrid.conf.json​

Un file JSON per entitΓ  (17 totali). I nomi seguono la convenzione <nomePlural>.dxgrid.conf.json (es. companies.dxgrid.conf.json).

πŸ”§ API pubblica​

CompaniesQueryModifier​

public class CompaniesQueryModifier(
IStringLocalizer<CompaniesQueryModifier> localizer)
: IQueryModifier<DataLayer.reg.company>
{
public ValueTask PreExecutionQuery(QueryModifierArgs<company> args);
public ValueTask PostExecutionQuery(QueryModifierArgs<company> args, object? result);
public ValueTask ModifyEntity(QueryModifierArgs<company> args);
public ValueTask ModifyQuery(QueryModifierArgs<company> args);
}

⚠️ Il costruttore inietta solo il localizer, non un ISimpleCRUDService: il servizio dati usato è quello del chiamante (args.Service), l'unico che vede la riga in scrittura dentro la transazione in corso. Iniettarne uno separato aprirebbe una seconda connessione, che si bloccherebbe su quelle stesse righe.

Hook registrati in Services/QueryModifiers/HostExtensions.cs:28 (Program.cs:77 chiama solo .AddQueryModifiers()). Il pattern IQueryModifier<T> Γ¨ di Brighela.SimpleCRUD: consente di innestare logica pre/post sulle operazioni CRUD standard senza override del service.

Servizi iniettati in Company.razor.cs​

[Inject] private IRiskInheritanceService RiskService { get; set; }
[Inject] private ITrainingExpirationService ExpirationService { get; set; }
[Inject] private ISimpleCRUDService SimpleCRUD { get; set; }

Quando l'utente seleziona un'azienda nella grid, il code-behind carica in parallelo: rischi aziendali, dipartimenti, stats di compliance (dettaglio in Logica applicativa).

🧩 Pattern chiave​

Cascade riskLevel su workers​

Quando si aggiorna un'azienda, CompaniesQueryModifier.PostExecutionQuery invoca worker.UpdateRiskLevel(companyId) per ricalcolare il livello di rischio dei lavoratori collegati. Questo avviene automaticamente su ogni Update via hook del QueryModifier, senza intervento del code-behind UI.

Il comportamento garantisce che il cambio di livello di rischio aziendale (forzato manualmente o conseguente a cambio ATECO) si propaghi ai lavoratori senza ricalcoli espliciti.

imported = false su update​

Il flag imported marca aziende caricate da import massivo. Alla prima modifica manuale (UpdateSingle), il QueryModifier.PreExecutionQuery lo resetta a false: l'azienda perde lo status di "solo importata".

Nessun service dedicato​

Il dominio reg non ha un IRegistryService centrale: la logica applicativa vive in:

  • CompaniesQueryModifier (hook DB)
  • .razor.cs custom (per logica UI di caricamento dettagli, validazioni form)
  • Servizi cross-dominio invocati dai code-behind (IRiskInheritanceService, ITrainingExpirationService)

Se la logica cresce, pattern da considerare: introdurre ICompanyService sul modello di IInvoiceService.

πŸ“¦ Dipendenze​

Runtime:

  • Brighela.SimpleCRUD β€” CRUD base + IQueryModifier<T>
  • Oss.Filters β€” filtri su grid
  • Tabiot.Blazor.* β€” componenti UI (wizard, grid, popup, combo)
  • DevExpress.Blazor β€” controlli UI

Cross-dominio (dipendenze di Company.razor.cs):

  • IRiskInheritanceService (dominio job)
  • ITrainingExpirationService (dominio edu)

DB cross-dominio (FK in uscita da reg):

  • reg.companies.atecoCode β†’ job.atecoCodes(code)
  • reg.companies.riskLevelId β†’ job.riskLevel(id)
  • reg.companies.legalNatureCode β†’ reg.legalNatures(code) (fuori scope)
  • reg.companies.ccnlId β†’ reg.ccnl(id) (fuori scope)
  • reg.companies.companySubcategoryId β†’ reg.companySubcategories(id) (fuori scope)

πŸ“ File chiave​

  • Services/QueryModifiers/reg/CompaniesQueryModifier.cs
  • Components/CRUD/reg/Company.razor.cs β€” code-behind principale
  • Components/CRUD/reg/Forms/CompanyForm.razor (+ .razor.cs) β€” form con validazione VIES e propagazione ATECO β†’ riskLevel
  • Database/reg/Tables/*.sql (17)
  • BackOffice/Components/CRUD/reg/_conf/*.dxgrid.conf.json (17) β€” non esiste una cartella Database/reg/_conf

⚠️ Domande aperte / debito tecnico​

  • Nessun ICompanyService. La logica Γ¨ distribuita fra QueryModifier e code-behind. Se cresce β€” e con reg che Γ¨ base di tutto β€” valutare estrazione in un service di dominio per testabilitΓ .
  • imported / originalName flag di migrazione. Entrambi contrassegnati nel DB come "da rimuovere una volta completate le importazioni iniziali". Roadmap di pulizia da concordare. Oggi i flag sono nascosti in UI.
  • CompaniesQueryModifier.UpdateRiskLevel senza entity. Nel caso di UpdateWhere senza entity, il modifier chiama worker.UpdateRiskLevel(simpleCRUD) senza filtro per azienda specifica: ricalcola TUTTI i workers. Performance da monitorare su dataset grandi.
  • headquarters globale senza proprietario. La tabella non ha FK verso reg.companies nΓ© verso un altro "tenant": Γ¨ effettivamente singleton del formatore. Se TrainingHub diventa multi-tenant con piΓΉ enti formatori, la tabella va estesa.

πŸ”— Vedi anche​