phronCare/Documents/Services/DocumentTemplateService.cs

102 lines
3.7 KiB
C#

using System.Collections.Concurrent;
using Documents.Interfaces;
using Documents.Models;
using Domain.Dtos; // QuoteDto
using Domain.Dtos.Sales; // DeliveryNoteDto
using Domain.Dtos.Stock; // ExpeditionDto
using Transversal.Interfaces;
public class DocumentTemplateService : IDocumentTemplateService
{
private readonly ITemplateRenderer _templateRenderer;
private readonly IPdfGeneratorService _pdfGeneratorService;
// Cache simple para no leer el logo del disco en cada render
private static readonly ConcurrentDictionary<string, string> _imageCacheBase64 = new();
public DocumentTemplateService(ITemplateRenderer templateRenderer, IPdfGeneratorService pdfGeneratorService)
{
_templateRenderer = templateRenderer;
_pdfGeneratorService = pdfGeneratorService;
}
public async Task<byte[]> GenerateDocumentAsync(DocumentGenerationRequest request)
{
if (request is null) throw new ArgumentNullException(nameof(request));
if (request.Model is null) throw new ArgumentNullException(nameof(request.Model));
string? templatePath = null;
try
{
// 1) Elegir plantilla por tipo de documento
templatePath = ResolveTemplate(request.DocumentType);
// 2) Inyectar logo (si el DTO lo soporta)
var logoBase64 = GetImageBase64Cached(
Path.Combine(Directory.GetCurrentDirectory(), "Resources", "logo.png"));
InjectLogoIfSupported(request.Model, logoBase64);
// 3) Render + PDF
var html = await _templateRenderer.RenderAsync(templatePath, request.Model);
return await _pdfGeneratorService.GeneratePdfFromHtmlAsync(html);
}
catch (Exception ex)
{
// Envolvemos con contexto para facilitar el diagnóstico
var wrapped = new Exception(
$"Document generation failed (DocumentType={request.DocumentType}, Template='{templatePath ?? "?"}', ModelType={request.Model.GetType().FullName}). See inner exception.",
ex
);
wrapped.Data["DocumentType"] = request.DocumentType.ToString();
if (!string.IsNullOrEmpty(templatePath)) wrapped.Data["TemplatePath"] = templatePath;
wrapped.Data["ModelType"] = request.Model.GetType().FullName;
throw wrapped;
}
}
private static string ResolveTemplate(DocumentType type) => type switch
{
DocumentType.Quote => "Quotes/Template_v1.cshtml",
DocumentType.DeliveryNote => "DeliveryNotes/Template_v1.cshtml",
DocumentType.Expedition => "Expeditions/Template_v1.cshtml",
_ => "Shared/Template_Generic.cshtml"
};
private static void InjectLogoIfSupported(object model, string base64)
{
// Inyección “segura”: si el modelo expone LogoBase64, lo seteamos.
switch (model)
{
case QuoteDto q:
q.LogoBase64 = base64;
break;
case ExpeditionDto e:
e.LogoBase64 = base64;
break;
case DeliveryNoteDto d:
d.LogoBase64 = base64;
break;
default:
// Si no tiene LogoBase64, no hacemos nada.
break;
}
}
private static string GetImageBase64Cached(string imagePath)
{
if (_imageCacheBase64.TryGetValue(imagePath, out var cached))
return cached;
if (!File.Exists(imagePath))
{
_imageCacheBase64[imagePath] = "";
return "";
}
var base64 = Convert.ToBase64String(File.ReadAllBytes(imagePath));
_imageCacheBase64[imagePath] = base64;
return base64;
}
}