Files
SmartReports/CertReports.Syncfusion/DOCS.md
SmartRootsSrl 8c3a900a9c docs: update README and DOCS with ?dividend=true feature
- Document ?dividend=true parameter in all report endpoints
- Add DividendSectionRenderer to architecture diagrams and file structure
- Update Struttura del report sections (Sezione 1b optional)
- Document 4-key cache combinations (base/branded/dividend/branded+dividend)
- Add 2026-03-23 changelog entries in DOCS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 17:38:48 +01:00

23 KiB
Raw Blame History

CertReports.Syncfusion — Documentazione Tecnica

Progetto ASP.NET Core 8 per la generazione di report PDF per certificati finanziari strutturati.
Sostituisce il vecchio progetto WebForms basato su DevExpress (non licenziato) + PdfSharp.
Utilizza Syncfusion PDF Library (Community License) e SkiaSharp per i grafici.


1. Panoramica

Il sistema riceve un codice ISIN (in chiaro, cifrato o tramite alias), interroga il database SQL Server tramite stored procedures, genera un report PDF composto da 4 sezioni e lo restituisce come stream inline al browser.

Flusso di generazione

Richiesta HTTP (ISIN)
    │
    ▼
ReportController ──→ decodifica ISIN (se cifrato)
    │
    ▼
ReportOrchestrator
    │
    ├──→ CertificateDataService
    │       ├── rpt_Master_CFT_ISIN        → Anagrafica certificato
    │       ├── rpt_Details_UL_ISIN        → Sottostanti
    │       ├── rpt_Events_CFT_ISIN        → Eventi
    │       └── rpt_AnalisiScenario_ISIN   → Analisi scenario
    │
    ├──→ AnagraficaSectionRenderer         → PDF Sezione 1 (tabella sottostanti omessa se showDividend=true)
    ├──→ DividendSectionRenderer           → PDF Sezione 1b (solo se showDividend=true, landscape A4)
    ├──→ EventiSectionRenderer             → PDF Sezione 2
    ├──→ ScenarioSectionRenderer           → PDF Sezione 3 (se dati presenti)
    │
    ├──→ ChartDataService
    │       ├── FSWeb_Chart_UL             → Info sottostanti grafico
    │       ├── FSWeb_Chart_DailyCTF       → Performance certificato
    │       └── FSWeb_Chart_DailyUL        → Performance sottostanti
    │
    ├──→ ChartSectionRenderer              → PDF Sezione 4 (SkiaSharp)
    │       └── SkiaChartRenderer          → Genera PNG in memoria
    │
    └──→ PdfMergerService                  → Unisce i PDF → byte[]
                                                │
                                                ▼
                                       Response PDF inline

2. Struttura del progetto

SmartReports/                                    ← Solution
└── CertReports.Syncfusion/                      ← Progetto
    ├── Controllers/
    │   ├── ReportController.cs                  ← API report PDF completo
    │   └── ChartController.cs                   ← API chart standalone (PNG/PDF)
    │
    ├── Helpers/
    │   ├── CryptoHelper.cs                      ← Decodifica ISIN (ENCRYPTBYPASSPHRASE)
    │   ├── GlobalExceptionMiddleware.cs         ← Error handling centralizzato
    │   ├── HealthChecks.cs                      ← Health check DB + Chart service
    │   └── PdfTheme.cs                          ← Colori, font, stili, bordi (tema centralizzato)
    │
    ├── Models/
    │   ├── CertificateModels.cs                 ← Modelli report (anagrafica, eventi, scenario)
    │   └── ChartModels.cs                       ← Modelli grafico (serie, punti, barriere)
    │
    ├── Services/
    │   ├── Interfaces/
    │   │   └── IServices.cs                     ← Contratti DI per tutti i servizi
    │   └── Implementations/
    │       ├── CertificateDataService.cs        ← Accesso DB: anagrafica, eventi, scenario
    │       ├── ChartDataService.cs              ← Accesso DB: dati grafico (3 SP)
    │       ├── AnagraficaSectionRenderer.cs        ← Sezione 1: anagrafica + sottostanti (in quotazione)
    │       ├── ExpiredAnagraficaSectionRenderer.cs ← Sezione 1: anagrafica + sottostanti (scaduti)
    │       ├── DividendSectionRenderer.cs          ← Sezione 1b: tabella Sottostanti+Dividendi landscape (opzionale)
    │       ├── EventiSectionRenderer.cs            ← Sezione 2: tabella eventi (multi-pagina)
    │       ├── ScenarioSectionRenderer.cs          ← Sezione 3: matrice scenario con gradiente
    │       ├── ChartSectionRenderer.cs             ← Sezione 4: inserisce chart PNG nel PDF
    │       ├── SkiaChartRenderer.cs                ← Genera il grafico con SkiaSharp
    │       ├── PdfMergerService.cs                 ← Merge PDF con Syncfusion
    │       ├── PdfCacheService.cs                  ← Cache in memoria (IMemoryCache)
    │       └── ReportOrchestrator.cs               ← Coordinatore principale
    │
    ├── Program.cs                               ← Entry point, DI, middleware
    ├── GlobalUsings.cs                          ← Using globali
    ├── CertReports.Syncfusion.csproj            ← Progetto + pacchetti NuGet
    ├── appsettings.json                         ← Configurazione produzione
    ├── appsettings.Development.json             ← Override per sviluppo
    ├── Dockerfile                               ← Build containerizzata
    ├── docker-compose.yml                       ← Compose con SQL Server
    └── .gitignore

