8.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project
SmartReports / CertReports.Syncfusion — ASP.NET Core 8 REST API che genera PDF di certificati finanziari strutturati, sostituendo il vecchio progetto WebForms DevExpress. Usa Syncfusion PDF (Community License) e SkiaSharp per i grafici.
Progetto unico nella solution: CertReports.Syncfusion/.
Build & Run
# Restore + build
dotnet restore
dotnet build
# Run locale → https://localhost:{porta}/api/report/by-isin/{ISIN}
dotnet run --project CertReports.Syncfusion
# Docker
docker-compose up --build
# → http://localhost:5080/api/report/by-isin/{ISIN}
Non esistono test automatici al momento.
Architettura
Il flusso di generazione è orchestrato da ReportOrchestrator, che sceglie il template in base al campo Stato del certificato:
HTTP (ISIN) → ReportController → ReportOrchestrator
├── CertificateDataService (4 SP → anagrafica, sottostanti, eventi, scenario)
│
├── [Stato == "Quotazione"] — report certificati in quotazione (4 sezioni)
│ ├── AnagraficaSectionRenderer → PdfDocument (Sezione 1)
│ ├── EventiSectionRenderer → PdfDocument (Sezione 2)
│ ├── ScenarioSectionRenderer → PdfDocument (Sezione 3, opzionale)
│ └── ChartSectionRenderer → PdfDocument (Sezione 4)
│
├── [Stato != "Quotazione"] — report certificati non in quotazione (3 sezioni)
│ ├── ExpiredAnagraficaSectionRenderer → PdfDocument (Sezione 1)
│ ├── EventiSectionRenderer → PdfDocument (Sezione 2, colonne adattate)
│ └── ChartSectionRenderer → PdfDocument (Sezione 3)
│
├── ChartDataService (3 SP → dati grafico)
├── SkiaChartRenderer → PNG in memoria
└── PdfMergerService → byte[] → Response
Stato certificato: il campo Stato viene dalla SP rpt_Master_CFT_ISIN. Valori: "Quotazione" (report attivo), "Scaduto" / "Rimborsato" / "Revocato" (report expired). Se Stato è vuoto, viene usato il flusso attivo come fallback.
Aggiungere una nuova sezione PDF al flusso attivo: implementare IPdfSectionRenderer, impostare SectionName e Order, registrare in Program.cs come AddScoped<IPdfSectionRenderer, NuovaSezione>(). L'orchestratore la include automaticamente ordinandola per Order.
ExpiredAnagraficaSectionRenderer: registrata come AddScoped<ExpiredAnagraficaSectionRenderer>() (senza interfaccia) e iniettata direttamente nell'orchestratore — non partecipa al ciclo IEnumerable<IPdfSectionRenderer>.
Tema: tutto in PdfTheme.cs — colori, font, layout, brush/pen. Modificare lì per aggiornare tutti i renderer.
Palette colori principale (stile "Ibrido elegante"):
AccentBlue#1565C0— titoli, header tabelle, valori chiaveNegativeRed#CC0000— valori negativi (performance, rendimento)PositiveGreen#2E7D32— valori positivi (performance)- Brush corrispondenti:
AccentBlueBrush,NegativeRedBrush,PositiveGreenBrush,TableHeaderBrush(alias diAccentBlueBrush)
Configurazione chiave
appsettings.json: connection string (CertDb), Syncfusion license key, CryptoSettings passphrase, cache TTLappsettings.Development.json: non deve contenereConnectionStrings, altrimenti sovrascriveappsettings.json- Connection string: usare
Data Source+Initial Catalog+Encrypt=False;perMicrosoft.Data.SqlClientv5 - Se la connessione fallisce via Named Pipes, aggiungere prefisso
tcp:all'indirizzo IP
Gotcha Syncfusion v33 & SkiaSharp
| Problema | Soluzione |
|---|---|
Color(r,g,b) rimosso |
Color.FromArgb(255, r, g, b) |
Syncfusion.Drawing.Net.Core non esiste |
Inglobato in Syncfusion.Pdf.Net.Core, rimuovere dal .csproj |
PdfStandardFont non è IDisposable |
Non usare using sulla dichiarazione |
grid.Headers non iterabile con foreach |
for (int r = 0; r < grid.Headers.Count; r++) |
PdfTextWebLink non trovato |
Aggiungere using Syncfusion.Pdf.Interactive; |
RectangleF non supporta named arguments |
new RectangleF(x, y, w, h) — no startY: o simili |
grid.Draw() restituisce void |
Stimare altezza con (rows + 1) * RowHeight |
PdfMergerService: stream chiuso prima del Save() |
Tenere tutti gli stream aperti fino al salvataggio finale, poi cleanup in finally |
SkiaSharp DrawText obsoleto |
SKFont + canvas.DrawText(text, x, y, SKTextAlign, font, paint) |
Namespace conflict CertReports.Syncfusion.Pdf |
Aggiungere using Syncfusion.Pdf; esplicito nel file |
Database
Tutte le stored procedure sono su FirstSolutionDB. I dati tornano già formattati come stringhe dalle SP (es. FORMAT(value,'P2','it-IT')): i modelli C# usano string per questi campi. Solo NominalValue, PrezzoEmissione, CpnPagati, CpnDaPagare, CpnInMemoria sono decimal? perché servono come valori numerici nel rendering.
API Endpoints
GET /api/report?p={isin_cifrato}/?alias={id}//by-isin/{isin}— PDF inlineGET /api/report/download?p={...}/?alias={...}— PDF come allegato- Tutti gli endpoint report accettano
?branding=true(defaultfalse) per abilitare il footer "Powered by Smart Roots" - Tutti gli endpoint report accettano
?dividend=true(defaultfalse) per aggiungere la pagina Sottostanti+Dividendi - Tutti gli endpoint report accettano
?natixis=true(defaultfalse) per mostrareinfo.Nomenel box Tipologia invece diinfo.Categoria GET /api/chart/{isin}[?format=png|pdf&width=&height=]— grafico standaloneGET /health— health check DB + chart service
Footer branding
Il parametro ?branding=true aggiunge al footer di ogni pagina del report:
- Sinistra:
"Powered by "+ hyperlink PDF cliccabile"Smart Roots"→https://www.smart-roots.net - Destra: numero pagina
Con branding=false (default): solo numero pagina centrato.
La cache usa chiavi separate: {isin}:branded vs {isin}. Il flag è propagato come CertificateReportData.ShowBranding e passato a PdfTheme.DrawFooter(g, pageWidth, pageHeight, pageNumber, showBranding). L'hyperlink usa PdfTextWebLink (richiede using Syncfusion.Pdf.Interactive;).
Parametri query report
Tutti i flag booleani hanno default false e seguono lo stesso pattern di propagazione: ReportController → IReportOrchestrator.GenerateReportAsync → CertificateReportData → renderer.
| Parametro | Flag su CertificateReportData |
Effetto |
|---|---|---|
?branding=true |
ShowBranding |
Footer con "Powered by Smart Roots" + link |
?dividend=true |
ShowDividend |
Aggiunge pagina Sottostanti+Dividendi; nasconde tabella Sottostanti dalla Sezione 1 |
?natixis=true |
ShowNatixis |
Box Tipologia mostra info.Nome invece di info.Categoria |
Cache key pattern (Approccio A — suffissi concatenati):
- Suffissi:
:branded,:dividend,:natixis— aggiunti in questo ordine se il flag ètrue - Chiave base:
{isin}[suffissi] - Chiave expired:
{isin}:expired[suffissi] - Esempio:
?branding=true&natixis=true→ chiave{isin}:branded:natixis
Sezione 1 — AnagraficaSectionRenderer
Struttura a 3 sezioni verticali in una singola pagina A4:
| Sezione | Contenuto |
|---|---|
| Titolo | "Scheda Prodotto {ISIN}" blu + Bid/Ask a destra + linea separatrice blu |
| A — Caratteristiche Prodotto | Tabella emittente a sinistra (ISIN, Mercato, Valuta, Date, Autocall) + tabella cedole a destra (CpnPagati/DaPagare/InMemoria, RendimentoAttuale) |
| B — Analisi | 9 KV a sinistra + 9 KV a destra. Leva/Memoria/FattoreAirbag/TriggerOneStar mostrati come "—" anche se vuoti |
| C — Sottostanti | PdfGrid 9 colonne: Nome, Strike, Last, % Perf., Barr.K, Buffer K, Trig.CPN, Buf.CPN, Trig.AC (Dist.AC rimossa) |
Se la tabella Sottostanti non entra (y > PageH-80pt), si crea una nuova pagina. Il footer viene disegnato su ogni pagina con PdfTheme.DrawFooter.