Passa al contenuto principale

Schema DB inv

🎯 Cosa fa​

Definisce il persistence layer della fatturazione: 16 tabelle, 4 viste e 1 funzione nel schema inv del progetto TrainingHub.Database. Deploy tramite DACPAC via pipeline Azure.

πŸ—ΊοΈ Tabelle​

TabellaRuolo
inv.issuersEmittente fattura (cedente/prestatore) con credenziali Aruba
inv.invoicesIntestazione fattura
inv.invoiceLinesRighe di dettaglio di una fattura
inv.invoicePaymentsPiano rate di una fattura
inv.invoiceAppointmentsAssociazione N:N fattura ↔ appuntamenti formativi
inv.invoiceStatusHistoryStorico cambi di stato di una fattura
inv.bankAccountsAnagrafica conti bancari per emittente
inv.vatCodesCodici IVA riutilizzabili (aliquota + natura)
inv.paymentMethodsLookup codici metodo di pagamento SDI (MP01–MP23)
inv.documentTypesLookup codici tipo documento SDI (TD01–TD28)
inv.conventionsConvenzioni commerciali (sconto per categoria corso)
inv.companyConventionsAssociazione N:N azienda ↔ convenzione
inv.companyPaymentTermsPiano di pagamento per azienda; companyId NULL = piano predefinito

πŸ”— Relazioni​

Referenze esterne: reg.companies, reg.organizers, edu.categories, edu.appointments.

πŸ—‚οΈ Dettaglio tabelle​

inv.issuers​

Emittente fattura. Una ragione sociale 3SD con credenziali Aruba dedicate.

  • PK: id (GUID, default newid())
  • FK: organizerSlug β†’ reg.organizers(slug)
  • Campi chiave:
    • companyName, vatCode, fiscalCode, address, city, zipCode, province, country (default 'IT') β€” anagrafica legale
    • pec, reaOffice, reaNumber, shareCapital β€” extra FatturaPA
    • arubaUsername, arubaPassword (in chiaro), arubaEnvironment ('demo' | 'production', default 'demo')
  • Check: CHK_issuers_arubaEnvironment su arubaEnvironment

inv.invoices​

