# 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 GenerateReportAsync(string isin, bool showBranding = false); } ``` - [ ] **Step 2: Aggiungere il parametro** ```csharp public interface IReportOrchestrator { Task 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 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 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 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 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; /// /// Genera la pagina landscape con la tabella unificata Sottostanti + Dividendi. /// Non implementa IPdfSectionRenderer — iniettato direttamente nell'orchestratore. /// 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 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(); ``` - [ ] **Step 2: Aggiungere la registrazione subito sotto** ```csharp builder.Services.AddScoped(); builder.Services.AddScoped(); ``` - [ ] **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 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.