From f403ffa90df508742056a30e20c8db7e7e308412 Mon Sep 17 00:00:00 2001 From: leandro Date: Wed, 25 Mar 2026 20:46:38 -0300 Subject: [PATCH] feat(documents): agregar template PDF de Delivery Note y endpoint API closes #43 --- Documents/Models/DocumentType.cs | 2 +- Documents/Services/DocumentTemplateService.cs | 5 + .../DeliveryNotes/Template_v1.cshtml | 182 ++++++++++++++++++ .../Sales/DeliveryNoteController.cs | 28 ++- 4 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 Documents/Templates/DeliveryNotes/Template_v1.cshtml diff --git a/Documents/Models/DocumentType.cs b/Documents/Models/DocumentType.cs index e9a06f8..bbd0582 100644 --- a/Documents/Models/DocumentType.cs +++ b/Documents/Models/DocumentType.cs @@ -4,9 +4,9 @@ { Quote, Expedition, + DeliveryNote, Invoice, Order, - Remito, Certificate } } diff --git a/Documents/Services/DocumentTemplateService.cs b/Documents/Services/DocumentTemplateService.cs index 8823c89..f5c8213 100644 --- a/Documents/Services/DocumentTemplateService.cs +++ b/Documents/Services/DocumentTemplateService.cs @@ -2,6 +2,7 @@ using Documents.Interfaces; using Documents.Models; using Domain.Dtos; // QuoteDto +using Domain.Dtos.Sales; // DeliveryNoteDto using Domain.Dtos.Stock; // ExpeditionDto using Transversal.Interfaces; @@ -57,6 +58,7 @@ public class DocumentTemplateService : IDocumentTemplateService 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" }; @@ -72,6 +74,9 @@ public class DocumentTemplateService : IDocumentTemplateService case ExpeditionDto e: e.LogoBase64 = base64; break; + case DeliveryNoteDto d: + d.LogoBase64 = base64; + break; default: // Si no tiene LogoBase64, no hacemos nada. break; diff --git a/Documents/Templates/DeliveryNotes/Template_v1.cshtml b/Documents/Templates/DeliveryNotes/Template_v1.cshtml new file mode 100644 index 0000000..a5cae79 --- /dev/null +++ b/Documents/Templates/DeliveryNotes/Template_v1.cshtml @@ -0,0 +1,182 @@ +@using System +@using System.Globalization +@using System.Text.Json +@using Domain.Dtos.Sales +@model DeliveryNoteDto + +@{ + Layout = null; + + var ci = CultureInfo.GetCultureInfo("es-AR"); + CultureInfo.CurrentCulture = ci; + CultureInfo.CurrentUICulture = ci; + + SurgerySnapshot snap; + if (string.IsNullOrWhiteSpace(Model.ExtraInfoJson)) + { + snap = new SurgerySnapshot(); + } + else + { + try + { + snap = JsonSerializer.Deserialize(Model.ExtraInfoJson) ?? new SurgerySnapshot(); + } + catch + { + snap = new SurgerySnapshot(); + } + } + + var reprintText = Model.PrintCount > 0 ? (" — Reimpresión " + Model.PrintCount) : string.Empty; +} + +@functions { + public class SurgerySnapshot + { + public string? Professional { get; set; } + public string? Institution { get; set; } + public string? Patient { get; set; } + public DateTime? SurgeryDate { get; set; } + } + + public static string FQty(decimal q) => q.ToString("G29", CultureInfo.InvariantCulture); + public static string FDate(DateTime? d) => d.HasValue ? d.Value.ToString("dd/MM/yyyy") : string.Empty; + public static string FText(string? value) => string.IsNullOrWhiteSpace(value) ? "-" : value.Trim(); + public static string FOrigin(byte originType) => originType switch + { + 1 => "Presupuesto", + 2 => "Manual", + _ => originType.ToString() + }; +} + + + + + + Remito @Model.DeliveryNoteNumber + + + + +
+
+
+ @if (!string.IsNullOrWhiteSpace(Model.LogoBase64)) + { + Logo + } +
Documento generado por PhronCare
+
+
+

Remito

+
@Model.DeliveryNoteNumber@reprintText
+
Fecha: @Model.IssueDate.ToString("dd/MM/yyyy")
+
+
+ +
+ +
+ + + + + + + + + + + + + +
Cliente@FText(Model.CustomerName)Estado@FText(Model.Status)
Presupuesto@FText(Model.QuoteNumber)ID interno@Model.Id
+
+ +
Contexto clínico
+
+ + + + + + + + + + + + + +
Profesional@FText(snap.Professional)Institución@FText(snap.Institution)
Paciente@FText(snap.Patient)Fecha cirugía@FDate(snap.SurgeryDate)
+
+ +
Detalle de ítems
+
+ + + + + + + + + + + + + @foreach (var item in Model.Items.OrderBy(i => i.LineNumber)) + { + + + + + + + + + } + +
#DescripciónCantidadOrigenRef.Notas
@item.LineNumber@FText(item.Description)@FQty(item.Quantity)@FOrigin(item.OriginType)@(item.OriginId?.ToString() ?? "-")@FText(item.Notes)
+
+ +
Observaciones
+
@FText(Model.Observations)
+ + +
+ + diff --git a/phronCare.API/Controllers/Sales/DeliveryNoteController.cs b/phronCare.API/Controllers/Sales/DeliveryNoteController.cs index c25215c..86de3ff 100644 --- a/phronCare.API/Controllers/Sales/DeliveryNoteController.cs +++ b/phronCare.API/Controllers/Sales/DeliveryNoteController.cs @@ -1,4 +1,6 @@ -using Core.Interfaces; +using Core.Interfaces; +using Documents.Interfaces; +using Documents.Models; using Domain.Dtos.Sales; using Domain.Generics; using Microsoft.AspNetCore.Mvc; @@ -10,10 +12,14 @@ namespace phronCare.API.Controllers.Sales [ApiController] public class DeliveryNoteController : ControllerBase { + private readonly IDocumentTemplateService _documentTemplateService; private readonly IDeliveryNoteDom _deliveryNoteService; - public DeliveryNoteController(IDeliveryNoteDom deliveryNoteService) + public DeliveryNoteController( + IDocumentTemplateService documentTemplateService, + IDeliveryNoteDom deliveryNoteService) { + _documentTemplateService = documentTemplateService ?? throw new ArgumentNullException(nameof(documentTemplateService)); _deliveryNoteService = deliveryNoteService ?? throw new ArgumentNullException(nameof(deliveryNoteService)); } @@ -89,6 +95,24 @@ namespace phronCare.API.Controllers.Sales } } + + [HttpGet("{id:int}/pdf")] + public async Task GetDeliveryNotePdf(int id) + { + var deliveryNote = await _deliveryNoteService.GetDtoByIdAsync(id); + + if (deliveryNote == null) + return NotFound($"Remito con ID {id} no encontrado."); + + var pdfBytes = await _documentTemplateService.GenerateDocumentAsync(new DocumentGenerationRequest + { + Model = deliveryNote, + DocumentType = DocumentType.DeliveryNote + }); + + return File(pdfBytes, "application/pdf", $"Remito_{deliveryNote.DeliveryNoteNumber}.pdf"); + } + [HttpGet("by-quote/{quoteId:int}")] public async Task>> GetByQuoteId(int quoteId) {