diff --git a/CertReports.Syncfusion/Controllers/FundReportController.cs b/CertReports.Syncfusion/Controllers/FundReportController.cs
new file mode 100644
index 0000000..4b8db74
--- /dev/null
+++ b/CertReports.Syncfusion/Controllers/FundReportController.cs
@@ -0,0 +1,115 @@
+using CertReports.Syncfusion.Helpers;
+using CertReports.Syncfusion.Services.Interfaces;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CertReports.Syncfusion.Controllers;
+
+///
+/// Endpoint:
+/// GET /api/report/fund/by-isin/{isin}
+/// GET /api/report/fund?p={isin_cifrato}
+/// GET /api/report/fund?alias={id}
+/// GET /api/report/fund/download?p={isin_cifrato}
+///
+[ApiController]
+[Route("api/report/fund")]
+public class FundReportController : ControllerBase
+{
+ private readonly IFundReportOrchestrator _orchestrator;
+ private readonly IFundDataService _dataService;
+ private readonly CryptoHelper _crypto;
+ private readonly ILogger _logger;
+
+ public FundReportController(
+ IFundReportOrchestrator orchestrator,
+ IFundDataService dataService,
+ CryptoHelper crypto,
+ ILogger logger)
+ {
+ _orchestrator = orchestrator;
+ _dataService = dataService;
+ _crypto = crypto;
+ _logger = logger;
+ }
+
+ [HttpGet("by-isin/{isin}")]
+ public async Task ByIsin(string isin,
+ [FromQuery] bool branding = false)
+ => await GenerateAndReturnPdf(isin, branding, inline: true);
+
+ [HttpGet("")]
+ public async Task ByQuery(
+ [FromQuery] string? p = null,
+ [FromQuery] string? alias = null,
+ [FromQuery] bool branding = false)
+ {
+ string? isin = null;
+ if (!string.IsNullOrEmpty(p))
+ {
+ try { isin = _crypto.DecryptIsin(p); }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Errore decodifica ISIN cifrato (fund)");
+ return BadRequest("Parametro 'p' non valido.");
+ }
+ }
+ else if (!string.IsNullOrEmpty(alias))
+ {
+ isin = await _dataService.FindIsinByAliasIdAsync(alias);
+ }
+
+ if (string.IsNullOrEmpty(isin))
+ return BadRequest("Specificare 'p' (ISIN cifrato) o 'alias'.");
+
+ return await GenerateAndReturnPdf(isin, branding, inline: true);
+ }
+
+ [HttpGet("download")]
+ public async Task Download(
+ [FromQuery] string? p = null,
+ [FromQuery] string? alias = null,
+ [FromQuery] bool branding = false)
+ {
+ string? isin = null;
+ if (!string.IsNullOrEmpty(p))
+ {
+ try { isin = _crypto.DecryptIsin(p); }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Errore decodifica ISIN cifrato (fund download)");
+ return BadRequest("Parametro 'p' non valido.");
+ }
+ }
+ else if (!string.IsNullOrEmpty(alias))
+ {
+ isin = await _dataService.FindIsinByAliasIdAsync(alias);
+ }
+
+ if (string.IsNullOrEmpty(isin))
+ return BadRequest("Specificare 'p' (ISIN cifrato) o 'alias'.");
+
+ return await GenerateAndReturnPdf(isin, branding, inline: false);
+ }
+
+ private async Task GenerateAndReturnPdf(string isin, bool branding, bool inline)
+ {
+ try
+ {
+ var pdfBytes = await _orchestrator.GenerateReportAsync(isin, branding);
+ var disposition = inline ? "inline" : "attachment";
+ Response.Headers.Append("Content-Disposition",
+ $"{disposition}; filename=fund_{isin}.pdf");
+ return File(pdfBytes, "application/pdf");
+ }
+ catch (InvalidOperationException ex) when (ex.Message.Contains("Nessun dato"))
+ {
+ _logger.LogWarning("ISIN fondo non trovato: {Isin}", isin);
+ return NotFound($"Nessun dato per ISIN {isin}");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Errore generazione fund report per ISIN {Isin}", isin);
+ return StatusCode(500, "Errore nella generazione del report.");
+ }
+ }
+}