feat: add sottostanti table to expired certificate report (sezione 1)

Adds the 9-column underlyings table (identical to the active report) to
ExpiredAnagraficaSectionRenderer, with overflow-to-new-page guard.
Also excludes Logs/** from csproj build and updates README accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 09:32:28 +01:00
parent 16d06340fc
commit 14d4baa629
3 changed files with 112 additions and 4 deletions

View File

@@ -7,6 +7,13 @@
<RootNamespace>CertReports.Syncfusion</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Logs\**" />
<Content Remove="Logs\**" />
<EmbeddedResource Remove="Logs\**" />
<None Remove="Logs\**" />
</ItemGroup>
<ItemGroup>
<!-- Syncfusion PDF -->
<PackageReference Include="Syncfusion.Pdf.Net.Core" Version="33.1.44" />

View File

@@ -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;
/// <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.
/// (Scaduto, Rimborsato, Revocato). Struttura: Titolo + Caratteristiche Prodotto + Analisi + Sottostanti.
/// Niente Bid/Ask nel titolo.
/// </summary>
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;
// ── 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<Sottostante> 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;

View File

@@ -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 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 |