790 lines
26 KiB
Markdown
790 lines
26 KiB
Markdown
# Dividend Section Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Aggiungere il parametro `?dividend=true` che sostituisce la tabella Sottostanti nelle sezioni Anagrafica con una pagina landscape dedicata Sottostanti+Dividendi.
|
|
|
|
**Architecture:** Il flag `ShowDividend` viene aggiunto a `CertificateReportData` e propagato attraverso controller → orchestratore → renderer. `DividendSectionRenderer` è un renderer diretto (non `IPdfSectionRenderer`) iniettato nell'orchestratore come già avviene per `ExpiredAnagraficaSectionRenderer`. Entrambi i flussi (attivo + expired) ricevono la pagina landscape subito dopo la Sezione 1. Le chiavi cache vengono estese a 4 combinazioni.
|
|
|
|
**Tech Stack:** ASP.NET Core 8, Syncfusion PDF Net.Core v33, SkiaSharp, Microsoft.Data.SqlClient
|
|
|
|
---
|
|
|
|
## File Map
|
|
|
|
| File | Azione |
|
|
|------|--------|
|
|
| `Models/CertificateModels.cs` | Modify — aggiungere `ShowDividend` |
|
|
| `Services/Interfaces/IServices.cs` | Modify — aggiungere `showDividend` a `IReportOrchestrator` |
|
|
| `Controllers/ReportController.cs` | Modify — aggiungere param `dividend` a tutti e 3 gli endpoint |
|
|
| `Services/Implementations/ReportOrchestrator.cs` | Modify — signature, cache keys, inject renderer, inserire sezione |
|
|
| `Services/Implementations/AnagraficaSectionRenderer.cs` | Modify — skip SEZIONE C se `ShowDividend=true` |
|
|
| `Services/Implementations/ExpiredAnagraficaSectionRenderer.cs` | Modify — skip SEZIONE C se `ShowDividend=true` |
|
|
| `Services/Implementations/DividendSectionRenderer.cs` | Create — nuova pagina landscape con tabella a 2 livelli |
|
|
| `Program.cs` | Modify — registrare `DividendSectionRenderer` |
|
|
|
|
---
|
|
|
|
## Task 1: Aggiungere `ShowDividend` al modello
|
|
|
|
**Files:**
|
|
- Modify: `Models/CertificateModels.cs`
|
|
|
|
- [ ] **Step 1: Leggere il file corrente**
|
|
|
|
Aprire `Models/CertificateModels.cs` e individuare la classe `CertificateReportData`.
|
|
Il file contiene già `public bool ShowBranding { get; set; } = false;`.
|
|
|
|
- [ ] **Step 2: Aggiungere la proprietà**
|
|
|
|
Subito dopo `ShowBranding`:
|
|
```csharp
|
|
public bool ShowBranding { get; set; } = false;
|
|
public bool ShowDividend { get; set; } = false;
|
|
```
|
|
|
|
- [ ] **Step 3: Build**
|
|
|
|
```bash
|
|
dotnet build CertReports.Syncfusion
|
|
```
|
|
Expected: Build succeeded, 0 errors.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add CertReports.Syncfusion/Models/CertificateModels.cs
|
|
git commit -m "feat: add ShowDividend flag to CertificateReportData"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 2: Aggiornare l'interfaccia `IReportOrchestrator`
|
|
|
|
**Files:**
|
|
- Modify: `Services/Interfaces/IServices.cs`
|
|
|
|
- [ ] **Step 1: Leggere il file**
|
|
|
|
Trovare `IReportOrchestrator`. Attualmente:
|
|
```csharp
|
|
public interface IReportOrchestrator
|
|
{
|
|
Task<byte[]> GenerateReportAsync(string isin, bool showBranding = false);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Aggiungere il parametro**
|
|
|
|
```csharp
|
|
public interface IReportOrchestrator
|
|
{
|
|
Task<byte[]> GenerateReportAsync(string isin, bool showBranding = false, bool showDividend = false);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Build**
|
|
|
|
```bash
|
|
dotnet build CertReports.Syncfusion
|
|
```
|
|
Expected: errore di compilazione su `ReportOrchestrator` (firma non corrisponde) — normale, fix al Task 4.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add CertReports.Syncfusion/Services/Interfaces/IServices.cs
|
|
git commit -m "feat: add showDividend param to IReportOrchestrator interface"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 3: Aggiornare `ReportController` — parametro `dividend`
|
|
|
|
**Files:**
|
|
- Modify: `Controllers/ReportController.cs`
|
|
|
|
- [ ] **Step 1: Leggere il file**
|
|
|
|
Ci sono 3 endpoint che chiamano `_orchestrator.GenerateReportAsync(isin, showBranding)`:
|
|
- `GenerateReport` (GET `/api/report?p=...`)
|
|
- `GenerateReportByIsin` (GET `/api/report/by-isin/{isin}`)
|
|
- `DownloadReport` (GET `/api/report/download?p=...`)
|
|
|
|
- [ ] **Step 2: Modificare `GenerateReport`**
|
|
|
|
Aggiungere il parametro `dividend` e passarlo all'orchestratore:
|
|
```csharp
|
|
public async Task<IActionResult> GenerateReport(
|
|
[FromQuery(Name = "p")] string? encryptedIsin = null,
|
|
[FromQuery(Name = "alias")] string? aliasId = null,
|
|
[FromQuery(Name = "branding")] bool showBranding = false,
|
|
[FromQuery(Name = "dividend")] bool showDividend = false)
|
|
```
|
|
E nella chiamata:
|
|
```csharp
|
|
return await GenerateAndReturnPdf(isin, showBranding, showDividend);
|
|
```
|
|
|
|
- [ ] **Step 3: Modificare `GenerateReportByIsin`**
|
|
|
|
```csharp
|
|
public async Task<IActionResult> GenerateReportByIsin(
|
|
string isin,
|
|
[FromQuery(Name = "branding")] bool showBranding = false,
|
|
[FromQuery(Name = "dividend")] bool showDividend = false)
|
|
```
|
|
E nella chiamata:
|
|
```csharp
|
|
return await GenerateAndReturnPdf(isin, showBranding, showDividend);
|
|
```
|
|
|
|
- [ ] **Step 4: Modificare `DownloadReport`**
|
|
|
|
```csharp
|
|
public async Task<IActionResult> DownloadReport(
|
|
[FromQuery(Name = "p")] string? encryptedIsin = null,
|
|
[FromQuery(Name = "alias")] string? aliasId = null,
|
|
[FromQuery(Name = "branding")] bool showBranding = false,
|
|
[FromQuery(Name = "dividend")] bool showDividend = false)
|
|
```
|
|
Nella chiamata interna:
|
|
```csharp
|
|
var pdfBytes = await _orchestrator.GenerateReportAsync(isin, showBranding, showDividend);
|
|
```
|
|
|
|
- [ ] **Step 5: Aggiornare il metodo privato `GenerateAndReturnPdf`**
|
|
|
|
Trovare la firma di `GenerateAndReturnPdf` e aggiungere `bool showDividend = false`, poi passarla a `GenerateReportAsync`:
|
|
```csharp
|
|
private async Task<IActionResult> GenerateAndReturnPdf(string isin, bool showBranding = false, bool showDividend = false)
|
|
{
|
|
// ...
|
|
var pdfBytes = await _orchestrator.GenerateReportAsync(isin, showBranding, showDividend);
|
|
// ...
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Build**
|
|
|
|
```bash
|
|
dotnet build CertReports.Syncfusion
|
|
```
|
|
Expected: errore su `ReportOrchestrator` (non ancora aggiornato) — normale.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add CertReports.Syncfusion/Controllers/ReportController.cs
|
|
git commit -m "feat: add ?dividend query param to all report endpoints"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 4: Aggiornare `AnagraficaSectionRenderer` — skip SEZIONE C
|
|
|
|
**Files:**
|
|
- Modify: `Services/Implementations/AnagraficaSectionRenderer.cs`
|
|
|
|
- [ ] **Step 1: Leggere il file**
|
|
|
|
Trovare il blocco SEZIONE C. Attualmente:
|
|
```csharp
|
|
// ── SEZIONE C: SOTTOSTANTI ────────────────────────────────────
|
|
if (info.Sottostanti.Count > 0)
|
|
{
|
|
// Se lo spazio rimanente è meno di 80pt, nuova pagina
|
|
if (y > PageH - 80f)
|
|
{
|
|
PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding);
|
|
page = doc.Pages.Add();
|
|
g = page.Graphics;
|
|
y = 0f;
|
|
}
|
|
DrawSottostanti(g, info.Sottostanti, PageW, y);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Wrappare il blocco con la condizione**
|
|
|
|
```csharp
|
|
// ── SEZIONE C: SOTTOSTANTI ────────────────────────────────────
|
|
if (!data.ShowDividend && info.Sottostanti.Count > 0)
|
|
{
|
|
// Se lo spazio rimanente è meno di 80pt, nuova pagina
|
|
if (y > PageH - 80f)
|
|
{
|
|
PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding);
|
|
page = doc.Pages.Add();
|
|
g = page.Graphics;
|
|
y = 0f;
|
|
}
|
|
DrawSottostanti(g, info.Sottostanti, PageW, y);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Build**
|
|
|
|
```bash
|
|
dotnet build CertReports.Syncfusion
|
|
```
|
|
Expected: stesso errore su `ReportOrchestrator` — normale.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add CertReports.Syncfusion/Services/Implementations/AnagraficaSectionRenderer.cs
|
|
git commit -m "feat: skip sottostanti table in anagrafica when ShowDividend=true"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 5: Aggiornare `ExpiredAnagraficaSectionRenderer` — skip SEZIONE C
|
|
|
|
**Files:**
|
|
- Modify: `Services/Implementations/ExpiredAnagraficaSectionRenderer.cs`
|
|
|
|
- [ ] **Step 1: Leggere il file**
|
|
|
|
Trovare il blocco SEZIONE C (struttura identica ad `AnagraficaSectionRenderer`):
|
|
```csharp
|
|
// ── SEZIONE C: SOTTOSTANTI ────────────────────────────────────
|
|
if (info.Sottostanti.Count > 0)
|
|
{
|
|
if (y > PageH - 80f)
|
|
{
|
|
PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding);
|
|
page = doc.Pages.Add();
|
|
g = page.Graphics;
|
|
...
|
|
}
|
|
DrawSottostanti(g, info.Sottostanti, PageW, y);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Stessa modifica del Task 4**
|
|
|
|
```csharp
|
|
// ── SEZIONE C: SOTTOSTANTI ────────────────────────────────────
|
|
if (!data.ShowDividend && info.Sottostanti.Count > 0)
|
|
{
|
|
if (y > PageH - 80f)
|
|
{
|
|
PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding);
|
|
page = doc.Pages.Add();
|
|
g = page.Graphics;
|
|
...
|
|
}
|
|
DrawSottostanti(g, info.Sottostanti, PageW, y);
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 3: Build**
|
|
|
|
```bash
|
|
dotnet build CertReports.Syncfusion
|
|
```
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs
|
|
git commit -m "feat: skip sottostanti table in expired anagrafica when ShowDividend=true"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 6: Creare `DividendSectionRenderer`
|
|
|
|
**Files:**
|
|
- Create: `Services/Implementations/DividendSectionRenderer.cs`
|
|
|
|
- [ ] **Step 1: Creare il file con la struttura base**
|
|
|
|
```csharp
|
|
using CertReports.Syncfusion.Models;
|
|
using Syncfusion.Pdf;
|
|
using Syncfusion.Pdf.Graphics;
|
|
using Syncfusion.Pdf.Grid;
|
|
using System.Drawing;
|
|
|
|
namespace CertReports.Syncfusion.Services.Implementations;
|
|
|
|
/// <summary>
|
|
/// Genera la pagina landscape con la tabella unificata Sottostanti + Dividendi.
|
|
/// Non implementa IPdfSectionRenderer — iniettato direttamente nell'orchestratore.
|
|
/// </summary>
|
|
public class DividendSectionRenderer
|
|
{
|
|
// Larghezze colonne indicative (totale 742pt) — scalate proporzionalmente a runtime
|
|
private static readonly float[] ColWidths =
|
|
[
|
|
90f, // Nome
|
|
50f, // Strike
|
|
50f, // Last
|
|
46f, // % Perf.
|
|
50f, // Barr.Cap.
|
|
46f, // Buf.Cap.
|
|
50f, // Trig.CPN
|
|
46f, // Buf.CPN
|
|
46f, // Trig.AC
|
|
54f, // Data Stacco
|
|
54f, // Data Pag.
|
|
40f, // Importo
|
|
40f, // Rend.
|
|
40f, // Imp.Fut.
|
|
40f, // Rend.Fut.
|
|
];
|
|
|
|
private static readonly string[] Col2Headers =
|
|
[
|
|
"", "Strike", "Last", "% Perf.",
|
|
"Barr.Cap.", "Buf.Cap.", "Trig.CPN", "Buf.CPN", "Trig.AC",
|
|
"Data Stacco", "Data Pag.", "Importo", "Rend.", "Imp.Fut.", "Rend.Fut.",
|
|
];
|
|
|
|
// Indici colonne soggette a colore performance (0-based)
|
|
private static readonly HashSet<int> PerfCols = [3, 5, 7, 12, 14];
|
|
|
|
public PdfDocument Render(CertificateReportData data)
|
|
{
|
|
var doc = new PdfDocument();
|
|
doc.PageSettings.Orientation = PdfPageOrientation.Landscape;
|
|
doc.PageSettings.Size = PdfPageSize.A4;
|
|
|
|
var page = doc.Pages.Add();
|
|
var g = page.Graphics;
|
|
var size = page.GetClientSize();
|
|
|
|
float w = size.Width - 2 * PdfTheme.PageMargin;
|
|
float h = size.Height - 2 * PdfTheme.PageMargin - PdfTheme.FooterHeight;
|
|
|
|
float x0 = PdfTheme.PageMargin;
|
|
float y = PdfTheme.PageMargin;
|
|
|
|
// ── Titolo ────────────────────────────────────────────────────
|
|
var titleFont = PdfTheme.TitleFont;
|
|
g.DrawString("Sottostanti e Dividendi", titleFont, PdfTheme.AccentBlueBrush,
|
|
new RectangleF(x0, y, w, 20f));
|
|
y += 20f + 4f;
|
|
|
|
// ── Linea separatrice ─────────────────────────────────────────
|
|
g.DrawLine(PdfTheme.AccentBluePen, new PointF(x0, y), new PointF(x0 + w, y));
|
|
y += 6f;
|
|
|
|
// ── Calcolo scale fattore su larghezza reale ──────────────────
|
|
float totalNominal = ColWidths.Sum();
|
|
float scale = w / totalNominal;
|
|
float[] cw = ColWidths.Select(c => c * scale).ToArray();
|
|
|
|
// ── Disegno header a 2 livelli ────────────────────────────────
|
|
float rh = PdfTheme.RowHeight;
|
|
|
|
DrawHeader(g, x0, y, cw, rh);
|
|
|
|
y += rh * 2;
|
|
|
|
// ── Tabella dati ──────────────────────────────────────────────
|
|
DrawDataGrid(g, data, x0, y, cw, w);
|
|
|
|
// ── Footer ────────────────────────────────────────────────────
|
|
PdfTheme.DrawFooter(g, w, h, 1, data.ShowBranding);
|
|
|
|
return doc;
|
|
}
|
|
|
|
private static void DrawHeader(PdfGraphics g, float x0, float y, float[] cw, float rh)
|
|
{
|
|
var headerFont = PdfTheme.TableFont;
|
|
var whiteBrush = PdfBrushes.White;
|
|
var accentBrush = PdfTheme.AccentBlueBrush;
|
|
var darkBlueBrush = new PdfSolidBrush(Color.FromArgb(255, 10, 56, 128)); // #0A3880
|
|
|
|
// ── Riga 1: Gruppi ────────────────────────────────────────────
|
|
// "Nome" (col 0)
|
|
float cx = x0;
|
|
DrawHeaderCell(g, cx, y, cw[0], rh, "", accentBrush, whiteBrush, headerFont);
|
|
cx += cw[0];
|
|
|
|
// "SOTTOSTANTE" (cols 1-3)
|
|
float sottostanteW = cw[1] + cw[2] + cw[3];
|
|
DrawHeaderCell(g, cx, y, sottostanteW, rh, "SOTTOSTANTE", accentBrush, whiteBrush, headerFont);
|
|
cx += sottostanteW;
|
|
|
|
// "BARRIERE" (cols 4-8)
|
|
float barriereW = cw[4] + cw[5] + cw[6] + cw[7] + cw[8];
|
|
DrawHeaderCell(g, cx, y, barriereW, rh, "BARRIERE", accentBrush, whiteBrush, headerFont);
|
|
cx += barriereW;
|
|
|
|
// "DIVIDENDI" (cols 9-14) — blu scuro
|
|
float dividendiW = cw[9] + cw[10] + cw[11] + cw[12] + cw[13] + cw[14];
|
|
DrawHeaderCell(g, cx, y, dividendiW, rh, "DIVIDENDI", darkBlueBrush, whiteBrush, headerFont);
|
|
|
|
// ── Riga 2: Sottocolonne ──────────────────────────────────────
|
|
cx = x0;
|
|
for (int i = 0; i < cw.Length; i++)
|
|
{
|
|
var bg = i >= 9 ? darkBlueBrush : accentBrush;
|
|
DrawHeaderCell(g, cx, y + rh, cw[i], rh, Col2Headers[i], bg, whiteBrush, headerFont);
|
|
cx += cw[i];
|
|
}
|
|
|
|
// ── Separatore verticale tra col 8 e col 9 ───────────────────
|
|
float sepX = x0 + cw.Take(9).Sum();
|
|
var sepPen = new PdfPen(Color.FromArgb(255, 100, 181, 246), 1.5f); // #64B5F6
|
|
g.DrawLine(sepPen, new PointF(sepX, y), new PointF(sepX, y + rh * 2));
|
|
}
|
|
|
|
private static void DrawHeaderCell(PdfGraphics g, float x, float y, float w, float h,
|
|
string text, PdfBrush bg, PdfBrush fg, PdfFont font)
|
|
{
|
|
g.DrawRectangle(bg, new RectangleF(x, y, w, h));
|
|
if (!string.IsNullOrEmpty(text))
|
|
{
|
|
var fmt = new PdfStringFormat
|
|
{
|
|
Alignment = PdfTextAlignment.Center,
|
|
LineAlignment = PdfVerticalAlignment.Middle,
|
|
};
|
|
g.DrawString(text, font, fg, new RectangleF(x + 1, y, w - 2, h), fmt);
|
|
}
|
|
}
|
|
|
|
private static void DrawDataGrid(PdfGraphics g, CertificateReportData data,
|
|
float x0, float y, float[] cw, float pageW)
|
|
{
|
|
var grid = new PdfGrid();
|
|
grid.Style.CellPadding = new PdfPaddings(2, 2, 2, 2);
|
|
grid.Style.Font = PdfTheme.TableFont;
|
|
|
|
// 15 colonne
|
|
grid.Columns.Add(15);
|
|
for (int i = 0; i < 15; i++)
|
|
grid.Columns[i].Width = cw[i];
|
|
|
|
var sottostanti = data.Info.Sottostanti;
|
|
|
|
for (int i = 0; i < sottostanti.Count; i++)
|
|
{
|
|
var s = sottostanti[i];
|
|
var row = grid.Rows.Add();
|
|
|
|
row.Cells[0].Value = s.Nome;
|
|
row.Cells[1].Value = s.Strike;
|
|
row.Cells[2].Value = s.LastPrice;
|
|
row.Cells[3].Value = s.Performance ?? "—";
|
|
row.Cells[4].Value = s.CapitalBarrier ?? "—";
|
|
row.Cells[5].Value = s.ULCapitalBarrierBuffer ?? "—";
|
|
row.Cells[6].Value = s.CouponBarrier ?? "—";
|
|
row.Cells[7].Value = s.ULCouponBarrierBuffer ?? "—";
|
|
row.Cells[8].Value = s.TriggerAutocall ?? "—";
|
|
row.Cells[9].Value = s.DividendExDate ?? "—";
|
|
row.Cells[10].Value = s.DividendPayDate ?? "—";
|
|
row.Cells[11].Value = s.DividendAmount ?? "—";
|
|
row.Cells[12].Value = s.DividendYield ?? "—";
|
|
row.Cells[13].Value = s.DividendFutAmount ?? "—";
|
|
row.Cells[14].Value = s.DividendFutYield ?? "—";
|
|
|
|
// Righe alternate
|
|
if (i % 2 == 1)
|
|
{
|
|
for (int c = 0; c < 15; c++)
|
|
row.Cells[c].Style.BackgroundBrush = PdfTheme.TableAltRowBrush;
|
|
}
|
|
|
|
// Colore performance (negativi rosso, positivi verde)
|
|
foreach (int c in PerfCols)
|
|
{
|
|
var val = row.Cells[c].Value?.ToString();
|
|
if (string.IsNullOrEmpty(val) || val == "—") continue;
|
|
|
|
// Le stringhe arrivano già formattate dalla SP, es. "-12,34%"
|
|
bool isNegative = val.TrimStart().StartsWith('-');
|
|
row.Cells[c].Style.TextBrush = isNegative
|
|
? PdfTheme.NegativeRedBrush
|
|
: PdfTheme.PositiveGreenBrush;
|
|
}
|
|
|
|
// Allineamento centrato per tutte le celle tranne Nome
|
|
for (int c = 1; c < 15; c++)
|
|
{
|
|
row.Cells[c].StringFormat = new PdfStringFormat
|
|
{
|
|
Alignment = PdfTextAlignment.Center,
|
|
LineAlignment = PdfVerticalAlignment.Middle,
|
|
};
|
|
}
|
|
}
|
|
|
|
grid.Draw(g, new PointF(x0, y));
|
|
}
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 2: Build**
|
|
|
|
```bash
|
|
dotnet build CertReports.Syncfusion
|
|
```
|
|
Expected: errore su `ReportOrchestrator` — normale.
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add CertReports.Syncfusion/Services/Implementations/DividendSectionRenderer.cs
|
|
git commit -m "feat: add DividendSectionRenderer with landscape two-level header table"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 7: Registrare `DividendSectionRenderer` in `Program.cs`
|
|
|
|
**Files:**
|
|
- Modify: `Program.cs`
|
|
|
|
- [ ] **Step 1: Leggere `Program.cs`**
|
|
|
|
Trovare il blocco delle registrazioni renderer. Attualmente c'è:
|
|
```csharp
|
|
builder.Services.AddScoped<ExpiredAnagraficaSectionRenderer>();
|
|
```
|
|
|
|
- [ ] **Step 2: Aggiungere la registrazione subito sotto**
|
|
|
|
```csharp
|
|
builder.Services.AddScoped<ExpiredAnagraficaSectionRenderer>();
|
|
builder.Services.AddScoped<DividendSectionRenderer>();
|
|
```
|
|
|
|
- [ ] **Step 3: Build**
|
|
|
|
```bash
|
|
dotnet build CertReports.Syncfusion
|
|
```
|
|
Expected: solo errore su `ReportOrchestrator` — normale.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add CertReports.Syncfusion/Program.cs
|
|
git commit -m "feat: register DividendSectionRenderer in DI container"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 8: Aggiornare `ReportOrchestrator`
|
|
|
|
**Files:**
|
|
- Modify: `Services/Implementations/ReportOrchestrator.cs`
|
|
|
|
- [ ] **Step 1: Leggere il file completo**
|
|
|
|
Leggere l'intero file per avere contesto preciso prima di modificare.
|
|
|
|
- [ ] **Step 2: Aggiungere il campo e il parametro costruttore**
|
|
|
|
Aggiungere dopo `_expiredAnagraficaRenderer`:
|
|
```csharp
|
|
private readonly ExpiredAnagraficaSectionRenderer _expiredAnagraficaRenderer;
|
|
private readonly DividendSectionRenderer _dividendRenderer;
|
|
```
|
|
|
|
Nel costruttore aggiungere il parametro dopo `expiredAnagraficaRenderer`:
|
|
```csharp
|
|
ExpiredAnagraficaSectionRenderer expiredAnagraficaRenderer,
|
|
DividendSectionRenderer dividendRenderer)
|
|
```
|
|
|
|
E l'assegnazione:
|
|
```csharp
|
|
_expiredAnagraficaRenderer = expiredAnagraficaRenderer;
|
|
_dividendRenderer = dividendRenderer;
|
|
```
|
|
|
|
- [ ] **Step 3: Aggiornare la signature del metodo e le chiavi cache**
|
|
|
|
Cambiare la firma:
|
|
```csharp
|
|
public async Task<byte[]> GenerateReportAsync(string isin, bool showBranding = false, bool showDividend = false)
|
|
```
|
|
|
|
Aggiornare le chiavi cache (le 4 combinazioni):
|
|
```csharp
|
|
var dividendSuffix = showDividend ? ":dividend" : "";
|
|
|
|
var baseCacheKey = showBranding ? $"{isin}:branded{dividendSuffix}" : $"{isin}{dividendSuffix}";
|
|
var expiredCacheKey = showBranding ? $"{isin}:expired:branded{dividendSuffix}" : $"{isin}:expired{dividendSuffix}";
|
|
```
|
|
|
|
Nota: questo produce le 4 chiavi:
|
|
- `{isin}` — no branding, no dividend
|
|
- `{isin}:branded` — branding, no dividend
|
|
- `{isin}:dividend` — no branding, dividend
|
|
- `{isin}:branded:dividend` — branding + dividend
|
|
- (+ varianti expired)
|
|
|
|
- [ ] **Step 4: Impostare `ShowDividend` nel `CertificateReportData`**
|
|
|
|
Nel blocco di inizializzazione:
|
|
```csharp
|
|
var reportData = new CertificateReportData
|
|
{
|
|
Info = await _dataService.GetCertificateInfoAsync(isin),
|
|
Eventi = await _dataService.GetCertificateEventsAsync(isin),
|
|
Scenario = await _dataService.GetScenarioAnalysisAsync(isin),
|
|
ShowBranding = showBranding,
|
|
ShowDividend = showDividend,
|
|
};
|
|
```
|
|
|
|
- [ ] **Step 5: Inserire la sezione dividend nel flusso expired**
|
|
|
|
Dopo `pdfSections.Add(_expiredAnagraficaRenderer.Render(reportData));`, aggiungere:
|
|
```csharp
|
|
pdfSections.Add(_expiredAnagraficaRenderer.Render(reportData));
|
|
|
|
if (reportData.ShowDividend)
|
|
{
|
|
try
|
|
{
|
|
pdfSections.Add(_dividendRenderer.Render(reportData));
|
|
_logger.LogInformation("Sezione 'Dividend' generata per {Isin}", isin);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nella sezione 'Dividend' per {Isin}", isin);
|
|
throw;
|
|
}
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Inserire la sezione dividend nel flusso attivo (attuale)**
|
|
|
|
Il flusso attivo usa un `foreach` su `_sectionRenderers.OrderBy(r => r.Order)`.
|
|
`AnagraficaSectionRenderer` ha `Order = 1` (Sezione 1).
|
|
Dobbiamo inserire la pagina dividend subito dopo Anagrafica.
|
|
|
|
Sostituire il `foreach` con:
|
|
```csharp
|
|
foreach (var renderer in _sectionRenderers.OrderBy(r => r.Order))
|
|
{
|
|
if (renderer.SectionName == "Scenario" && !isScenarioAllowed)
|
|
{
|
|
_logger.LogInformation("Sezione Scenario saltata per {Isin}", isin);
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
pdfSections.Add(renderer.Render(reportData));
|
|
_logger.LogInformation("Sezione '{Section}' generata per {Isin}", renderer.SectionName, isin);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nella sezione '{Section}' per {Isin}", renderer.SectionName, isin);
|
|
throw;
|
|
}
|
|
|
|
// Inserire pagina dividend subito dopo Anagrafica (Sezione 1)
|
|
if (renderer.SectionName == "Anagrafica" && reportData.ShowDividend)
|
|
{
|
|
try
|
|
{
|
|
pdfSections.Add(_dividendRenderer.Render(reportData));
|
|
_logger.LogInformation("Sezione 'Dividend' generata per {Isin}", isin);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Errore nella sezione 'Dividend' per {Isin}", isin);
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 7: Build**
|
|
|
|
```bash
|
|
dotnet build CertReports.Syncfusion
|
|
```
|
|
Expected: **Build succeeded, 0 errors**.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
git add CertReports.Syncfusion/Services/Implementations/ReportOrchestrator.cs
|
|
git commit -m "feat: wire DividendSectionRenderer into orchestrator — both active and expired flows"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 9: Verifica manuale
|
|
|
|
Non esistono test automatici nel progetto. Verificare manualmente:
|
|
|
|
- [ ] **Step 1: Avviare l'applicazione**
|
|
|
|
```bash
|
|
dotnet run --project CertReports.Syncfusion
|
|
```
|
|
|
|
- [ ] **Step 2: Test baseline — comportamento invariato**
|
|
|
|
Aprire nel browser:
|
|
```
|
|
https://localhost:{porta}/api/report/by-isin/{ISIN_VALIDO}
|
|
```
|
|
Expected: report normale con tabella Sottostanti visibile in Sezione 1. Nessuna pagina landscape.
|
|
|
|
- [ ] **Step 3: Test con `?dividend=true`**
|
|
|
|
```
|
|
https://localhost:{porta}/api/report/by-isin/{ISIN_VALIDO}?dividend=true
|
|
```
|
|
Expected:
|
|
- Sezione 1 (Anagrafica): **nessuna** tabella Sottostanti
|
|
- Dopo Sezione 1: **pagina landscape** con titolo "Sottostanti e Dividendi"
|
|
- Header a 2 livelli: "SOTTOSTANTE" (blu) | "BARRIERE" (blu) | "DIVIDENDI" (blu scuro)
|
|
- Separatore verticale tra colonna Trig.AC e Data Stacco
|
|
- Righe dati con colori performance su % Perf., Buf.Cap., Buf.CPN, Rend., Rend.Fut.
|
|
- Colonne dividendo vuote mostrano "—"
|
|
- Footer con numero pagina
|
|
|
|
- [ ] **Step 4: Test combinato `?dividend=true&branding=true`**
|
|
|
|
Expected: come sopra + footer con "Powered by Smart Roots" su tutte le pagine.
|
|
|
|
- [ ] **Step 5: Test su certificato expired**
|
|
|
|
Usare un ISIN con `Stato != "Quotazione"`. Verificare lo stesso comportamento nei flusso expired.
|
|
|
|
- [ ] **Step 6: Commit finale**
|
|
|
|
```bash
|
|
git add -A
|
|
git commit -m "feat: dividend section complete — landscape page with two-level header"
|
|
```
|
|
|
|
---
|
|
|
|
## Note implementative
|
|
|
|
### Gotcha Syncfusion v33 da rispettare
|
|
|
|
| Problema | Soluzione applicata nel piano |
|
|
|----------|-------------------------------|
|
|
| `Color(r,g,b)` rimosso | `Color.FromArgb(255, r, g, b)` usato ovunque |
|
|
| `grid.Draw()` restituisce `void` | Usato senza assegnazione |
|
|
| `RectangleF` no named args | `new RectangleF(x, y, w, h)` — no named parameters |
|
|
| Namespace conflict | Aggiungere `using Syncfusion.Pdf;` se necessario |
|
|
|
|
### Dimensioni pagina landscape
|
|
|
|
A4 landscape in Syncfusion: `Width ≈ 841pt`, `Height ≈ 595pt`.
|
|
Con `PageMargin = 30pt` (verificare in `PdfTheme`), la larghezza utile `w ≈ 781pt`.
|
|
Le colonne vengono scalate proporzionalmente da 742pt a `w` → `scale ≈ 1.05`.
|
|
|
|
### `SectionName == "Anagrafica"`
|
|
|
|
Il valore esatto confermato in `AnagraficaSectionRenderer.cs` è `"Anagrafica"` — la stringa di confronto nel Task 8 Step 6 è corretta.
|