Add Export Exoeditions
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 21m48s

This commit is contained in:
Leandro Hernan Rojas 2025-09-09 23:54:11 -03:00
parent 813bcc24b1
commit c2bd8247a1
10 changed files with 157 additions and 18 deletions

View File

@ -23,5 +23,6 @@ namespace Core.Interfaces.Stock
ELSExpeditionHeader header, ELSExpeditionHeader header,
IEnumerable<ELSExpeditionDetail> details, IEnumerable<ELSExpeditionDetail> details,
int formSeriesId); int formSeriesId);
Task<byte[]> ExportFilteredToExcelAsync(ExpeditionSearchParams searchParams);
} }
} }

View File

@ -3,6 +3,8 @@ using Domain.Dtos.Stock;
using Domain.Entities; using Domain.Entities;
using Domain.Generics; using Domain.Generics;
using Models.Interfaces; using Models.Interfaces;
using System.Reflection;
using Transversal.Services;
namespace Core.Services.Stock namespace Core.Services.Stock
{ {
@ -47,5 +49,53 @@ namespace Core.Services.Stock
=> _repo.SearchAsync(expeditionNumber, status, issueDateFrom, issueDateTo, locationId, page, pageSize); => _repo.SearchAsync(expeditionNumber, status, issueDateFrom, issueDateTo, locationId, page, pageSize);
public Task<ExpeditionDto?> GetDtoByIdAsync(int id) public Task<ExpeditionDto?> GetDtoByIdAsync(int id)
=> _repo.GetDtoByIdAsync(id); => _repo.GetDtoByIdAsync(id);
public async Task<byte[]> ExportFilteredToExcelAsync(ExpeditionSearchParams searchParams)
{
try
{
// Realiza la búsqueda de clientes con los parámetros proporcionados
var searchResult = await SearchAsync(
searchParams.Number,
searchParams.Status,
searchParams.From,
searchParams.To,
searchParams.LocationId,
searchParams.Page,
searchParams.PageSize
);
// Verifica que se hayan encontrado resultados
if (searchResult?.Items is null || !searchResult.Items.Any())
{
throw new Exception("No se encontraron clientes para exportar.");
}
// Llamamos a un método que exporta los datos a Excel
var stream = new XLSXExportBase();
// Convertimos los resultados de la búsqueda a un formato adecuado para el exportador
var items = searchResult.Items.Select(c => new
{
c.Expeditionnumber,
Issuedate = c.Issuedate.ToString("yyyy-MM-dd"), // ← string
Createdat = c.Createdat.ToString("yyyy-MM-dd HH:mm"), // ← string
c.Status,
c.LocationId,
c.ExternalReference,
c.TicketId,
c.ExtrainfoJson,
c.Observations,
c.TotalItems
}).ToList();
// Genera el archivo Excel
var excelFile = stream.ExportExcel(items);
// Devuelve el archivo Excel como un array de bytes
return excelFile;
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
throw new Exception($"{ex.Message}", ex);
}
}
} }
} }

View File

@ -0,0 +1,12 @@
namespace Domain.Generics
{
public class ExpeditionSearchParams:PagedRequest
{
public string? Number { get; set; }
public string? Status { get; set; }
public DateTime? From { get; set; }
public DateTime? To { get; set; }
public int? LocationId { get; set; }
}
}

View File

