Passa al contenuto principale

Componenti UI - dominio inv

🎯 Cosa fa​

Sotto TrainingHub.BackOffice/Components/CRUD/inv/ vivono i componenti Blazor CRUD auto-generati (tramite MCP 3sd-generator) per le 9 entità del dominio fatturazione. Ogni entità ha la stessa triade di file (grid + form + popup) più un code-behind custom per logica non generabile.

🗺️ Entità​

EntitàUso
InvoicePagina principale fattura (con wizard a 5 step)
InvoiceLineGrid/form delle righe fattura - usato inline nel wizard
InvoicePaymentGrid/form rate di pagamento - usato inline nel wizard
InvoiceStatusHistoryStorico stati - mostrato nel riepilogo del wizard
InvoiceDocumentAllegati della fattura - step 5 del wizard
InvoiceCounterNumerazioni progressive per emittente/anno
ReminderPromemoria fatturabili (/inv/reminders)
CompanyPaymentTermPiani di pagamento per azienda (con default globale)
BankAccountConti bancari dell'emittente
PaymentMethod / DocumentTypeLookup SDI
IssuerAnagrafica emittente
VatCodeCodici IVA
ConventionConvenzioni
CompanyConventionAssociazioni azienda ↔ convenzione
InvoicesDataCRUD generato sulla vista inv.vw_invoicesData
BillableItem / BillableCourse / BillableSession / BillableReminderGriglie del fatturabile, generate sulle viste inv.vw_billable*

In tutto sono 20 le pagine con @page sotto Components/CRUD/inv/.

🧩 Pattern chiave​

Triade di file per entità​

Per ogni entità <Entity> la generazione produce:

CRUD/inv/
├── <Entity>.razor ← grid autogenerata
├── <Entity>.razor.tt.cs ← partial autogenerata (init, FK, query)
├── <Entity>.razor.cs ← code-behind CUSTOM (mai rigenerato)
├── Forms/
│ ├── <Entity>Form.razor ← form autogenerato
│ ├── <Entity>Form.razor.tt.cs
│ └── <Entity>Form.razor.cs ← code-behind custom form
└── FormPopups/
├── <Entity>FormPopup.razor
├── <Entity>FormPopup.razor.tt.cs
└── <Entity>FormPopup.razor.cs

Regola chiave: i .razor e .razor.tt.cs vengono sovrascritti alla rigenerazione. Tutto il codice custom deve stare nel .razor.cs (che è una partial class del componente).

Dalla documentazione di progetto (CLAUDE.md):

I file all'interno delle cartelle CRUD sono generati automaticamente: se li modifichi direttamente, per persistere le modifiche devi riportarle nei relativi file di configurazione (conf.json) e rigenerare tramite i tool MCP del server 3sd-generator.

