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

448 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```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`:
- `ICertificateDataService``CertificateDataService` (Scoped)
- `IChartDataService``ChartDataService` (Scoped)
- `IPdfSectionRenderer` → registrati multipli: `Anagrafica`, `Eventi`, `Scenario` (Scoped)
- `ExpiredAnagraficaSectionRenderer` (Scoped, iniettata direttamente nell'orchestratore — senza interfaccia)
- `DividendSectionRenderer` (Scoped, iniettata direttamente nell'orchestratore — senza interfaccia)
- `IChartSectionRenderer``ChartSectionRenderer` (Scoped)
- `IPdfMergerService``PdfMergerService` (Scoped)
- `IReportOrchestrator``ReportOrchestrator` (Scoped)
- `IPdfCacheService``PdfCacheService` (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
```bash
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 |