diff --git a/CertReports.Syncfusion/CertReports.Syncfusion.csproj b/CertReports.Syncfusion/CertReports.Syncfusion.csproj index 5ff333e..40ceab0 100644 --- a/CertReports.Syncfusion/CertReports.Syncfusion.csproj +++ b/CertReports.Syncfusion/CertReports.Syncfusion.csproj @@ -7,6 +7,13 @@ CertReports.Syncfusion + + + + + + + diff --git a/CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs b/CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs index d5d1e05..06076c1 100644 --- a/CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs +++ b/CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs @@ -4,13 +4,14 @@ using CertReports.Syncfusion.Services.Interfaces; using Syncfusion.Drawing; using Syncfusion.Pdf; using Syncfusion.Pdf.Graphics; +using Syncfusion.Pdf.Grid; namespace CertReports.Syncfusion.Services.Implementations; /// /// Sezione 1 — Prima pagina del report per certificati non in quotazione -/// (Scaduto, Rimborsato, Revocato). Struttura semplificata rispetto al report attivo: -/// solo Titolo + Caratteristiche Prodotto + Analisi. Niente Bid/Ask, niente Sottostanti. +/// (Scaduto, Rimborsato, Revocato). Struttura: Titolo + Caratteristiche Prodotto + Analisi + Sottostanti. +/// Niente Bid/Ask nel titolo. /// public class ExpiredAnagraficaSectionRenderer : IPdfSectionRenderer { @@ -42,8 +43,24 @@ public class ExpiredAnagraficaSectionRenderer : IPdfSectionRenderer // ── SEZIONE B: ANALISI ──────────────────────────────────────── y = DrawSectionLabel(g, "Analisi", y); y = DrawAnalisi(g, info, y); + y += SectionGap; - PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding); + // ── 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; + y = 0f; + } + + y = DrawSectionLabel(g, "Sottostanti", y); + DrawSottostanti(g, info.Sottostanti, PageW, y); + } + + PdfTheme.DrawFooter(g, PageW, PageH, doc.Pages.Count, data.ShowBranding); return doc; } @@ -201,6 +218,90 @@ public class ExpiredAnagraficaSectionRenderer : IPdfSectionRenderer return DrawKVList(g, items, 0, ColW, y); } + // ═══════════════════════════════════════════════════════════════ + // SEZIONE C: SOTTOSTANTI (9 colonne, font 7.5pt) + // ═══════════════════════════════════════════════════════════════ + + private void DrawSottostanti(PdfGraphics g, List sottostanti, float w, float y) + { + var grid = new PdfGrid(); + grid.Style.CellPadding = new PdfPaddings(2, 2, 2, 2); + + string[] headers = { "Nome", "Strike", "Last", "% Perf.", "Barriera Capitale", + "Buffer Capitale", "Trigger Cedola", "Buffer Cedola", "Trigger Autocall" }; + + foreach (var _ in headers) grid.Columns.Add(); + + var hr = grid.Headers.Add(1)[0]; + for (int i = 0; i < headers.Length; i++) + { + hr.Cells[i].Value = headers[i]; + hr.Cells[i].Style.Font = PdfTheme.TableBold; + hr.Cells[i].Style.BackgroundBrush = PdfTheme.TableHeaderBrush; + hr.Cells[i].Style.TextBrush = PdfTheme.HeaderTextBrush; + hr.Cells[i].StringFormat = new PdfStringFormat( + i == 0 ? PdfTextAlignment.Left : PdfTextAlignment.Center); + } + + 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; + + for (int c = 0; c < headers.Length; c++) + { + row.Cells[c].Style.Font = PdfTheme.TableFont; + row.Cells[c].StringFormat = new PdfStringFormat( + c == 0 ? PdfTextAlignment.Left : PdfTextAlignment.Right); + } + + ColorPerformanceCell(row.Cells[3], s.Performance); + ColorPerformanceCell(row.Cells[5], s.ULCapitalBarrierBuffer); + ColorPerformanceCell(row.Cells[7], s.ULCouponBarrierBuffer); + + if (i % 2 == 1) + for (int c = 0; c < headers.Length; c++) + row.Cells[c].Style.BackgroundBrush = PdfTheme.TableAltRowBrush; + } + + float[] cw = { 90f, 52f, 52f, 46f, 52f, 46f, 52f, 46f, 46f }; + float total = cw.Sum(); + float scale = w / total; + for (int i = 0; i < cw.Length; i++) + grid.Columns[i].Width = cw[i] * scale; + + PdfTheme.ApplyThinBorders(grid); + grid.Draw(g, new PointF(0, y)); + } + + // ═══════════════════════════════════════════════════════════════ + // Helper + // ═══════════════════════════════════════════════════════════════ + + private static bool IsNegativeValue(string? value) + { + if (string.IsNullOrWhiteSpace(value)) return false; + return value.TrimStart().StartsWith('-'); + } + + private static void ColorPerformanceCell(PdfGridCell cell, string? value) + { + if (IsNegativeValue(value)) + cell.Style.TextBrush = new PdfSolidBrush(PdfTheme.NegativeRed); + else if (!string.IsNullOrWhiteSpace(value) && value != "-" && value != "—") + cell.Style.TextBrush = new PdfSolidBrush(PdfTheme.PositiveGreen); + } + private float DrawKVList(PdfGraphics g, (string Label, string Value)[] items, float x, float w, float y) { float rh = PdfTheme.RowHeight; diff --git a/README.md b/README.md index 02021a5..914555c 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Generatore di report PDF per certificati finanziari strutturati, sviluppato in A | Sezione | Contenuto | |---------|-----------| -| **1 — Anagrafica (semplificata)** | Header con solo Tipologia, caratteristiche con Valore/Data Rimborso, analisi KV senza Sottostanti né Bid/Ask | +| **1 — Anagrafica (semplificata)** | Header con solo Tipologia, caratteristiche con Valore/Data Rimborso, analisi KV, tabella sottostanti (9 colonne, senza Bid/Ask) | | **2 — Eventi** | Lista eventi con colonne adattate (Barriera Cedola, Soglia Rimborso, Barriera Capitale, Rimborso Capitale) | | **3 — Grafico** | Performance storica dei sottostanti |