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 5 passi)
InvoiceLineGrid/form delle righe fattura — usato inline nel wizard
InvoicePaymentGrid/form rate di pagamento — usato inline nel wizard
InvoiceAppointmentAssociazioni fattura ↔ appuntamenti (gestita internamente al wizard)
InvoiceStatusHistoryStorico stati — mostrato nel riepilogo del wizard
IssuerAnagrafica emittente
VatCodeCodici IVA
ConventionConvenzioni
CompanyConventionAssociazioni azienda ↔ convenzione

🧩 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):

FileResponsabilità
InvoiceFormPopup.razor.csInject, state, CustomEditCompleted, PopupClosing (core)
InvoiceFormPopup.StepDraft.csPasso 1 — IsFormValid, SaveInvoiceDraft, IsItemChanged
InvoiceFormPopup.StepAppointments.csPasso 2 — LoadAppointments, CreateLinesFromAppointments, GetConventionDiscount, record AppointmentRow/ActiveConventionRow
InvoiceFormPopup.StepPayments.csPasso 4 — LoadPayments, PaymentsValid, IsPaymentsValid, SavePayments
InvoiceFormPopup.StepSummary.csPasso 5 — LoadSummary, HandleSend, HandleRefreshStatus, HandlePreviewXml, CopyPreviewXmlToClipboard, ReloadInvoiceAndHistory

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