Logica applicativa — dominio reg
🎯 Cosa fa
Il dominio reg non ha un service dedicato (ICompanyService o
equivalente). La logica applicativa vive in due punti:
CompaniesQueryModifier— hook pre/post su CRUDcompanies- Code-behind custom
.razor.cs— logica UI per caricamento dettagli, validazioni form, integrazioni cross-dominio
Questa pagina documenta dove sta cosa e i pattern in uso.
🧩 CompaniesQueryModifier
Implementa Brighela.SimpleCRUD.Service.IQueryModifier<company>.
Registrato come hook sul CRUD standard di reg.companies.
Responsabilità
Pre-execution:
- Su
UpdateSingle: settaimported = falsesull'entity in arrivo. Effetto: alla prima modifica manuale dell'utente, il flag "azienda importata da Excel" viene resettato.
Post-execution:
- Su
UpdateSingleeUpdateWhere: ricalcola il livello di rischio dei workers associati all'azienda modificata (o di tutti i workers, seUpdateWheresenza entity specifica).
public async ValueTask PostExecutionQuery(
QueryModifierArgs<company> args,
object? executionResult)
{
if (operationsToHandle.Contains(args.Operation))
{
if (args.Entity != null)
await worker.UpdateRiskLevel(simpleCRUD, companyId: args.Entity.id);
else
await worker.UpdateRiskLevel(simpleCRUD); // tutti
}
}
private static readonly List<SqlOperation> operationsToHandle = [
SqlOperation.UpdateSingle,
SqlOperation.UpdateWhere
];
Perché è un QueryModifier e non un service
- Si innesta su ogni percorso che aggiorna
companies, incluso:- UI CRUD standard (form azienda)
- Batch update da altri moduli
- Direct call al servizio generico
ISimpleCRUDService
- Non richiede che i chiamanti conoscano la logica di cascade rischio: funziona trasparentemente.
- Svantaggio: effetto "magico" — chi legge il codice chiamante non vede la cascade, scopre il comportamento solo leggendo il modifier.
🧩 Code-behind custom
Company.razor.cs — dettaglio aggregato
Carica in parallelo rischi aziendali, dipartimenti e compliance stats
quando l'utente seleziona un'azienda. Usa Task.WhenAll per
parallelismo e traccia lastLoadedCompanyId per idempotency.
Servizi iniettati:
IRiskInheritanceService(dominiojob)ITrainingExpirationService(dominioedu)ISimpleCRUDService(CRUD generico)
Forms/CompanyForm.razor.cs — validazione VIES e cascade ATECO
Logiche chiave:
SetRiskLevelId— invocato dopo cambio ATECO o toggleriskLevelOverride. Carica il riskLevel associato all'ATECO dal dominiojobe lo imposta suldataItemse override non attivo.ValidateVatCodeAsync— chiama l'integrazione VIES (servizio esterno o API), settaviesValidated = truein caso di successo.
Esempio:
private async Task SetRiskLevelId()
{
if (dataItem.riskLevelOverride) return;
var risk = await simpleCRUD.GetAsync<job.atecoCode, string>(dataItem.atecoCode);
if (risk != null) dataItem.riskLevelId = risk.riskLevelId;
}
Altri .razor.cs reg
CompanyContact.razor.cs,CompanyLocation.razor.cs,Headquarter.razor.cs— code-behind minimi, override standard senza logica estesa.
📦 Dipendenze cross-dominio
Il dominio reg dipende da:
| Dipendenza | Provenienza | Uso |
|---|---|---|
DataLayer.job.worker.UpdateRiskLevel | Dominio job | Cascade rischi da QueryModifier |
IRiskInheritanceService | Dominio job | Dettaglio rischi aziendali in UI |
ITrainingExpirationService | Dominio edu | Stats compliance in UI |
ISimpleCRUDService | Brighela.SimpleCRUD | CRUD generico |
I domini inv, edu, iso, bi dipendono a loro volta da reg (via
FK companyId nelle rispettive tabelle), creando relazione
bidirezionale di fatto: reg è base dati ma importa logica
cross-dominio tramite service injection.
🧩 Pattern e convenzioni
Idempotency per carichi UI
lastLoadedCompanyId in Company.razor.cs evita ricarichi duplicati
al reclick sulla stessa riga:
private Guid? lastLoadedCompanyId;
protected async Task OnSelectedCompanyChanged(Guid companyId)
{
if (lastLoadedCompanyId == companyId) return;
lastLoadedCompanyId = companyId;
// ... load ...
}
Pattern da replicare quando una selezione di riga triggera caricamenti di dataset correlati costosi.
Nessun layer di validazione business
Il form non blocca partite IVA mal formate, codici fiscali invalidi o SDI errati: la validazione strutturale non è implementata. Emerso come open question in pagine utente e schema DB.
Cascade implicita via DB FK
Cancellazione di un'azienda è impossibile se ci sono record dipendenti
(invoices, workers, ecc.): la cancellazione fallisce con errore FK
DB. Il messaggio generico "record in uso" emerge anche in UI (vedi
pagina utente Convenzioni (docs-site-user) per caso
simile).
📁 File chiave
TrainingHub.BackOffice/Services/QueryModifiers/reg/CompaniesQueryModifier.csTrainingHub.BackOffice/Components/CRUD/reg/Company.razor.csTrainingHub.BackOffice/Components/CRUD/reg/Forms/CompanyForm.razor.csProgram.cs— registrazione DI di QueryModifier e services correlati
⚠️ Debito tecnico
- Nessun
ICompanyService. Pattern esistente ininv(IInvoiceService) non replicato inreg. Se la logica cresce, l'assenza di un layer di service diventa problema per testabilità e separazione di responsabilità. -
PostExecutionQuerysenza entity ricalcola tutti i workers. PathUpdateWheresenza entity specifica invocaworker.UpdateRiskLevel(simpleCRUD)globale. Su dataset grandi è costoso. Mitigazione: evitareUpdateWheresucompanieso accettare il costo come raro. - Cross-dominio nel code-behind UI.
Company.razor.csinietta services dijobededu. Concentra responsabilità di aggregazione nel componente UI. Se crescono i pannelli, valutare ViewModel/Query object dedicato. - Validazione P.IVA / CF / SDI non strutturata. Manca
validazione formato (Luhn, regex). Errori silenziosi al salvataggio.
Feature da aggiungere in
CompanyForm.razor.cso in un servizio centralizzato. -
importedreset implicito. Utente non è avvisato del comportamento. Documentare o rendere esplicito (es. toast "azienda marcata come manualmente verificata"). - Magic string
"imported"e"riskLevel"in flussi cascade. Refactoring in costanti o enum semplifica audit del comportamento.
🔗 Vedi anche
- Panoramica dominio
- Schema DB —
IX_companies_activeottimizza le query su aziende attive - Componenti UI — form con cascade ATECO e VIES
- Dominio
inv: servizi — pattern service-oriented per confronto