Dominio inv - Panoramica sviluppatore
🎯 Cosa fa
Il dominio inv gestisce la fatturazione elettronica FatturaPA:
emissione, trasmissione via Aruba al Sistema di Interscambio,
tracciamento stati, piano rate e convenzioni commerciali.
🗺️ Mappa moduli
Database - TrainingHub.Database/inv/
| File | Ruolo |
|---|---|
Tables/issuers.sql | Emittenti (cedente/prestatore + credenziali Aruba) |
Tables/bankAccounts.sql | Anagrafica conti bancari per emittente |
Tables/invoices.sql | Intestazione fattura |
Tables/invoiceLines.sql | Righe di dettaglio |
Tables/invoicePayments.sql | Piano rate |
Tables/invoiceAppointments.sql | Associazione N:N fattura ↔ appuntamenti formativi |
Tables/invoiceStatusHistory.sql | Storico cambi di stato |
Tables/conventions.sql | Convenzioni (sconto per categoria corso) |
Tables/companyConventions.sql | Associazione N:N azienda ↔ convenzione |
Tables/vatCodes.sql | Codici IVA riutilizzabili |
Tables/paymentMethods.sql | Lookup codici metodo pagamento SDI (MP01–MP23) |
Tables/documentTypes.sql | Lookup codici tipo documento SDI (TD01–TD28) |
Tables/invoiceCounters.sql | Numerazioni progressive per (issuerId, documentTypeCode, year) |
Tables/invoiceDocuments.sql | Allegati della fattura |
Tables/reminders.sql | Promemoria fatturabili (sorgente di riga) |
Tables/companyPaymentTerms.sql | Piani di pagamento per azienda, con default globale (companyId IS NULL) |
Functions/fn_activeConventions.sql | TVF: convenzioni attive per (companyId, issueDate) |
Views/vw_invoicesData.sql | Vista dati fatture (CRUD generato) |
Views/vw_billableItems.sql, vw_billableAppointments.sql, vw_billableReminders.sql | Il fatturabile, con pagine dedicate |
In tutto: 16 tabelle, 4 viste e 1 funzione.
Service layer - TrainingHub.BackOffice/Services/Invoicing/
23 file. I principali:
| File | Ruolo |
|---|---|
IInvoiceService.cs / InvoiceService.cs | Orchestrazione fattura + integrazione Aruba |
IInvoiceBillingService.cs / InvoiceBillingService.cs | Righe da appuntamenti e promemoria |
IArubaSdiService.cs / ArubaSdiService.cs | HTTP verso Aruba, con retry |
IFatturaPaXmlGenerator.cs / FatturaPaXmlGenerator.cs | XML FatturaPA 1.2 |
PaymentTermsService.cs + PaymentTermsCalculator.cs | Piani di pagamento |
InvoiceCourtesyCopyService.cs | Copia di cortesia |
InvoiceStatusRefreshWorker.cs | Refresh stati ogni 10 minuti |
Elenco completo in Servizi.
UI CRUD - TrainingHub.BackOffice/Components/CRUD/inv/
19 pagine CRUD auto-generate. Pattern per ciascuna:
<Entity>.razor+.razor.tt.cs(grid autogenerata)<Entity>.razor.cs(code-behind custom)Forms/<Entity>Form.razor+.razor.cs+.razor.tt.csFormPopups/<Entity>FormPopup.razor+.razor.cs+.razor.tt.cs
Entità presenti:
Invoice(con wizard a 5 step inFormPopups/InvoiceFormPopup.razor+ i partialInvoiceFormPopup.razor.Step*.cs)InvoiceLine,InvoicePayment,InvoiceAppointment,InvoiceStatusHistory,InvoiceDocument,InvoiceCounterReminder,CompanyPaymentTermIssuer,VatCode,PaymentMethod,DocumentTypeBankAccountConvention,CompanyConvention- Generati su vista:
InvoicesData,BillableItem,BillableAppointment,BillableReminder
Configurazione CRUD - _conf/*.dxgrid.conf.json
Un file JSON per entità che guida la generazione. Modificare qui per
cambi persistenti; i file .razor e .razor.tt.cs vengono rigenerati
dal tool MCP 3sd-generator.
Test - TrainingHub.UnitTests/Services/Invoicing/
21 file di test sull'area. Fra i principali: InvoiceServiceTests.cs
(numerazione, invio, storico, piano rate),
InvoiceStatusExtensionsTests.cs (mapping notifiche SDI),
ArubaSdiServiceRetryTests.cs e ArubaSdiServiceRateLimitTests.cs
(retry e 429), PaymentTermsServiceTests.cs,
InvoiceCourtesyCopyServiceTests.cs,
InvoiceStatusRefreshWorkerTests.cs.
🔧 API pubblica - IInvoiceService
Task<invoice> AssignNumberAsync(Guid invoiceId, CancellationToken ct);
Task<string> GeneratePreviewXmlAsync(Guid invoiceId, CancellationToken ct = default);
Task SendToArubaAsync(Guid invoiceId, CancellationToken ct);
Task RefreshStatusFromArubaAsync(Guid invoiceId, CancellationToken ct);
Task AddStatusHistoryAsync(Guid invoiceId, InvoiceStatus status, string? arubaStatus, string? notes, string? createdBy, CancellationToken ct);
Task<IEnumerable<invoiceStatusHistory>> GetStatusHistoryAsync(Guid invoiceId, CancellationToken ct);
Task<IEnumerable<invoicePayment>> GetPaymentsAsync(Guid invoiceId, CancellationToken ct);
Task SavePaymentsAsync(Guid invoiceId, IEnumerable<invoicePayment> payments, CancellationToken ct);
Registrato in Program.cs nella sezione DI.
🧩 Pattern chiave
Wizard fattura
Il form di edit della fattura è un wizard a 5 step realizzato in
FormPopups/InvoiceFormPopup.razor + .razor.cs, usando
Tabiot.Blazor.Wizard.Wizard. Sequenza step e hook OnStepChange:
| Step | FormId | Titolo | Hook |
|---|---|---|---|
| 1 | invoice_data | Dati fattura | SaveInvoiceDraft |
| 2 | invoice_lines | Righe fattura | sorgenti di fatturazione aggiunte da popup (appuntamenti, promemoria) |
| 3 | payments | Pagamenti | SavePayments (valida somma = 100%) |
| 4 | summary | Riepilogo | LoadSummary (totali + storico + pulsanti SDI) |
| 5 | documents | Documenti | allegati della fattura |
Generazione CRUD
I file .razor e .razor.tt.cs sono rigenerati dal tool MCP
3sd-generator a partire dallo schema DB e dal conf.json. Modifiche
dirette in questi file vanno perse alla rigenerazione: per rendere
persistenti le modifiche aggiornare il conf.json e rigenerare.
Il code-behind .razor.cs contiene il codice custom e non viene
toccato dalla generazione.
Applicazione convenzioni
Al momento dell'emissione fattura, CreateLinesFromAppointments (in
InvoiceFormPopup.razor.cs) usa inv.fn_activeConventions(companyId, issueDate) per recuperare lo sconto attivo e pre-popolare la riga con
il discountPercentage corretto.
Numerazione
La bozza non è numerata. All'emissione (SendToArubaAsync), AssignNumberAsync
incrementa in transazione il contatore inv.invoiceCounters della serie
(issuerId, documentTypeCode, year) - serie dedicata al tipo documento se esiste,
altrimenti la serie di default (documentTypeCode NULL) condivisa da tutti i tipi.
Il code è generato con un template Handlebars (templatingExpression, default
{{year}}/{{progressive}}); roll-over annuale con terminated, retry su collisione.
L'unicità (issuerId, documentTypeCode, invoiceYear, invoiceNumber) è garantita
dall'indice filtrato UQ_invoices_number (WHERE invoiceNumber IS NOT NULL).
📦 Dipendenze
Brighela.SimpleCRUD.Service.ISimpleCRUDService- CRUD di baseOss.Filters- filtri su gridTabiot.Blazor.*- wizard, grid, popup, comboDevExpress.Blazor- componenti UIMicrosoft.Extensions.Localization.IStringLocalizer<T>- etichette localizzate- Ecosistema 3SD: Scarnas, Brighela, Servel, Ploc, Tabiot, Mulet
⚠️ Domande aperte / debito tecnico
- Credenziali Aruba in chiaro.
inv.issuers.arubaPasswordè memorizzata in chiaro. Decisione esplicita a design (back-office interno), ma valutare cifratura simmetrica con chiave da config per compliance futura. -
Job schedulato di refresh stato fatture inviate.Consegnato.Services/Invoicing/InvoiceStatusRefreshWorker.csè unBackgroundServiceconInterval = TimeSpan.FromMinutes(10), registrato inProgram.cs:133(AddHostedService<…>()) e governato dal gate di configurazioneInvoiceStatusRefresh:Enabled. Il refresh manuale via pulsante resta come scorciatoia.
🔗 Vedi anche
- Schema DB - dettaglio tabelle, FK, indici
- Componenti UI - struttura Blazor CRUD
- Servizi - logica
InvoiceService - Aggiungere un campo - tutorial
- Guida utente: Panoramica fatturazione (docs-site-user)