Files
SmartReports/docs/superpowers/specs/2026-03-21-expired-certificate-report-design.md

198 lines
7.5 KiB
Markdown

# 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<IPdfSectionRenderer>`) 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<ExpiredAnagraficaSectionRenderer>();
```
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