3. Mapping vecchio → nuovo

Vecchio (DevExpress / WebForms) Nuovo (Syncfusion / ASP.NET Core 8)
ReportFSSiteCrypt.aspx (Page_Load) ReportController.cs (API REST)
XtraReport + file .repx Classi IPdfSectionRenderer (codice C#)
report.ExportToPdf() per ogni sezione Ogni renderer restituisce PdfDocument
PdfSharp.PdfReader.Open + CopyPages PdfMergerService (Syncfusion)
CommonClass.DecryptCombined() CryptoHelper.DecryptIsin()
CommonClass.execSP_Scalar() CertificateDataService (async)
ChartFSWeb.aspx (DevExpress WebChart) SkiaChartRenderer + ChartController
CallWebApi() con WebRequest Generazione interna (nessuna chiamata HTTP)
File temporanei su disco + File.Delete Tutto in memoria (MemoryStream)
Response.BinaryWrite() return File(bytes, "application/pdf")
System.Data.SqlClient Microsoft.Data.SqlClient v5

4. Stored procedures utilizzate

Tutte le SP sono nel database FirstSolutionDB.

Report (sezioni 1-3)

SP Parametro Sezione Descrizione
rpt_Master_CFT_ISIN @ISIN varchar(12) 1 — Anagrafica Dati certificato (1 record). Campi pre-formattati con FORMAT(). Join su Issuer, Category, BasketType, BarrierType + numerose OUTER APPLY per valori calcolati.
rpt_Details_UL_ISIN @ISIN varchar(12) 1 — Sottostanti N record sottostanti con strike, last price, barriere, buffer, dividendi. Campi formattati con FORMAT(...,'N3','it-IT').
rpt_Events_CFT_ISIN @ISIN varchar(50) 2 — Eventi Lista eventi ordinata per data. Cedole, trigger, autocall, importi pagati. Campi formattati. Colonne Trigger Capitale e Valore Capitale escluse dal report.
rpt_AnalisiScenario_ISIN @ISIN varchar(50) 3 — Scenario 7 righe UNION con label in Descrizione e 13 colonne di valori (col1..col13). Variazioni: -90%, -80%, -70%, -60%, -50%, -40%, -30%, -20%, -10%, 0%, +10%, +20%, +30%. Tutte stringhe pre-formattate.
rpt_FindIsinbyAliasID @AliasID Risoluzione Restituisce l'ISIN dato un alias ID.

Grafico (sezione 4)

SP Parametri Descrizione
FSWeb_Chart_UL @isin varchar(15) Info sottostanti per il grafico: IDCertificates, IDUnderlyings, StartDate, Strike, BarrieraCoupon, BarrieraCapitale, Nome. Restituisce dati solo se ci sono almeno 30 prezzi EOD.
FSWeb_Chart_DailyCTF @ISIN varchar(50) Performance giornaliera del certificato: Px_date, Performance (= PX_LAST_EOD / Nominal_amount × 100).
FSWeb_Chart_DailyUL @IDCertificates, @IDUnderlyings, @StartDate, @Strike Performance giornaliera di ogni sottostante: Px_date, Performance (= Px_close / Strike × 100). Usa Px_closeadj se AdjustedPrices=1. Filtra per date presenti nel certificato.

Nota sui dati formattati

Le SP restituiscono la maggior parte dei valori già formattati come stringhe (es. FORMAT(value,'P2','it-IT'), FORMAT(value,'F2','it-IT')). I modelli C# usano string per questi campi per evitare parsing e riformattazione non necessari. Solo NominalValue, PrezzoEmissione, CpnPagati, CpnDaPagare, CpnInMemoria sono decimal? perché servono come valori numerici nel rendering.


5. Endpoint API

Generazione report PDF completo (inline nel browser)

GET /api/report?p={isin_cifrato}
GET /api/report?alias={alias_id}
GET /api/report/by-isin/{isin}

Download report come allegato

GET /api/report/download?p={isin_cifrato}
GET /api/report/download?alias={alias_id}

Parametri opzionali (tutti gli endpoint report)

Parametro Default Effetto
?branding=true false Aggiunge footer "Powered by Smart Roots" con hyperlink cliccabile
?dividend=true false Inserisce pagina landscape Sottostanti+Dividendi dopo la Sezione 1; omette la tabella sottostanti dalla Sezione 1

I parametri sono combinabili: ?branding=true&dividend=true. La cache usa 4 chiavi distinte: {isin}, {isin}:branded, {isin}:dividend, {isin}:branded:dividend.

Grafico standalone (richiamabile da altri progetti)

GET /api/chart/{isin}                              → PNG inline
GET /api/chart/{isin}?format=png                   → PNG inline
GET /api/chart/{isin}?format=pdf                   → PDF inline
GET /api/chart/{isin}?width=1200&height=800        → Dimensioni custom

Parametri opzionali: width (400-2000, default 1100), height (300-1500, default 700), format (png/pdf, default png).

Health check

GET /health

6. Configurazione

appsettings.json

{
  "ConnectionStrings": {
    "CertDb": "Data Source=INDIRIZZO_SERVER;Initial Catalog=FirstSolutionDB;Persist Security Info=True;User ID=sa;Password=***;TrustServerCertificate=True;Encrypt=False;"
  },
  "Syncfusion": {
    "LicenseKey": "CHIAVE_COMMUNITY_SYNCFUSION"
  },
  "CryptoSettings": {
    "Passphrase": "ddCE3hM9BNJXgwtj"
  },
  "ReportSettings": {
    "CacheMinutes": 5
  }
}

La sezione ChartService non è più necessaria: il grafico viene generato internamente.

Note sulla connection string

  • Usare Data Source (non Server) e Initial Catalog (non Database) per compatibilità con Microsoft.Data.SqlClient v5
  • Aggiungere Encrypt=False; se il SQL Server non ha SSL configurato
  • Se la connessione non funziona via TCP, aggiungere il prefisso tcp: all'indirizzo (es. Data Source=tcp:26.69.45.60;)

appsettings.Development.json

Non deve contenere ConnectionStrings — altrimenti sovrascrive i valori di appsettings.json. Contiene solo override per logging e cache in sviluppo.


7. Pacchetti NuGet

Pacchetto Versione Scopo
Syncfusion.Pdf.Net.Core 33.x Generazione e merge PDF
Microsoft.Data.SqlClient 5.x Accesso SQL Server
SkiaSharp 2.x Generazione grafico server-side
Serilog.AspNetCore 8.x Logging strutturato
Serilog.Sinks.File 5.x Log su file con rotazione giornaliera

Note

  • Syncfusion.Drawing.Net.Core non esiste più nella v33: è inglobato in Syncfusion.Pdf.Net.Core
  • Per i colori usare Color.FromArgb(255, r, g, b) invece di new Color(r, g, b) (cambiamento v33)
  • PdfStandardFont non è IDisposable in Syncfusion v33: non usare using nella dichiarazione
  • SkiaSharp v2.x: usare SKFont per dimensione/typeface e canvas.DrawText(text, x, y, SKTextAlign, font, paint) — le API su SKPaint sono obsolete

8. Architettura e pattern

Dependency Injection

Tutti i servizi sono registrati in Program.cs:

  • ICertificateDataServiceCertificateDataService (Scoped)
  • IChartDataServiceChartDataService (Scoped)
  • IPdfSectionRenderer → registrati multipli: Anagrafica, Eventi, Scenario (Scoped)
  • ExpiredAnagraficaSectionRenderer (Scoped, iniettata direttamente nell'orchestratore — senza interfaccia)
  • DividendSectionRenderer (Scoped, iniettata direttamente nell'orchestratore — senza interfaccia)
  • IChartSectionRendererChartSectionRenderer (Scoped)
  • IPdfMergerServicePdfMergerService (Scoped)
  • IReportOrchestratorReportOrchestrator (Scoped)
  • IPdfCacheServicePdfCacheService (Singleton)
  • CryptoHelper (Singleton)

Aggiungere una nuova sezione al report

  1. Creare una classe che implementa IPdfSectionRenderer
  2. Assegnare SectionName e Order (determina la posizione nel PDF)
  3. Implementare Render(CertificateReportData data) che restituisce un PdfDocument
  4. Registrarla in Program.cs: builder.Services.AddScoped<IPdfSectionRenderer, NuovaSezione>();

L'orchestratore la includerà automaticamente nel report, ordinata per Order.

Tema e stili

PdfTheme.cs centralizza:

  • Colori: HeaderBackground, AlternateRow, PositiveValue, NegativeValue, ecc.
  • Font: Regular (8pt), Bold, Small (6.5pt), Title (14pt), Header (7pt bold)
  • Layout: PageMargin (40pt), RowHeight (18pt), CellPadding (4pt)
  • Brushes e Pens: pronti all'uso, derivati dai colori
  • Bordi: ApplyThinBorders(grid, thickness) per linee tabella sottili e uniformi

Modificare un valore in PdfTheme aggiorna tutti i renderer automaticamente.

Bordi tabelle

Tutte le tabelle PdfGrid usano PdfTheme.ApplyThinBorders(grid) prima del Draw() per ottenere linee sottili e uniformi (0.25pt di default). Il metodo itera su headers e righe applicando PdfBorders con PdfPen del colore e spessore definiti nel tema.

Cache

I PDF generati vengono memorizzati in IMemoryCache per CacheMinutes (default 5 minuti, configurabile). Evita rigenerazioni per ricaricamenti di pagina. La cache ha un limite di 200 MB totali.

Gestione errori

GlobalExceptionMiddleware cattura tutte le eccezioni non gestite e restituisce risposte JSON coerenti con status code appropriati (400, 404, 408, 500).


9. Sezione grafico (Sezione 4)

Il grafico viene generato interamente in memoria con SkiaSharp, eliminando la dipendenza dall'endpoint esterno ChartFSWeb.aspx.

Architettura

ChartSectionRenderer
    │
    ├──→ ChartDataService         ← Recupera dati da DB (3 SP)
    │
    └──→ SkiaChartRenderer        ← Genera PNG con SkiaSharp
            │
            └──→ PNG in memoria → inserito nel PDF come PdfBitmap

Cosa disegna il grafico

  • Serie certificato: linea nera, spessore 2.5px (performance % nel tempo)
  • Serie sottostanti: N linee colorate, spessore 1.5px (ciascuna con colore diverso dal palette ciclico)
  • Barriera Capitale: linea orizzontale rossa
  • Barriera Coupon: linea orizzontale viola (solo se diversa da Barriera Capitale)
  • Strike (100%): linea orizzontale verde
  • Asse Y: percentuale con auto-scaling e margine 10%
  • Asse X: date con step automatico (trimestrale/semestrale/annuale)
  • Legenda: box in alto a destra con tutte le serie e le linee costanti
  • Griglia: linee orizzontali e verticali grigio chiaro

API chart standalone

Il ChartController espone il grafico come API indipendente, richiamabile da qualsiasi altro progetto senza passare per il report completo.


10. Sezione scenario (Sezione 3)

Stile visivo

La sezione scenario replica lo stile del vecchio report DevExpress:

  • Header con gradiente colore: da rosso scuro (-90%) attraverso arancio e giallo fino a verde (+30%)
  • Testo header bianco su sfondo colorato
  • Titolo "Analisi Scenario" centrato, grande, colore blu
  • Righe alternate con sfondo grigio chiaro
  • Valori negativi in rosso scuro
  • 13 colonne di variazione: -90%, -80%, -70%, -60%, -50%, -40%, -30%, -20%, -10%, 0%, +10%, +20%, +30%

Logica di inclusione

La sezione scenario viene inclusa nel report se rpt_AnalisiScenario_ISIN restituisce almeno una riga. La condizione è semplicemente scenario.Rows.Count > 0 (non controlla i valori, perché la SP restituisce anche "--" per dati non calcolabili).


11. Deploy

Locale (Visual Studio)

F5 → https://localhost:{porta}/api/report/by-isin/{ISIN}
F5 → https://localhost:{porta}/api/chart/{ISIN}

Docker

docker-compose up --build
# → http://localhost:5080/api/report/by-isin/{ISIN}
# → http://localhost:5080/api/chart/{ISIN}

Il Dockerfile include l'installazione dei font necessari per il rendering PDF e SkiaSharp (libfontconfig1, fonts-dejavu-core) e gira come utente non-root per sicurezza.


12. Logging

Serilog scrive su:

  • Console: tutti i log in tempo reale
  • File: Logs/certreports-{data}.log con rotazione giornaliera

Livelli configurabili in appsettings.json. In Development il livello di default è Debug, in Production è Information.

Ogni step del flusso è loggato:

[INF] Richiesta report per ISIN CH1277653163
[DBG] Cache MISS per ISIN CH1277653163
[INF] Inizio generazione report per ISIN CH1277653163
[INF] Anagrafica caricata per CH1277653163: Leonteq, 3 sottostanti
[INF] Eventi caricati per CH1277653163: 36 eventi
[INF] Scenario caricato per CH1277653163: 7 righe
[INF] Sezione 'Anagrafica' generata per CH1277653163
[INF] Sezione 'Eventi' generata per CH1277653163
[INF] Sezione 'Scenario' generata per CH1277653163
[INF] Dati grafico caricati per CH1277653163: 4 serie, certificato 650 punti
[INF] Grafico generato per CH1277653163: 82450 bytes PNG, 4 serie
[INF] Sezione Grafico aggiunta per CH1277653163
[INF] Report generato per CH1277653163: 125000 bytes, 4 sezioni
[DBG] Cache SET per CH1277653163 (125000 bytes, TTL 5min)

13. Problemi noti e soluzioni

Problema Causa Soluzione
Cannot access a closed Stream nel merge Gli stream dei PdfLoadedDocument venivano chiusi prima del Save() finale PdfMergerService tiene tutti gli stream aperti fino al salvataggio, poi fa cleanup nel finally
Color does not contain a constructor that takes 3 arguments Syncfusion v33 ha rimosso new Color(r,g,b) Usare Color.FromArgb(255, r, g, b)
Syncfusion.Drawing.Net.Core non trovato Pacchetto rimosso nella v33, inglobato in Syncfusion.Pdf.Net.Core Rimuovere dal .csproj
Unexpected character '{' in FormatPercent Interpolazione annidata non supportata in C# Usare value.Value.ToString($"N{decimals}") + " %"
Connessione DB via Named Pipes Microsoft.Data.SqlClient v5 tenta Named Pipes di default Aggiungere Encrypt=False; e/o prefisso tcp: nella connection string
Connection string ignorata appsettings.Development.json sovrascriveva appsettings.json Rimuovere ConnectionStrings da appsettings.Development.json
Scenario mai generato SP sbagliata (rpt_AnalisiRischio_ISIN con 11 colonne) e check troppo restrittivo Usare rpt_AnalisiScenario_ISIN (13 colonne, label in Descrizione), condizione Rows.Count > 0
Namespace conflict Syncfusion.Pdf in ChartController CertReports.Syncfusion.Pdf confuso con Syncfusion.Pdf Aggiungere using Syncfusion.Pdf; esplicito, non usare riferimenti fully-qualified
PdfStandardFont non è IDisposable Syncfusion v33 non implementa IDisposable su questo tipo Non usare using nella dichiarazione
SkiaSharp DrawText obsoleto SKPaint.TextSize/Typeface deprecati in SkiaSharp 2.x Usare SKFont + canvas.DrawText(text, x, y, SKTextAlign, font, paint)
Bordi tabelle troppo spessi PdfGrid usa bordi di default grossi PdfTheme.ApplyThinBorders(grid) con PdfPen a 0.25pt prima di Draw()
grid.Headers non iterabile con foreach Syncfusion v33 non espone IEnumerable sugli headers Usare for (int r = 0; r < grid.Headers.Count; r++) con accesso indicizzato
grid.Draw() restituisce void Syncfusion v33 ha rimosso il valore di ritorno Stimare altezza con (grid.Rows.Count + 1) * RowHeight

14. Cronologia modifiche

Data Modifica
18/03/2026 Creazione progetto iniziale con struttura ASP.NET Core 8 + Syncfusion
18/03/2026 Implementazione completa CryptoHelper.DecryptCombined (v1 TripleDES + v2 AES)
18/03/2026 Mapping SP reali: rpt_Master_CFT_ISIN, rpt_Details_UL_ISIN, rpt_Events_CFT_ISIN
18/03/2026 Fix compatibilità Syncfusion v33: Color.FromArgb, rimozione Syncfusion.Drawing.Net.Core
18/03/2026 Fix PdfMergerService: stream mantenuti aperti fino al Save() finale
18/03/2026 Fix connection string: Data Source + Encrypt=False per Microsoft.Data.SqlClient v5
18/03/2026 Fix appsettings.Development.json che sovrascriveva la connection string
18/03/2026 Prima generazione PDF riuscita con dati reali
18/03/2026 Porting grafico da DevExpress ChartFSWeb.aspx a SkiaSharp interno
18/03/2026 Aggiunta ChartDataService (SP: FSWeb_Chart_UL, FSWeb_Chart_DailyCTF, FSWeb_Chart_DailyUL)
18/03/2026 Aggiunta SkiaChartRenderer con serie, barriere, legenda, auto-scaling
18/03/2026 Aggiunta ChartController per API chart standalone (/api/chart/{isin})
18/03/2026 Fix scenario: SP corretta rpt_AnalisiScenario_ISIN con 13 colonne e label Descrizione
18/03/2026 Scenario: header con gradiente rosso→verde, valori negativi in rosso
18/03/2026 Rimozione colonne Trigger Capitale e Valore Capitale dalla tabella eventi
18/03/2026 Fix bordi tabelle: PdfTheme.ApplyThinBorders() con linee 0.25pt
18/03/2026 Fix SkiaSharp: migrazione a API moderna SKFont (rimozione warning obsoleti)
23/03/2026 Aggiunto parametro ?dividend=true su tutti gli endpoint report
23/03/2026 Aggiunto DividendSectionRenderer: pagina landscape con tabella Sottostanti+Dividendi (header raggruppati SOTTOSTANTE / BARRIERE / DIVIDENDI)
23/03/2026 Quando dividend=true: tabella Sottostanti omessa dalla Sezione 1 (entrambi i flussi attivo/expired)
23/03/2026 Cache aggiornata a 4 chiavi: {isin}, {isin}:branded, {isin}:dividend, {isin}:branded:dividend
23/03/2026 Label italianizzate uniformemente: Strike→Livello Iniziale, Buffer→Protezione, Trigger→Livello (Cedola/Richiamo Anticipato), IRR→Tasso Rendimento Interno
23/03/2026 Stile titoli sezioni uniformato (DrawSectionLabel): barra blu 3pt + testo AccentBlue su bianco (Events, Scenario, Dividend)
23/03/2026 Font tabelle Sottostanti allineati a EventiSectionRenderer: PdfTheme.Header per header, PdfTheme.Small per dati