# Design Spec: Report Certificati Non in Quotazione (Expired) **Data:** 2026-03-21 **Progetto:** SmartReports / CertReports.Syncfusion **Autore:** Brainstorming session --- ## Obiettivo Implementare un secondo template di report PDF per certificati non più in quotazione (Stato: Revocato, Scaduto, Rimborsato). Il report mantiene lo stesso stile grafico del report attivo ma mostra meno informazioni: 3 pagine totali (Anagrafica semplificata + Lista Eventi + Grafico). Niente Scenario. Il rilevamento del tipo è automatico tramite il campo `Stato` restituito dalla SP esistente `rpt_Master_CFT_ISIN`. --- ## Struttura del Report Expired (3 pagine) | Pagina | Renderer | Contenuto | |--------|----------|-----------| | 1 | `ExpiredAnagraficaSectionRenderer` (nuovo) | Titolo + Caratteristiche + Analisi | | 2 | `EventiSectionRenderer` (riusato) | Lista eventi (identica all'attuale) | | 3 | `ChartSectionRenderer` (riusato) | Grafico performance (identico all'attuale) | --- ## Modifiche al Modello ### `CertificateInfo` — aggiungere un campo ```csharp public string Stato { get; set; } = string.Empty; // Valori possibili: "Quotazione" | "Revocato" | "Scaduto" | "Rimborsato" ``` ### `CertificateDataService` — mappare il nuovo campo Nel metodo che chiama `rpt_Master_CFT_ISIN`, aggiungere la mappatura: ```csharp Stato = reader["Stato"]?.ToString() ?? string.Empty ``` Nessuna altra modifica alle query o agli altri servizi dati. --- ## Nuovo Renderer: `ExpiredAnagraficaSectionRenderer` **File:** `CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs` **Interfaccia:** `IPdfSectionRenderer` **Order:** 1 **SectionName:** `"ExpiredAnagrafica"` ### Layout Pagina 1 (Portrait A4) #### Blocco Titolo - "Scheda Prodotto {ISIN}" — stesso stile del titolo attuale (font grande, colore AccentBlue) - Riga sotto: "Tipologia: {Categoria}" — font normale - **Niente** Bid/Ask, niente LastPriceDate - Linea separatrice orizzontale blu (come attuale) #### Blocco Caratteristiche Prodotto (tabella sinistra, stessa grafica attuale) | Label | Campo modello | |-------|---------------| | EMITTENTE | `Info.Emittente` | | ISIN | `Info.Isin` | | Mercato | `Info.Mercato` | | Valuta | `Info.Valuta` | | Data Emissione | `Info.DataEmissione` | | Valore Rimborso | `Info.ValoreRimborso` | | Data Rimborso | `Info.DataRimborso` | #### Blocco Analisi (KV, stessa grafica attuale) | Label | Campo modello | |-------|---------------| | Importo Cedola (p.a.) | `Info.NominalAnnualYield` | | Frequenza Cedola | `Info.FrequenzaCedole` | | Valore Nominale | `Info.NominalValue` | | Memoria Cedola | `Info.Memory` | | Tipo Barriera | `Info.BarrierType` | | Tipo Basket | `Info.BasketType` | | Rendimento Totale | `Info.RendimentoTotale` | #### Escluso dalla pagina 1 - Sezione Bid/Ask/LastPriceDate - Tabella Sottostanti (sezione C del report attivo) - Sezione cedole (CpnPagati/DaPagare/InMemoria) - Sezione Analisi (8+9 KV del report attivo) ### Footer `PdfTheme.DrawFooter(g, pageWidth, pageHeight, pageNumber, showBranding)` — identico a tutti gli altri renderer, supporta il parametro branding. --- ## Modifiche all'Orchestratore ### `ReportOrchestrator.cs` Dopo il caricamento dei dati (`CertificateDataService`), leggere `data.Info.Stato` per scegliere il flusso: **Strategia isExpired — denylist:** il check usa `!= "Quotazione"` (denylist), così qualsiasi nuovo stato non previsto viene trattato come expired piuttosto che come attivo. Se in futuro si aggiungono stati con report differenti, si sostituirà con un allowlist esplicito. ```csharp bool isExpired = !string.IsNullOrEmpty(data.Info.Stato) && data.Info.Stato != "Quotazione"; // Copre: "Revocato", "Scaduto", "Rimborsato" — e qualsiasi stato futuro non-attivo if (isExpired) { // Flusso expired: ExpiredAnagrafica + Eventi + Chart var eventiRenderer = _sectionRenderers.First(r => r.SectionName == "Eventi"); pdfSections.Add(_expiredAnagraficaRenderer.Render(data)); pdfSections.Add(eventiRenderer.Render(data)); // Chart: await _chartRenderer.RenderAsync(data.Info.Isin) — aggiunto se non null } else { // Flusso attuale: foreach _sectionRenderers.OrderBy(r => r.Order) — logica esistente invariata } ``` **Come si ottiene EventiSectionRenderer nel flusso expired:** tramite `_sectionRenderers.First(r => r.SectionName == "Eventi")`. È già registrato come `IPdfSectionRenderer` e presente nella collection iniettata. **Firma ChartRenderer:** `await _chartRenderer.RenderAsync(data.Info.Isin)` — stessa chiamata del flusso attivo, restituisce `PdfDocument?` (null se il grafico non è disponibile). Il renderer `ExpiredAnagraficaSectionRenderer` è iniettato direttamente nell'orchestratore (non via `IEnumerable`) per evitare che venga incluso nel ciclo del flusso normale. ### Cache — chiavi separate Le chiavi sono passate a `_cache.Get/Set` a livello orchestratore; `PdfCacheService.CacheKey()` aggiunge poi il prefisso `report_pdf_` internamente (comportamento invariato per entrambi i flussi). | Scenario | Chiave passata all'orchestratore | Chiave effettiva in memoria | |----------|----------------------------------|-----------------------------| | Attivo, no branding | `{isin}` | `report_pdf_{isin}` | | Attivo, branding | `{isin}:branded` | `report_pdf_{isin}:branded` | | Expired, no branding | `{isin}:expired` | `report_pdf_{isin}:expired` | | Expired, branding | `{isin}:expired:branded` | `report_pdf_{isin}:expired:branded` | --- ## Branding Il parametro `?branding=true` funziona identicamente al report attivo: - Propagato come `CertificateReportData.ShowBranding` - Passato a `PdfTheme.DrawFooter(..., showBranding)` in ogni pagina del report expired - Footer sinistra: "Powered by " + hyperlink "Smart Roots" → `https://www.smart-roots.net` - Footer destra: numero pagina - Cache usa chiave `{isin}:expired:branded` vs `{isin}:expired` --- ## Dependency Injection — `Program.cs` ```csharp // Registrazione diretta (non come IPdfSectionRenderer) per evitare inclusione nel ciclo normale builder.Services.AddScoped(); ``` Gli altri renderer esistenti rimangono invariati. --- ## Endpoint **Nessun nuovo endpoint.** Il rilevamento del tipo report è trasparente al chiamante. Tutti gli endpoint esistenti funzionano per entrambi i tipi: | Endpoint | Comportamento | |----------|---------------| | `GET /api/report?p={encrypted}` | Auto-rileva da Stato | | `GET /api/report?alias={id}` | Auto-rileva da Stato | | `GET /api/report/by-isin/{isin}` | Auto-rileva da Stato | | `GET /api/report/download?p={...}` | Auto-rileva da Stato | | `GET /api/report/download?alias={...}` | Auto-rileva da Stato | | Tutti | Supportano `?branding=true` | --- ## File Modificati / Creati | File | Tipo modifica | |------|---------------| | `Models/CertificateModels.cs` | Aggiunta campo `Stato` a `CertificateInfo` | | `Services/Implementations/CertificateDataService.cs` | Mappatura campo `Stato` dalla SP | | `Services/Implementations/ExpiredAnagraficaSectionRenderer.cs` | **Nuovo file** | | `Services/Implementations/ReportOrchestrator.cs` | Logica branching expired/attivo + cache keys | | `Program.cs` | Registrazione `ExpiredAnagraficaSectionRenderer` | --- ## Non modificati - `AnagraficaSectionRenderer.cs` — invariato - `EventiSectionRenderer.cs` — riusato as-is - `ScenarioSectionRenderer.cs` — invariato (semplicemente non incluso nel flusso expired) - `ChartSectionRenderer.cs` — riusato as-is - `PdfTheme.cs` — invariato - `ReportController.cs` — invariato - Tutti gli endpoint — invariati