diff --git a/CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs b/CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs new file mode 100644 index 0000000..fccce9e --- /dev/null +++ b/CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs @@ -0,0 +1,235 @@ +using CertReports.Syncfusion.Helpers; +using CertReports.Syncfusion.Models; +using CertReports.Syncfusion.Services.Interfaces; +using Syncfusion.Drawing; +using Syncfusion.Pdf; +using Syncfusion.Pdf.Graphics; + +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. +/// +public class ExpiredAnagraficaSectionRenderer : IPdfSectionRenderer +{ + public string SectionName => "ExpiredAnagrafica"; + public int Order => 1; + + private const float PageW = 595f - 2 * PdfTheme.PageMargin; + private const float PageH = 842f - 2 * PdfTheme.PageMargin - PdfTheme.FooterHeight; + private const float ColGap = 10f; + private const float ColW = (PageW - ColGap) / 2f; + private const float SectionGap = 10f; + + public PdfDocument Render(CertificateReportData data) + { + var doc = new PdfDocument(); + var page = PdfTheme.AddA4Page(doc); + var g = page.Graphics; + var info = data.Info; + float y = 0f; + + // ── TITOLO ──────────────────────────────────────────────────── + y = DrawTitle(g, info, PageW, y); + + // ── SEZIONE A: CARATTERISTICHE PRODOTTO ─────────────────────── + y = DrawSectionLabel(g, "Caratteristiche Prodotto", y); + y = DrawCaratteristiche(g, info, y); + y += SectionGap; + + // ── SEZIONE B: ANALISI ──────────────────────────────────────── + y = DrawSectionLabel(g, "Analisi", y); + DrawAnalisi(g, info, y); + + PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding); + + return doc; + } + + // ═══════════════════════════════════════════════════════════════ + // TITOLO — Solo Tipologia box (niente Data/Bid/Ask) + // ═══════════════════════════════════════════════════════════════ + + private float DrawTitle(PdfGraphics g, CertificateInfo info, float w, float y) + { + g.DrawString($"Scheda Prodotto {info.Isin}", + PdfTheme.SectionTitleFont, + new PdfSolidBrush(PdfTheme.AccentBlue), + new RectangleF(0, y, w, 20f), + new PdfStringFormat(PdfTextAlignment.Center)); + y += 24f; + + g.DrawLine(PdfTheme.AccentBluePen, 0, y, w, y); + y += 8f; + + // Solo box Tipologia, niente Data/Bid/Ask + const float boxH = 28f; + if (!string.IsNullOrEmpty(info.Categoria)) + { + DrawInfoBox(g, 0, y, w, boxH, + "TIPOLOGIA", info.Categoria, + PdfTheme.BoxLightBlueBgBrush, PdfTheme.AccentBlueBrush, + PdfTheme.AccentBlueBrush, PdfTheme.AccentBlueDarkBrush, + PdfTheme.Bold); + } + + y += boxH + 10f; + return y; + } + + // ═══════════════════════════════════════════════════════════════ + // INTESTAZIONE DI SEZIONE + // ═══════════════════════════════════════════════════════════════ + + private float DrawSectionLabel(PdfGraphics g, string title, float y) + { + g.DrawRectangle(PdfTheme.AccentBlueBrush, new RectangleF(0, y, 3f, 14f)); + g.DrawString(title, PdfTheme.Bold, + new PdfSolidBrush(PdfTheme.AccentBlue), + new RectangleF(6f, y, PageW, 14f)); + return y + 16f; + } + + // ═══════════════════════════════════════════════════════════════ + // INFO BOX + // ═══════════════════════════════════════════════════════════════ + + private void DrawInfoBox( + PdfGraphics g, + float x, float y, float w, float boxH, + string label, string value, + PdfBrush bgBrush, PdfBrush accentBrush, + PdfBrush labelBrush, PdfBrush valueBrush, + PdfFont valueFont) + { + g.DrawRectangle(bgBrush, new RectangleF(x, y, w, boxH)); + g.DrawRectangle(accentBrush, new RectangleF(x, y, 4f, boxH)); + float innerX = x + 4f + 6f; + float innerW = w - 4f - 6f - 4f; + g.DrawString(label, PdfTheme.Small, labelBrush, + new RectangleF(innerX, y + 4f, innerW, 10f)); + g.DrawString(value, valueFont, valueBrush, + new RectangleF(innerX, y + 14f, innerW, 14f)); + } + + // ═══════════════════════════════════════════════════════════════ + // SEZIONE A: CARATTERISTICHE PRODOTTO + // ═══════════════════════════════════════════════════════════════ + + private float DrawCaratteristiche(PdfGraphics g, CertificateInfo info, float y) + { + return DrawEmittenteTable(g, info, 0, ColW, y); + } + + private float DrawEmittenteTable(PdfGraphics g, CertificateInfo info, float x, float w, float y) + { + float rh = PdfTheme.RowHeight; + float pad = PdfTheme.CellPadding; + + // Header blu + g.DrawRectangle(PdfTheme.TableHeaderBrush, new RectangleF(x, y, w, rh)); + string headerText = string.IsNullOrEmpty(info.Emittente) + ? "EMITTENTE" + : $"EMITTENTE {info.Emittente.ToUpper()}"; + g.DrawString(headerText, PdfTheme.TableBold, + PdfTheme.HeaderTextBrush, + new RectangleF(x + pad, y + 3f, w - pad * 2, rh)); + y += rh; + + var rows = new[] + { + ("ISIN", info.Isin), + ("Mercato", info.Mercato), + ("Valuta", info.Valuta), + ("Data Emissione", info.DataEmissione), + ("Valore Rimborso", info.ValoreRimborso), + ("Data Rimborso", info.DataRimborso), + }; + + for (int i = 0; i < rows.Length; i++) + { + var (label, value) = rows[i]; + if (string.IsNullOrWhiteSpace(value)) continue; + + bool alt = i % 2 == 1; + if (alt) + g.DrawRectangle(new PdfSolidBrush(PdfTheme.TableAltRow), + new RectangleF(x, y, w, rh)); + + g.DrawLine(PdfTheme.TableBorderPen, x, y, x + w, y); + + g.DrawString(label, PdfTheme.TableFont, + new PdfSolidBrush(PdfTheme.TextSecondary), + new RectangleF(x + pad, y + 2f, w * 0.5f, rh)); + + var valueBrush = label == "ISIN" + ? new PdfSolidBrush(PdfTheme.AccentBlue) + : new PdfSolidBrush(PdfTheme.TextPrimary); + + g.DrawString(value, PdfTheme.TableBold, valueBrush, + new RectangleF(x + w * 0.5f, y + 2f, w * 0.5f - pad, rh), + new PdfStringFormat(PdfTextAlignment.Right)); + + y += rh; + } + + g.DrawLine(PdfTheme.TableBorderPen, x, y, x + w, y); + return y + 2f; + } + + // ═══════════════════════════════════════════════════════════════ + // SEZIONE B: ANALISI — KV list colonna destra + // ═══════════════════════════════════════════════════════════════ + + private float DrawAnalisi(PdfGraphics g, CertificateInfo info, float y) + { + var items = new (string Label, string Value)[] + { + ("Importo Cedola (p.a.)", info.NominalAnnualYield), + ("Frequenza Cedola", info.FrequenzaCedole), + ("Valore Nominale", info.NominalValue?.ToString("N0") ?? string.Empty), + ("Memoria Cedola", info.Memory), + ("Tipo Barriera", info.BarrierType), + ("Tipo Basket", info.BasketType), + ("Rendimento Totale", info.RendimentoTotale), + }; + + return DrawKVList(g, items, ColW + ColGap, ColW, y); + } + + private float DrawKVList(PdfGraphics g, (string Label, string Value)[] items, float x, float w, float y) + { + float rh = PdfTheme.RowHeight; + float pad = PdfTheme.CellPadding; + float labelW = w * 0.58f; + float valueW = w * 0.42f; + + foreach (var (label, value) in items) + { + if (string.IsNullOrWhiteSpace(value)) continue; + + g.DrawString(label, PdfTheme.TableFont, + new PdfSolidBrush(PdfTheme.TextSecondary), + new RectangleF(x + pad, y + 1f, labelW, rh)); + + bool isNeg = value.TrimStart().StartsWith('-'); + bool isKey = label == "Rendimento Totale"; + + var brush = isNeg ? new PdfSolidBrush(PdfTheme.NegativeRed) + : isKey ? new PdfSolidBrush(PdfTheme.AccentBlue) + : new PdfSolidBrush(PdfTheme.TextPrimary); + + g.DrawString(value, PdfTheme.TableBold, brush, + new RectangleF(x + labelW, y + 1f, valueW - pad, rh), + new PdfStringFormat(PdfTextAlignment.Right)); + + g.DrawLine(new PdfPen(PdfTheme.SeparatorLine, 0.3f), + x, y + rh, x + w, y + rh); + + y += rh; + } + return y; + } +}