@ -10,7 +10,7 @@ namespace Transversal.Services
ExcelPackage.LicenseContext = LicenseContext.NonCommercial; ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage()) using (var package = new ExcelPackage())
{ {
var worksheet = package.Workbook.Worksheets.Add("Datos"); var worksheet = package.Workbook.Worksheets.Add("datos");
// Obtener las propiedades de T // Obtener las propiedades de T
var propiedades = typeof(T).GetProperties(); var propiedades = typeof(T).GetProperties();

View File

@ -81,8 +81,6 @@ namespace phronCare.API.Controllers.Stock
if (dto is null) return NotFound($"No se encontró expedición {expeditionNumber}."); if (dto is null) return NotFound($"No se encontró expedición {expeditionNumber}.");
return Ok(dto); return Ok(dto);
} }
#region Endpoint de emision de expedicion (encabezado + detalles) #region Endpoint de emision de expedicion (encabezado + detalles)
[HttpPost("createfull")] [HttpPost("createfull")]
public async Task<IActionResult> CreateFullExpedition([FromBody] CreateFullExpeditionRequest request) public async Task<IActionResult> CreateFullExpedition([FromBody] CreateFullExpeditionRequest request)
@ -111,7 +109,6 @@ namespace phronCare.API.Controllers.Stock
} }
} }
#endregion #endregion
/// <summary> /// <summary>
/// Genera y devuelve un archivo PDF correspondiente al presupuesto especificado por su ID. /// Genera y devuelve un archivo PDF correspondiente al presupuesto especificado por su ID.
/// </summary> /// </summary>
@ -131,6 +128,22 @@ namespace phronCare.API.Controllers.Stock
return File(pdfBytes, "application/pdf", $"Expedicion_{expedition.Expeditionnumber}.pdf"); return File(pdfBytes, "application/pdf", $"Expedicion_{expedition.Expeditionnumber}.pdf");
} }
[HttpPost("exportfiltered")]
public async Task<IActionResult> ExportFiltered([FromBody] ExpeditionSearchParams searchParams)
{
try
{
var file = await _expeditionService.ExportFilteredToExcelAsync(searchParams);
return File(file,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Expediciones.xlsx");
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
} }
public class CreateFullExpeditionRequest public class CreateFullExpeditionRequest
{ {

View File

@ -694,6 +694,22 @@
], ],
"ReturnTypes": [] "ReturnTypes": []
}, },
{
"ContainingType": "phronCare.API.Controllers.Stock.ExpeditionController",
"Method": "ExportFiltered",
"RelativePath": "api/Expedition/exportfiltered",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "searchParams",
"Type": "Domain.Generics.ExpeditionSearchParams",
"IsRequired": true
}
],
"ReturnTypes": []
},
{ {
"ContainingType": "phronCare.API.Controllers.Stock.ExpeditionController", "ContainingType": "phronCare.API.Controllers.Stock.ExpeditionController",
"Method": "Search", "Method": "Search",

View File

@ -5,7 +5,7 @@
@using System.Text.Json @using System.Text.Json
@using phronCare.UIBlazor.Services.Stock.Expeditions @using phronCare.UIBlazor.Services.Stock.Expeditions
@inject ExpeditionService expeditionService @inject IExpeditionService expeditionService
@inject NavigationManager Nav @inject NavigationManager Nav
@inject IToastService Toast @inject IToastService Toast
@ -71,14 +71,13 @@
</button> </button>
<button class="btn btn-success rounded-pill" @onclick="Create"> <button class="btn btn-success rounded-pill" @onclick="Create">
<i class="fas fa-plus me-1"></i> Nuevo <i class="fas fa-plus me-1"></i> Nuevo
</button>
<button class="btn btn-success rounded-pill" @onclick="ExportarExcel">
<i class="fas fa-file-excel me-1"></i> Excel
</button> </button>
<button class="btn btn-secondary rounded-pill" @onclick="Clear"> <button class="btn btn-secondary rounded-pill" @onclick="Clear">
<i class="fas fa-arrow-left me-1"></i> Volver <i class="fas fa-arrow-left me-1"></i> Volver
</button> </button>
<button class="btn btn-success rounded-pill" @onclick="ExportCurrent">
<i class="fas fa-file-excel me-1"></i> Excel
</button>
</div> </div>
</EditForm> </EditForm>
</div> </div>
@ -167,6 +166,8 @@
Loading="@loadingDetail" /> Loading="@loadingDetail" />
@code { @code {
private LSProductSearchParams SearchParams = new() { Page = 1, PageSize = 10 };
private Filters filters = new(); private Filters filters = new();
private PagedResult<ExpeditionDto>? result; private PagedResult<ExpeditionDto>? result;
private int page = 1; private int page = 1;
@ -225,11 +226,26 @@
Nav.NavigateTo("/expeditions/create"); Nav.NavigateTo("/expeditions/create");
} }
private async Task ExportCurrent() private async Task ExportarExcel()
{ {
// Opcional: /api/lsm/expeditions/export?{filtros} SearchParams.Page = 1;
Toast.ShowInfo("Export en preparación."); SearchParams.PageSize = int.MaxValue; // Exportar todos los resultados
} try
{
await expeditionService.ExportFilteredAsync(SearchParams);
Toast.ShowSuccess("Exportación completada.");
}
catch (Exception ex)
{
Toast.ShowError($"Error: {ex.Message}");
}
}
// private async Task ExportCurrent()
// {
// // Opcional: /api/lsm/expeditions/export?{filtros}
// Toast.ShowInfo("Export en preparación.");
// }
private async Task ViewPdf(int id, string number) private async Task ViewPdf(int id, string number)
{ {

View File

@ -53,6 +53,7 @@ static void InjectDependencies(WebAssemblyHostBuilder builder)
builder.Services.AddScoped<IStockLookUpService, StockLookUpService>(); builder.Services.AddScoped<IStockLookUpService, StockLookUpService>();
builder.Services.AddScoped<IExchangeRateService, ExchangeRateService>(); builder.Services.AddScoped<IExchangeRateService, ExchangeRateService>();
builder.Services.AddScoped<IStockScanService, StockScanService>(); builder.Services.AddScoped<IStockScanService, StockScanService>();
builder.Services.AddScoped<IExpeditionService, ExpeditionService>();
builder.Services.AddScoped<ExchangeRateService>(); builder.Services.AddScoped<ExchangeRateService>();
builder.Services.AddScoped<QuoteService>(); builder.Services.AddScoped<QuoteService>();

View File

@ -4,6 +4,9 @@ using Domain.Entities;
using Domain.Generics; using Domain.Generics;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Reflection;
using System.Text;
using System.Text.Json;
namespace phronCare.UIBlazor.Services.Stock.Expeditions namespace phronCare.UIBlazor.Services.Stock.Expeditions
{ {
@ -53,6 +56,7 @@ namespace phronCare.UIBlazor.Services.Stock.Expeditions
var result = await _http.GetFromJsonAsync<PagedResult<ExpeditionDto>>(url); var result = await _http.GetFromJsonAsync<PagedResult<ExpeditionDto>>(url);
return result!; return result!;
} }
/// <summary> /// <summary>
/// Obtiene un presupuesto por QuoteNumber. /// Obtiene un presupuesto por QuoteNumber.
/// </summary> /// </summary>
@ -148,10 +152,37 @@ namespace phronCare.UIBlazor.Services.Stock.Expeditions
throw new Exception($"ExportPdfAsync: {message}", ex); throw new Exception($"ExportPdfAsync: {message}", ex);
} }
} }
public async Task ExportFilteredAsync(LSProductSearchParams searchParams)
{
try
{
var content = new StringContent(JsonSerializer.Serialize(searchParams), Encoding.UTF8, "application/json");
var response = await _http.PostAsync("api/Expedition/exportfiltered", content);
//response.EnsureSuccessStatusCode();
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
throw new Exception(errorContent);
}
var bytes = await response.Content.ReadAsByteArrayAsync();
var base64 = Convert.ToBase64String(bytes);
var timestamp = DateTime.Now.ToString("yyyyMMddHHmm");
var fileName = $"{timestamp}_expeditions.xlsx";
await _js.InvokeVoidAsync("saveAsFile", fileName, base64);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
var message = ex.Message ?? "No message provided";
throw new Exception($"{message}", ex);
}
}
} }
/// <summary> /// <summary>
/// Contrato de request simétrico a CreateFullQuoteRequest. /// Contrato de request.
/// </summary> /// </summary>
public class CreateFullExpeditionRequest public class CreateFullExpeditionRequest
{ {
@ -160,7 +191,7 @@ namespace phronCare.UIBlazor.Services.Stock.Expeditions
} }
/// <summary> /// <summary>
/// Resultado del create/issue simétrico a CreateQuoteResult. /// Resultado del create/issue.
/// </summary> /// </summary>
public class CreateExpeditionResult public class CreateExpeditionResult
{ {
@ -170,6 +201,4 @@ namespace phronCare.UIBlazor.Services.Stock.Expeditions
public string ErrorMessage { get; set; } = string.Empty; public string ErrorMessage { get; set; } = string.Empty;
} }
// TODO: Ajustar namespace real si es distinto
} }

View File

@ -10,5 +10,6 @@ namespace phronCare.UIBlazor.Services.Stock.Expeditions
Task<ExpeditionDto?> GetDtoByIdAsync(int id); Task<ExpeditionDto?> GetDtoByIdAsync(int id);
Task<QuoteDto?> GetQuoteByNumberAsync(string quoteNumber); Task<QuoteDto?> GetQuoteByNumberAsync(string quoteNumber);
Task<PagedResult<ExpeditionDto>> SearchAsync(string? expeditionNumber = null, string? status = null, DateTime? issueDateFrom = null, DateTime? issueDateTo = null, int? locationId = null, int page = 1, int pageSize = 10); Task<PagedResult<ExpeditionDto>> SearchAsync(string? expeditionNumber = null, string? status = null, DateTime? issueDateFrom = null, DateTime? issueDateTo = null, int? locationId = null, int page = 1, int pageSize = 10);
Task ExportFilteredAsync(LSProductSearchParams searchParams);
} }
} }