feat: add ExpiredAnagraficaSectionRenderer for non-quoted certificates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 10:33:18 +01:00
parent 7ad14c129c
commit 3cca41a77b

View File

@@ -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;
/// <summary>
/// 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.
/// </summary>
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;
}
}