Intestazione fattura.

  • PK: id
  • FK: issuerId β†’ inv.issuers(id), companyId β†’ reg.companies(id), documentTypeCode β†’ inv.documentTypes(code), relatedInvoiceId β†’ inv.invoices(id) (self-FK, nullable)
  • Campi chiave:
    • documentTypeCode (NVARCHAR(8), default 'TD01') β€” codice SDI tipo documento (FatturaPA), es. TD01 fattura, TD04 nota di credito
    • relatedInvoiceId (self-FK verso inv.invoices, nullable) β€” fattura stornata da questa nota di credito; valorizzato solo sui documenti TD04, creato da InvoiceService.BuildCreditNoteHeader
    • code (renderizzato dal template della serie, es. 2026/1), invoiceNumber (progressivo), invoiceYear β€” NULL in bozza: assegnati solo all'emissione (vedi inv.invoiceCounters)
    • issueDate, status (string, non enum DB β€” mappato a InvoiceStatus lato C#)
    • taxableAmount, vatAmount, totalAmount
    • notes
    • Aruba: arubaTransmissionId, sdiProtocolNumber, arubaStatus, arubaLastCheckAt, arubaNotificationXml, sentXmlContent
    • Audit: createdAt (default getdate()), updatedAt
  • Unique: UQ_invoices_number(issuerId, documentTypeCode, invoiceYear, invoiceNumber) β€” indice filtrato WHERE invoiceNumber IS NOT NULL: garantisce unicitΓ  progressivo per emittente/tipo/anno ammettendo piΓΉ bozze non numerate

inv.invoiceCounters​

Contatore progressivo per serie di numerazione, sorgente autoritativa (assegnata all'emissione, non in bozza).

  • Campi chiave:
    • issuerId (FK β†’ inv.issuers), year
    • documentTypeCode (FK β†’ inv.documentTypes, nullable) β€” serie dedicata al tipo documento (es. TD04 note di credito); NULL = serie di default valida per tutti i tipi senza contatore specifico (fallback)
    • counter β€” ultimo numero assegnato (prossimo = counter + 1)
    • templatingExpression β€” template Handlebars del codice (variabili progressive, year, documentTypeCode; default {{year}}/{{progressive}})
    • terminated β€” serie chiusa: non usata per il roll-over dell'anno successivo
  • Unique: UQ_invoiceCounters_series(issuerId, documentTypeCode, year)

inv.invoiceLines​

Righe di dettaglio fattura.

  • PK: id
  • FK: invoiceId β†’ inv.invoices(id) ON DELETE CASCADE, vatCodeId β†’ inv.vatCodes(id)
  • Campi chiave:
    • lineNumber (progressivo da 1)
    • productServiceCode (opzionale), description, quantity, unitOfMeasure, unitPrice, discountPercentage
    • totalAmount (calcolato lato app: quantity * unitPrice * (1 βˆ’ discount/100))
  • Cascade: elimina riga se fattura eliminata

inv.invoicePayments​

Piano rate di pagamento.

  • PK: id
  • FK: invoiceId β†’ inv.invoices(id) ON DELETE CASCADE, paymentMethodCode β†’ inv.paymentMethods(code), bankAccountId β†’ inv.bankAccounts(id) (nullable)
  • Campi chiave:
    • lineNumber, paymentMethodCode (codice SDI), paymentDueDate, paymentDate (data ricezione, nullable), bankAccountId (nullable; obbligatorio solo se paymentMethods.ibanRequired = 1)
    • percentage (somma rate = 100%), amount (calcolato: totalAmount * percentage / 100)

inv.invoiceAppointments​

Associazione N:N tra fattura e appuntamenti formativi.

  • PK composita: (invoiceId, appointmentId)
  • FK: invoiceId β†’ inv.invoices(id) ON DELETE CASCADE, appointmentId β†’ edu.appointments(id)

inv.invoiceStatusHistory​

Storico cambi di stato di una fattura.

  • PK: id
  • FK: invoiceId β†’ inv.invoices(id) (no cascade β€” audit trail)
  • Campi chiave:
    • status, arubaStatus (nullable), notes, createdAt (default getdate()), createdBy

inv.bankAccounts​

Anagrafica conti bancari per emittente. Riferita da inv.invoicePayments.bankAccountId per il rendering del nodo <IBAN> nell'XML FatturaPA.

  • PK: id
  • FK: issuerId β†’ inv.issuers(id)
  • Campi chiave:
    • label (etichetta breve), iban (NVARCHAR(64), formato SEPA/IT validato lato C# da [ItalianIban]), bankName, bic (NVARCHAR(11)), accountHolder
    • active (bit, default 1) β€” soft-delete: se 0 il conto non Γ¨ selezionabile in nuove rate ma resta legato ai pagamenti storici
    • isDefault (bit, default 0) β€” conto ereditato dalle rate generate da inv.companyPaymentTerms
  • Indici: IX_bankAccounts_issuerId; UQ_bankAccounts_default (unique filtrato su issuerId WHERE isDefault = 1: al massimo un predefinito per emittente, garantito dal DB)

inv.vatCodes​

Codici IVA riutilizzabili.

  • PK: id
  • Campi chiave: label, vatRate, vatNature (se aliquota = 0)

inv.paymentMethods​

Lookup dei codici metodo di pagamento SDI (FatturaPA, MP01–MP23). Riferito da inv.invoicePayments.paymentMethodCode.

  • PK: code (NVARCHAR(8), es. MP05)
  • Campi chiave:
    • label (es. Bonifico), description
    • ibanRequired (bit) β€” se 1, l'IBAN diventa obbligatorio sulla rata che usa questo metodo
    • active (bit, default 1) β€” flag soft-delete
  • Seed: Scripts/PostDeploy/01-seed-lookups.sql:8 (eseguito dal DACPAC a ogni publish; il vecchio Scripts/seed-inv-paymentMethods.sql Γ¨ archiviato in Scripts/_archive/2026-06/)

inv.documentTypes​

Lookup dei codici tipo documento SDI (FatturaPA, TD01–TD28). Riferito da inv.invoices.documentTypeCode.

  • PK: code (NVARCHAR(8), es. TD01)
  • Campi chiave:
    • label (es. Fattura, Nota di credito), description
    • active (bit, default 1) β€” flag soft-delete; i tipi non attivi non sono selezionabili in nuove fatture
  • Seed: Scripts/PostDeploy/01-seed-lookups.sql:51 (idem β€” il vecchio Scripts/seed-inv-documentTypes.sql Γ¨ archiviato)

inv.conventions​

Convenzioni commerciali.

  • PK: id
  • FK: categoryId β†’ edu.categories(id), organizerSlug β†’ reg.organizers(slug) (entrambe nullable)
  • Campi chiave:
    • label, discountPercentage
    • validFrom (default '1900-01-01'), validTo (nullable = illimitata)

inv.companyConventions​

Associazione N:N azienda ↔ convenzione, con override validitΓ  opzionale.

  • PK: id
  • FK: companyId β†’ reg.companies(id), conventionId β†’ inv.conventions(id)
  • Unique: UQ_companyConventions(companyId, conventionId)
  • Campi chiave: validFrom, validTo (entrambi nullable; NULL = usa le date della convenzione)

inv.companyPaymentTerms​

Piano di pagamento: una riga per rata. Le righe con companyId IS NULL sono il piano predefinito, applicato alle aziende prive di righe proprie. Consumato da PaymentTermsService, che ne deriva le inv.invoicePayments della fattura.

  • PK: id
  • FK: companyId β†’ reg.companies(id) (nullable), paymentMethodCode β†’ inv.paymentMethods(code)
  • Unique: UQ_companyPaymentTerms(companyId, lineNumber) β€” SQL Server tratta NULL come un valore nell'unicitΓ , quindi (NULL,1) e (NULL,2) convivono e un secondo (NULL,1) viene rifiutato: Γ¨ esattamente la semantica voluta per il piano predefinito
  • Check: percentage > 0 AND <= 100, daysOffset >= 0
  • Campi chiave:
    • lineNumber β€” ordine della rata nel piano
    • daysOffset β€” giorni di dilazione dalla data fattura
    • endOfMonth (bit, default 0) β€” la scadenza calcolata slitta all'ultimo giorno del suo mese: i giorni si sommano prima, il fine mese si applica dopo (15/01 + 30 gg f.m. β†’ 14/02 β†’ 28/02)
    • percentage β€” quota del totale; le righe di un piano devono sommare 100, altrimenti il piano non viene applicato affatto
  • Indice: IX_companyPaymentTerms_companyId
  • Seed: PostDeploy/01-seed-lookups.sql inserisce il predefinito (MP05, 30 giorni, 100%) solo se non esiste alcuna riga globale

πŸ”§ Funzioni​

inv.fn_activeConventions(@companyId, @issueDate)​

TVF che restituisce le convenzioni attive per un'azienda a una data data. Verifica entrambe le validitΓ  (convenzione e associazione):

SELECT c.id AS conventionId, c.categoryId, c.discountPercentage
FROM inv.conventions c
INNER JOIN inv.companyConventions cc ON cc.conventionId = c.id
WHERE cc.companyId = @companyId
AND @issueDate >= c.validFrom
AND (c.validTo IS NULL OR @issueDate <= c.validTo)
AND (cc.validFrom IS NULL OR @issueDate >= cc.validFrom)
AND (cc.validTo IS NULL OR @issueDate <= cc.validTo);

Usata da InvoiceService / wizard fattura per applicare lo sconto in automatico.

πŸ“ File chiave​

  • TrainingHub.Database/inv/Tables/*.sql β€” tabelle
  • TrainingHub.Database/inv/Functions/fn_activeConventions.sql β€” funzione TVF
  • TrainingHub.Database/TrainingHub.Database.sqlproj β€” progetto SQL

⚠️ Debito tecnico​

  • invoices.status come stringa anzichΓ© FK a una lookup o CHECK esplicito: oggi validato solo lato C# con Enum.Parse. Valori errati scritti in DB via altri path non sono bloccati. Valutare check constraint o tabella di lookup.
  • arubaPassword in chiaro. Decisione esplicita a design (back- office interno con accesso controllato), ma da rivedere per compliance.
  • Indici espliciti minimi. Esiste unique su invoices.number, companyConventions(companyId, conventionId) e index su bankAccounts.issuerId. Valutare indici aggiuntivi su FK frequentemente usate in join (es. invoiceLines.invoiceId, invoicePayments.invoiceId, invoiceStatusHistory.invoiceId) se query lente emergono in profilo.

πŸ”— Vedi anche​