From 27439cbd95e3562b1c6b5449a74d22c56e43c6a0 Mon Sep 17 00:00:00 2001 From: Leandro Hernan Rojas Date: Sat, 5 Jul 2025 13:26:22 -0300 Subject: [PATCH] Add Download Template Product --- Core/Interfaces/Stock/ILSProductDom.cs | 1 + Core/Services/Stock/LSProductService.cs | 11 +++++ .../Controllers/Stock/LSProductController.cs | 9 ++++ .../Templates/Stock/plantilla_productos.xlsx | Bin 0 -> 5245 bytes .../obj/Debug/net8.0/ApiEndpoints.json | 10 +++++ phronCare.API/phronCare.API.csproj | 5 +++ .../Pages/Stock/ProductImport.razor | 40 +++++++++++++++++- .../Services/Stock/LSProductService.cs | 17 +++++++- 8 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 phronCare.API/Resources/Templates/Stock/plantilla_productos.xlsx diff --git a/Core/Interfaces/Stock/ILSProductDom.cs b/Core/Interfaces/Stock/ILSProductDom.cs index bba90cd..4c76f25 100644 --- a/Core/Interfaces/Stock/ILSProductDom.cs +++ b/Core/Interfaces/Stock/ILSProductDom.cs @@ -11,5 +11,6 @@ namespace Core.Interfaces Task UpdateAsync(ELSProduct entity); Task DeleteAsync(int id); Task ExportToExcelAsync(LSProductSearchParams searchParams); + byte[] GetImportTemplate(); } } \ No newline at end of file diff --git a/Core/Services/Stock/LSProductService.cs b/Core/Services/Stock/LSProductService.cs index 335456b..a9cd721 100644 --- a/Core/Services/Stock/LSProductService.cs +++ b/Core/Services/Stock/LSProductService.cs @@ -77,5 +77,16 @@ namespace Core.Services throw new Exception($"Error en {method}: {ex.Message}", ex); } } + public byte[] GetImportTemplate() + { + var path = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "Templates", "Stock", "plantilla_productos.xlsx"); + + if (!File.Exists(path)) + throw new FileNotFoundException("No se encontró la plantilla de importación de productos.", path); + + return File.ReadAllBytes(path); + } + + } } diff --git a/phronCare.API/Controllers/Stock/LSProductController.cs b/phronCare.API/Controllers/Stock/LSProductController.cs index ccf762d..4c01c38 100644 --- a/phronCare.API/Controllers/Stock/LSProductController.cs +++ b/phronCare.API/Controllers/Stock/LSProductController.cs @@ -64,5 +64,14 @@ namespace API.Controllers.Stock var fileName = $"productos_{DateTime.Now:yyyyMMddHHmm}.xlsx"; return File(content, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName); } + + [HttpGet("download-template")] + public IActionResult DownloadImportTemplate() + { + var file = _service.GetImportTemplate(); + return File(file, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "plantilla_productos.xlsx"); + } } } diff --git a/phronCare.API/Resources/Templates/Stock/plantilla_productos.xlsx b/phronCare.API/Resources/Templates/Stock/plantilla_productos.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7019c442b260493859a3cccdb53382a401789fad GIT binary patch literal 5245 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 + + Always + true + PreserveNewest + diff --git a/phronCare.UIBlazor/Pages/Stock/ProductImport.razor b/phronCare.UIBlazor/Pages/Stock/ProductImport.razor index fcf391d..fe1b296 100644 --- a/phronCare.UIBlazor/Pages/Stock/ProductImport.razor +++ b/phronCare.UIBlazor/Pages/Stock/ProductImport.razor @@ -1,13 +1,17 @@ @page "/stock/productimport" +@using phronCare.UIBlazor.Services.Stock @inject IJSRuntime JS +@inject IToastService toastService @inject NavigationManager Navigation +@inject LSProductService productService

Importación masiva de productos

+
@@ -16,6 +20,13 @@
+@if (UploadedFile != null) +{ +
+ +
+} + @if (PreviewItems != null) {
Vista previa
@@ -61,6 +72,8 @@ @code { private List? PreviewItems; + private IBrowserFile? UploadedFile; + protected override void OnInitialized() { PreviewItems = new List @@ -72,14 +85,37 @@ new() { FactoryCode = "ZIM005", Name = "Perno cortical", Description = "Perno de fijación", ProductType = 1, TraceabilityType = 3, DivisionCode = "ZIM", UnitCode = "UN", PlusProcess = true, ExternalCode = "EXT005" } }; } + private async Task DownloadTemplate() { - Navigation.NavigateTo("api/LSProductImport/download-template", forceLoad: true); + try + { + await productService.DownloadTemplateAsync(); + } + catch (Exception ex) + { + toastService.ShowError($"Error al descargar la plantilla: {ex.Message}"); + } } private async Task HandleFileSelected(InputFileChangeEventArgs e) { - // Lógica futura para parsear y enviar archivo al backend + UploadedFile = e.File; + //PreviewItems = null; // Limpiar preview anterior + } + + private async Task ProcessFile() + { + if (UploadedFile == null) return; + + using var stream = UploadedFile.OpenReadStream(10 * 1024 * 1024); + using var ms = new MemoryStream(); + await stream.CopyToAsync(ms); + var content = ms.ToArray(); + + // Aquí deberías llamar al backend para validar y obtener la vista previa + // Por ahora se simula con los datos ya cargados en OnInitialized + // PreviewItems = await Http.PostAsJsonAsync(...) } private async Task SimulateImport() diff --git a/phronCare.UIBlazor/Services/Stock/LSProductService.cs b/phronCare.UIBlazor/Services/Stock/LSProductService.cs index 62fd7f7..499b3b9 100644 --- a/phronCare.UIBlazor/Services/Stock/LSProductService.cs +++ b/phronCare.UIBlazor/Services/Stock/LSProductService.cs @@ -73,5 +73,20 @@ namespace phronCare.UIBlazor.Services.Stock throw new Exception($"{message}", ex); } } - } + public async Task DownloadTemplateAsync() + { + var response = await _http.GetAsync("/api/LSProduct/download-template"); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new Exception($"No se pudo descargar la plantilla: {errorContent}"); + } + + var bytes = await response.Content.ReadAsByteArrayAsync(); + var base64 = Convert.ToBase64String(bytes); + await _js.InvokeVoidAsync("saveAsFile", "plantilla_productos.xlsx", base64); + } + +} }