File di configurazione - _conf/*.dxgrid.conf.json​

Per ogni entità esiste un JSON in CRUD/inv/_conf/ che guida la generazione. Esempio invoices.dxgrid.conf.json (estratto):

{
"PageRoute": "inv/invoices",
"PageAuthorizationPolicy": "page-inv-invoices",
"AllowView": true,
"AllowInsert": true,
"AllowUpdate": true,
"AllowClone": true,
"AllowDelete": true,
"FormPopup": { "CssClass": "min-w-80" },
"Columns": { ... },
"FormOrder": [ ... ]
}

Il conf.json controlla:

  • Rotta, policy di autorizzazione (_R, _C, _U, _D), label
  • Visibilità e ordine colonne in grid
  • Visibilità, ordine, readonly dei campi in form (insert vs update)
  • MarkupBeforeForm / MarkupAfterForm - inserimento di markup Razor custom intorno al form generato (es. il wizard)
  • EventsReplacement / CommandConditions - hook di estensione

Code-behind custom - .razor.cs​

Esempio Invoice.razor.cs (36 righe):

public partial class Invoice
{
[Inject] private IInvoiceService invoiceService { get; set; } = default!;

protected async Task HandleSend(invoice item)
{
try
{
await invoiceService.SendToArubaAsync(item.id);
toast.ShowSuccess(localizer["invoice_sent_success"]);
await LoadData();
}
catch (Exception ex)
{
toast.ShowError($"{localizer["invoice_send_error"]}: {ex.Message}");
}
}

protected async Task HandleRefreshStatus(invoice item) { ... }
}

Tipicamente contiene:

  • Inject di servizi di dominio (IInvoiceService)
  • Handler di eventi custom chiamati dai command button definiti in conf.json
  • Override di metodi virtuali della classe base DxGridBase<T>

Wizard fattura - FormPopups/InvoiceFormPopup.*​

Il FormPopup dell'entità Invoice ospita il wizard a 5 step. La logica è splittata in partial class per step (uno per ogni passo del wizard):

Attenzione al nome dei file: il .razor. sta in mezzo (InvoiceFormPopup.razor.StepDraft.cs), non alla fine.

FileResponsabilità
InvoiceFormPopup.razor.csInject, state, CustomEditCompleted, PopupClosing (core)
InvoiceFormPopup.razor.StepDraft.csPasso 1 - IsFormValid, SaveInvoiceDraft, IsItemChanged
InvoiceFormPopup.razor.StepBillingSources.csPasso 2 - sorgenti di fatturazione (corsi, sessioni dedicate e promemoria) aggiunte da popup dentro lo step Righe
InvoiceFormPopup.razor.StepPayments.csPasso 3 - LoadPayments, PaymentsValid, IsPaymentsValid, SavePayments
InvoiceFormPopup.razor.StepSummary.csPasso 4 - LoadSummary, HandleSend, HandleRefreshStatus, HandlePreviewXml, CopyPreviewXmlToClipboard, ReloadInvoiceAndHistory

Gli step reali sono invoice_data, invoice_lines, payments, summary, documents (InvoiceFormPopup.razor:22,51,71,91,142): non esiste più uno step «Appuntamenti» separato, e i Documenti sono il quinto.

Il markup del wizard (5 step + popup anteprima XML) è iniettato dal conf.json di invoices tramite gli hook FormPopup.MarkupBeforeForm, MarkupAfterForm e MarkupEnd, non è scritto a mano nel .razor (che viene rigenerato).

Pattern Send / Refresh / PreviewXml condiviso tra grid invoice e StepSummary tramite l'helper InvoiceActionRunner.ExecuteAsync (vedi Servizi) - incapsula try / azione / toast successo / reload / catch toast errore.

Componenti CRUD "figli" riusati inline​

InvoiceLine e InvoicePayment sono componenti CRUD a sé stanti (con propria pagina) ma vengono embedded nel wizard con parametri per disattivare la propria navigazione:

<InvoiceLine invoiceId="@dataItem.id"
SetPageData="false"
CommandColumnVisible="@(!readOnly)" />

Questo riuso evita duplicazione UI tra pagina standalone e step wizard.

📁 File chiave​

  • CRUD/inv/Invoice.razor.cs - handler Send / RefreshStatus sulla grid fatture
  • CRUD/inv/FormPopups/InvoiceFormPopup.razor.cs - logica wizard 5 step
  • CRUD/inv/_conf/invoices.dxgrid.conf.json - configurazione generazione (rotte, permessi, colonne, markup wizard)
  • CRUD/inv/_conf/*.dxgrid.conf.json - conf delle altre 8 entità

🔌 Estensione tipica​

Aggiungere un command button alla grid​

  1. Modificare _conf/<entity>.dxgrid.conf.json: aggiungere entry in CommandColumnItems (o equivalente) con nome, icona, policy.
  2. Aggiungere handler nel .razor.cs custom.
  3. Rigenerare via MCP 3sd-generator.

Nascondere un campo in form​

Modificare _conf/<entity>.dxgrid.conf.json → sezione Columns o FormFieldSettings: impostare visibilità Insert/Update su hidden o readonly. Rigenerare.

Aggiungere logica a un form​

Scrivere la logica in Forms/<Entity>Form.razor.cs (partial class). Il .razor.tt.cs chiama punti di estensione (OnInitializedAsync, OnSubmit, ecc.) che il custom può sovrascrivere.

Aggiungere markup custom​

Per markup che resiste alla rigenerazione (es. un wizard), inserirlo nei campi MarkupBeforeForm / MarkupAfterForm del conf.json. Il generatore li include nei punti giusti del .razor.

⚠️ Debito tecnico​

  • Commento "FormPopup.MarkupBeforeForm" nel conf: il markup inline JSON rende difficile la leggibilità/diff del wizard. Valutare estrazione in file Razor esterno incluso, se supportato dal generatore.

🔗 Vedi anche​