chore: initial commit - baseline before redesign

This commit is contained in:
2026-03-20 12:03:38 +01:00
commit 85ee66750a
31 changed files with 3426 additions and 0 deletions

View File

@@ -0,0 +1,426 @@
# 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
├──→ 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
│ ├── 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}
```
### 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)
- `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) |