Add Massive Import Products
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 26m25s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 26m25s
This commit is contained in:
parent
27439cbd95
commit
369190695b
@ -1,4 +1,5 @@
|
|||||||
using Domain.Entities;
|
using Domain.Dtos.Stock;
|
||||||
|
using Domain.Entities;
|
||||||
using Domain.Generics;
|
using Domain.Generics;
|
||||||
|
|
||||||
namespace Core.Interfaces
|
namespace Core.Interfaces
|
||||||
@ -12,5 +13,8 @@ namespace Core.Interfaces
|
|||||||
Task<bool> DeleteAsync(int id);
|
Task<bool> DeleteAsync(int id);
|
||||||
Task<byte[]> ExportToExcelAsync(LSProductSearchParams searchParams);
|
Task<byte[]> ExportToExcelAsync(LSProductSearchParams searchParams);
|
||||||
byte[] GetImportTemplate();
|
byte[] GetImportTemplate();
|
||||||
|
List<ProductImportPreviewDto> PreviewImportFromExcel(byte[] fileBytes);
|
||||||
|
Task<ProductImportResultDto> ImportProductsAsync(List<ProductImportPreviewDto> items);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
using Core.Interfaces;
|
using Core.Interfaces;
|
||||||
|
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 System.Reflection;
|
||||||
|
using Transversal.Interfaces;
|
||||||
using Transversal.Services;
|
using Transversal.Services;
|
||||||
|
|
||||||
namespace Core.Services
|
namespace Core.Services
|
||||||
@ -10,10 +12,20 @@ namespace Core.Services
|
|||||||
public class LSProductService : ILSProductDom
|
public class LSProductService : ILSProductDom
|
||||||
{
|
{
|
||||||
private readonly IPhLSMProductRepository _repository;
|
private readonly IPhLSMProductRepository _repository;
|
||||||
|
private readonly IPhLSMProductDivisionRepository _divisionRepo;
|
||||||
|
private readonly IPhLSMUnitOfMeasureRepository _unitRepo;
|
||||||
|
|
||||||
public LSProductService(IPhLSMProductRepository repository)
|
private List<string> _divisionCodes = [];
|
||||||
|
private List<string> _unitCodes = [];
|
||||||
|
|
||||||
|
public LSProductService(
|
||||||
|
IPhLSMProductRepository repository,
|
||||||
|
IPhLSMProductDivisionRepository divisionRepo ,
|
||||||
|
IPhLSMUnitOfMeasureRepository unitRepo)
|
||||||
{
|
{
|
||||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||||
|
_divisionRepo = divisionRepo ?? throw new ArgumentNullException(nameof(divisionRepo));
|
||||||
|
_unitRepo = unitRepo ?? throw new ArgumentNullException(nameof(unitRepo));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PagedResult<ELSProduct>> SearchAsync(LSProductSearchParams searchParams)
|
public async Task<PagedResult<ELSProduct>> SearchAsync(LSProductSearchParams searchParams)
|
||||||
@ -86,7 +98,44 @@ namespace Core.Services
|
|||||||
|
|
||||||
return File.ReadAllBytes(path);
|
return File.ReadAllBytes(path);
|
||||||
}
|
}
|
||||||
|
public List<ProductImportPreviewDto> PreviewImportFromExcel(byte[] fileBytes)
|
||||||
|
{
|
||||||
|
var importador = new XLSXImportBase();
|
||||||
|
LoadReferenceCodes(); // Cargar códigos de referencia antes de procesar el archivo
|
||||||
|
var rawItems = importador.ReadProductImport(fileBytes);
|
||||||
|
foreach (var item in rawItems)
|
||||||
|
{
|
||||||
|
// Validaciones de dominio
|
||||||
|
if (!_divisionCodes.Contains(item.DivisionCode))
|
||||||
|
item.ErrorMessage = "División no reconocida";
|
||||||
|
else if (!_unitCodes.Contains(item.UnitCode))
|
||||||
|
item.ErrorMessage = "Unidad no reconocida";
|
||||||
|
else if (item.ProductType < 1 || item.ProductType > 3)
|
||||||
|
item.ErrorMessage = "Tipo de producto inválido";
|
||||||
|
// Agregar validaciones extras...
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawItems;
|
||||||
|
}
|
||||||
|
private void LoadReferenceCodes()
|
||||||
|
{
|
||||||
|
_divisionCodes = _divisionRepo.GetAllAsync().Result.Items.Select(x => x.Code).ToList();
|
||||||
|
_unitCodes = _unitRepo.GetAllAsync().Result.Items.Select(x => x.Code).ToList();
|
||||||
|
}
|
||||||
|
public async Task<ProductImportResultDto> ImportProductsAsync(List<ProductImportPreviewDto> items)
|
||||||
|
{
|
||||||
|
if (items == null || !items.Any())
|
||||||
|
return new ProductImportResultDto { Inserted = 0, Skipped = 0 };
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _repository.ImportProductsAsync(items);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var method = MethodBase.GetCurrentMethod()?.Name ?? "ImportProductsAsync";
|
||||||
|
throw new Exception($"Error en {method}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
Domain/Dtos/Stock/ProductImportPreviewDto.cs
Normal file
18
Domain/Dtos/Stock/ProductImportPreviewDto.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Domain.Dtos.Stock
|
||||||
|
{
|
||||||
|
public class ProductImportPreviewDto
|
||||||
|
{
|
||||||
|
public string FactoryCode { get; set; } = string.Empty;
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public int ProductType { get; set; }
|
||||||
|
public int TraceabilityType { get; set; }
|
||||||
|
public string DivisionCode { get; set; } = string.Empty;
|
||||||
|
public string UnitCode { get; set; } = string.Empty;
|
||||||
|
public bool PlusProcess { get; set; }
|
||||||
|
public string ExternalCode { get; set; } = string.Empty;
|
||||||
|
public string? ErrorMessage { get; set; }
|
||||||
|
public bool HasError => !string.IsNullOrWhiteSpace(ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
9
Domain/Dtos/Stock/ProductImportResultDto.cs
Normal file
9
Domain/Dtos/Stock/ProductImportResultDto.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Domain.Dtos.Stock
|
||||||
|
{
|
||||||
|
public class ProductImportResultDto
|
||||||
|
{
|
||||||
|
public int Inserted { get; set; }
|
||||||
|
public int Skipped { get; set; }
|
||||||
|
public List<string>? Errors { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,9 @@ namespace Models.Interfaces
|
|||||||
{
|
{
|
||||||
Task<ELSProductDivision> CreateAsync(ELSProductDivision entity);
|
Task<ELSProductDivision> CreateAsync(ELSProductDivision entity);
|
||||||
Task<bool> DeleteAsync(int id);
|
Task<bool> DeleteAsync(int id);
|
||||||
|
Task<bool> ExistsByCodeAsync(string code);
|
||||||
Task<PagedResult<ELSProductDivision>> GetAllAsync(int page = 1, int pageSize = 50);
|
Task<PagedResult<ELSProductDivision>> GetAllAsync(int page = 1, int pageSize = 50);
|
||||||
|
Task<List<string>> GetAllCodesAsync();
|
||||||
Task<ELSProductDivision?> GetByIdAsync(int id);
|
Task<ELSProductDivision?> GetByIdAsync(int id);
|
||||||
Task<PagedResult<ELSProductDivision>> SearchAsync(string? term, int page = 1, int pageSize = 50);
|
Task<PagedResult<ELSProductDivision>> SearchAsync(string? term, int page = 1, int pageSize = 50);
|
||||||
Task<bool> UpdateAsync(ELSProductDivision entity);
|
Task<bool> UpdateAsync(ELSProductDivision entity);
|
||||||
|
|||||||
@ -1,15 +1,52 @@
|
|||||||
using Domain.Entities;
|
using Domain.Dtos.Stock;
|
||||||
|
using Domain.Entities;
|
||||||
using Domain.Generics;
|
using Domain.Generics;
|
||||||
using Models.Models;
|
|
||||||
|
|
||||||
namespace Models.Interfaces
|
namespace Models.Interfaces
|
||||||
{
|
{
|
||||||
public interface IPhLSMProductRepository
|
public interface IPhLSMProductRepository
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Realiza una búsqueda paginada de productos según los parámetros provistos.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="searchParams">Parámetros de búsqueda y paginación.</param>
|
||||||
|
/// <returns>Página de productos que cumplen con los filtros.</returns>
|
||||||
Task<PagedResult<ELSProduct>> SearchAsync(LSProductSearchParams searchParams);
|
Task<PagedResult<ELSProduct>> SearchAsync(LSProductSearchParams searchParams);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene un producto por su identificador único.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">ID del producto.</param>
|
||||||
|
/// <returns>Producto encontrado o null si no existe.</returns>
|
||||||
Task<ELSProduct?> GetByIdAsync(int id);
|
Task<ELSProduct?> GetByIdAsync(int id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserta una lista de productos importados. Devuelve la cantidad de insertados y los omitidos/skipped.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="items">Lista de productos a importar (vista previa validada).</param>
|
||||||
|
/// <returns>Resultado de la importación con cantidades y errores.</returns>
|
||||||
|
Task<ProductImportResultDto> ImportProductsAsync(List<ProductImportPreviewDto> items);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un nuevo producto en la base de datos.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">Entidad de producto a crear.</param>
|
||||||
|
/// <returns>Producto creado.</returns>
|
||||||
Task<ELSProduct> CreateAsync(ELSProduct entity);
|
Task<ELSProduct> CreateAsync(ELSProduct entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza un producto existente.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">Entidad de producto con los datos actualizados.</param>
|
||||||
|
/// <returns>True si la actualización fue exitosa.</returns>
|
||||||
Task<bool> UpdateAsync(ELSProduct entity);
|
Task<bool> UpdateAsync(ELSProduct entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Elimina un producto por su identificador único.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">ID del producto a eliminar.</param>
|
||||||
|
/// <returns>True si la eliminación fue exitosa.</returns>
|
||||||
Task<bool> DeleteAsync(int id);
|
Task<bool> DeleteAsync(int id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
Models/Interfaces/IPhLSMUnitOfMeasureRepository.cs
Normal file
16
Models/Interfaces/IPhLSMUnitOfMeasureRepository.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using Domain.Entities;
|
||||||
|
using Domain.Generics;
|
||||||
|
|
||||||
|
namespace Models.Interfaces
|
||||||
|
{
|
||||||
|
public interface IPhLSMUnitOfMeasureRepository
|
||||||
|
{
|
||||||
|
Task<PagedResult<ELSUnitOfMeasure>> GetAllAsync(int page = 1, int pageSize = 100);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retorna true si el código existe
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> ExistsByCodeAsync(string code);
|
||||||
|
Task<List<string>> GetAllCodesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -107,5 +107,19 @@ namespace Models.Repositories.Stock
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public async Task<List<string>> GetAllCodesAsync()
|
||||||
|
{
|
||||||
|
return await _context.PhLsmProductDivisions
|
||||||
|
.Where(x => !string.IsNullOrEmpty(x.Code))
|
||||||
|
.Select(x => x.Code!)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsByCodeAsync(string code)
|
||||||
|
{
|
||||||
|
return await _context.PhLsmProductDivisions
|
||||||
|
.AnyAsync(x => x.Code != null && x.Code == code);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Domain.Entities;
|
using Domain.Dtos.Stock;
|
||||||
|
using Domain.Entities;
|
||||||
using Domain.Generics;
|
using Domain.Generics;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Models.Helpers;
|
using Models.Helpers;
|
||||||
@ -97,5 +98,72 @@ namespace Models.Repositories
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ProductImportResultDto> ImportProductsAsync(List<ProductImportPreviewDto> items)
|
||||||
|
{
|
||||||
|
// 1. Prevenir nulos/vacíos
|
||||||
|
if (items == null || items.Count == 0)
|
||||||
|
return new ProductImportResultDto { Inserted = 0, Skipped = 0 };
|
||||||
|
|
||||||
|
// 2. Obtener todos los códigos únicos de División y Unidad
|
||||||
|
var divisionCodes = items.Select(x => x.DivisionCode).Distinct().ToList();
|
||||||
|
var unitCodes = items.Select(x => x.UnitCode).Distinct().ToList();
|
||||||
|
|
||||||
|
// 3. Mapear a IDs desde la base
|
||||||
|
var divisionMap = await _context.PhLsmProductDivisions
|
||||||
|
.Where(d => divisionCodes.Contains(d.Code))
|
||||||
|
.ToDictionaryAsync(d => d.Code, d => d.Id);
|
||||||
|
|
||||||
|
var unitMap = await _context.PhLsmUnitOfMeasures
|
||||||
|
.Where(u => unitCodes.Contains(u.Code))
|
||||||
|
.ToDictionaryAsync(u => u.Code, u => u.Id);
|
||||||
|
|
||||||
|
// 4. Armar entidades para insertar (sólo si las FK existen)
|
||||||
|
var toInsert = new List<PhLsmProduct>();
|
||||||
|
int skipped = 0;
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
// Validaciones de existencia de Division y Unidad
|
||||||
|
if (!divisionMap.TryGetValue(item.DivisionCode, out var divisionId)
|
||||||
|
|| !unitMap.TryGetValue(item.UnitCode, out var unitId))
|
||||||
|
{
|
||||||
|
skipped++;
|
||||||
|
continue; // Saltea el producto si alguna FK no existe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Armá la entidad
|
||||||
|
var entity = new PhLsmProduct
|
||||||
|
{
|
||||||
|
FactoryCode = item.FactoryCode,
|
||||||
|
Name = item.Name,
|
||||||
|
Descripcion = item.Description,
|
||||||
|
ProductType = item.ProductType,
|
||||||
|
TraceabilityType = item.TraceabilityType,
|
||||||
|
DivisionId = divisionId,
|
||||||
|
UnitId = unitId,
|
||||||
|
PlusProcess = item.PlusProcess,
|
||||||
|
ExternalCode = item.ExternalCode,
|
||||||
|
// otros campos...
|
||||||
|
};
|
||||||
|
|
||||||
|
toInsert.Add(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Insertar en batch
|
||||||
|
if (toInsert.Count > 0)
|
||||||
|
{
|
||||||
|
_context.PhLsmProducts.AddRange(toInsert);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Retornar resumen
|
||||||
|
return new ProductImportResultDto
|
||||||
|
{
|
||||||
|
Inserted = toInsert.Count,
|
||||||
|
Skipped = skipped
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
Models/Repositories/Stock/PhLSMUnitOfMeasureRepository.cs
Normal file
41
Models/Repositories/Stock/PhLSMUnitOfMeasureRepository.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using Domain.Entities;
|
||||||
|
using Domain.Generics;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Models.Helpers;
|
||||||
|
using Models.Interfaces;
|
||||||
|
using Models.Models;
|
||||||
|
|
||||||
|
namespace Models.Repositories.Stock
|
||||||
|
{
|
||||||
|
public class PhLSMUnitOfMeasureRepository(PhronCareOperationsHubContext context) : IPhLSMUnitOfMeasureRepository
|
||||||
|
{
|
||||||
|
private readonly PhronCareOperationsHubContext _context = context;
|
||||||
|
public async Task<PagedResult<ELSUnitOfMeasure>> GetAllAsync(int page = 1, int pageSize = 100)
|
||||||
|
{
|
||||||
|
var query = _context.PhLsmUnitOfMeasures.AsQueryable();
|
||||||
|
var paged = await query.ToPagedResultAsync(page, pageSize);
|
||||||
|
|
||||||
|
return new PagedResult<ELSUnitOfMeasure>
|
||||||
|
{
|
||||||
|
Items = paged.Items.Select(EntityMapper.MapEntity<PhLsmUnitOfMeasure, ELSUnitOfMeasure>),
|
||||||
|
TotalItems = paged.TotalItems,
|
||||||
|
Page = paged.Page,
|
||||||
|
PageSize = paged.PageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public async Task<bool> ExistsByCodeAsync(string code)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(code))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return await _context.PhLsmUnitOfMeasures
|
||||||
|
.AnyAsync(x => x.Code.ToLower() == code.ToLower());
|
||||||
|
}
|
||||||
|
public async Task<List<string>> GetAllCodesAsync()
|
||||||
|
{
|
||||||
|
return await _context.PhLsmUnitOfMeasures
|
||||||
|
.Select(x => x.Code)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Transversal/Interfaces/IXLSXImportBase.cs
Normal file
10
Transversal/Interfaces/IXLSXImportBase.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Transversal/Interfaces/IXLSXImportBase.cs
|
||||||
|
using Domain.Dtos.Stock;
|
||||||
|
|
||||||
|
namespace Transversal.Interfaces
|
||||||
|
{
|
||||||
|
public interface IXLSXImportBase
|
||||||
|
{
|
||||||
|
List<ProductImportPreviewDto> ReadProductImport(byte[] fileBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Transversal/Services/XLSXImportBase.cs
Normal file
48
Transversal/Services/XLSXImportBase.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using OfficeOpenXml;
|
||||||
|
using Domain.Dtos.Stock;
|
||||||
|
using Transversal.Interfaces;
|
||||||
|
|
||||||
|
namespace Transversal.Services
|
||||||
|
{
|
||||||
|
public class XLSXImportBase : IXLSXImportBase
|
||||||
|
{
|
||||||
|
public List<ProductImportPreviewDto> ReadProductImport(byte[] fileBytes)
|
||||||
|
{
|
||||||
|
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
|
||||||
|
|
||||||
|
var result = new List<ProductImportPreviewDto>();
|
||||||
|
|
||||||
|
using var stream = new MemoryStream(fileBytes);
|
||||||
|
using var package = new ExcelPackage(stream);
|
||||||
|
|
||||||
|
var worksheet = package.Workbook.Worksheets.FirstOrDefault();
|
||||||
|
if (worksheet == null)
|
||||||
|
throw new Exception("El archivo no contiene hojas válidas.");
|
||||||
|
|
||||||
|
int row = 2;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var factoryCode = worksheet.Cells[row, 1].Text?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(factoryCode)) break; // fin del archivo
|
||||||
|
|
||||||
|
var item = new ProductImportPreviewDto
|
||||||
|
{
|
||||||
|
FactoryCode = factoryCode,
|
||||||
|
Name = worksheet.Cells[row, 2].Text?.Trim(),
|
||||||
|
Description = worksheet.Cells[row, 3].Text?.Trim(),
|
||||||
|
ProductType = int.TryParse(worksheet.Cells[row, 4].Text, out var pt) ? pt : 0,
|
||||||
|
TraceabilityType = int.TryParse(worksheet.Cells[row, 5].Text, out var tt) ? tt : 0,
|
||||||
|
DivisionCode = worksheet.Cells[row, 6].Text?.Trim(),
|
||||||
|
UnitCode = worksheet.Cells[row, 7].Text?.Trim(),
|
||||||
|
PlusProcess = worksheet.Cells[row, 8].Text.Trim().ToLower() == "sí" || worksheet.Cells[row, 8].Text.Trim().ToLower() == "si",
|
||||||
|
ExternalCode = worksheet.Cells[row, 9].Text?.Trim(),
|
||||||
|
};
|
||||||
|
|
||||||
|
result.Add(item);
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,4 +11,8 @@
|
|||||||
<PackageReference Include="PuppeteerSharp" Version="6.0.0" />
|
<PackageReference Include="PuppeteerSharp" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Domain\Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Core.Interfaces;
|
using Core.Interfaces;
|
||||||
|
using Domain.Dtos.Stock;
|
||||||
using Domain.Entities;
|
using Domain.Entities;
|
||||||
using Domain.Generics;
|
using Domain.Generics;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -73,5 +74,36 @@ namespace API.Controllers.Stock
|
|||||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
"plantilla_productos.xlsx");
|
"plantilla_productos.xlsx");
|
||||||
}
|
}
|
||||||
|
[HttpPost("preview-import")]
|
||||||
|
public ActionResult<List<ProductImportPreviewDto>> PreviewImport([FromForm] ProductImportFormDto dto)
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
dto.File.CopyTo(ms);
|
||||||
|
var fileBytes = ms.ToArray();
|
||||||
|
|
||||||
|
var preview = _service.PreviewImportFromExcel(fileBytes);
|
||||||
|
return Ok(preview);
|
||||||
|
}
|
||||||
|
public class ProductImportFormDto
|
||||||
|
{
|
||||||
|
public IFormFile File { get; set; } = default!;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Importa productos desde la vista previa. Espera una lista de ProductImportPreviewDto SIN errores.
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("import")]
|
||||||
|
public async Task<ActionResult<ProductImportResultDto>> ImportProducts([FromBody] List<ProductImportPreviewDto> items)
|
||||||
|
{
|
||||||
|
if (items == null || !items.Any())
|
||||||
|
return BadRequest("No se enviaron productos para importar.");
|
||||||
|
|
||||||
|
// Opcional: validación de errores
|
||||||
|
if (items.Any(x => x.HasError))
|
||||||
|
return BadRequest("Hay productos con errores. Corríjalos antes de importar.");
|
||||||
|
|
||||||
|
var result = await _service.ImportProductsAsync(items);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
phronCare.API/Helpers/FileUploadOperationFilter.cs
Normal file
34
phronCare.API/Helpers/FileUploadOperationFilter.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
|
public class FileUploadOperationFilter : IOperationFilter
|
||||||
|
{
|
||||||
|
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||||
|
{
|
||||||
|
var hasFormFile = context.MethodInfo.GetParameters()
|
||||||
|
.Any(p => p.ParameterType == typeof(IFormFile));
|
||||||
|
|
||||||
|
if (!hasFormFile) return;
|
||||||
|
|
||||||
|
operation.RequestBody = new OpenApiRequestBody
|
||||||
|
{
|
||||||
|
Content = {
|
||||||
|
["multipart/form-data"] = new OpenApiMediaType
|
||||||
|
{
|
||||||
|
Schema = new OpenApiSchema
|
||||||
|
{
|
||||||
|
Type = "object",
|
||||||
|
Properties = {
|
||||||
|
["file"] = new OpenApiSchema
|
||||||
|
{
|
||||||
|
Type = "string",
|
||||||
|
Format = "binary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Required = { "file" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -132,6 +132,8 @@ builder.Services.AddSwaggerGen(option =>
|
|||||||
new string[]{}
|
new string[]{}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
//option.OperationFilter<FileUploadOperationFilter>();
|
||||||
|
|
||||||
});
|
});
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -265,4 +267,6 @@ static void RepositorysAndServices(WebApplicationBuilder builder)
|
|||||||
|
|
||||||
builder.Services.AddScoped<IPhLSMLookUpRepository, PhLSMLookUpRepository>();
|
builder.Services.AddScoped<IPhLSMLookUpRepository, PhLSMLookUpRepository>();
|
||||||
builder.Services.AddScoped<ILSMLookUpDom, LSMLookUpService>();
|
builder.Services.AddScoped<ILSMLookUpDom, LSMLookUpService>();
|
||||||
|
builder.Services.AddScoped<IPhLSMUnitOfMeasureRepository, PhLSMUnitOfMeasureRepository>();
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1106,6 +1106,58 @@
|
|||||||
],
|
],
|
||||||
"ReturnTypes": []
|
"ReturnTypes": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ContainingType": "API.Controllers.Stock.LSProductController",
|
||||||
|
"Method": "ImportProducts",
|
||||||
|
"RelativePath": "api/LSProduct/import",
|
||||||
|
"HttpMethod": "POST",
|
||||||
|
"IsController": true,
|
||||||
|
"Order": 0,
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "items",
|
||||||
|
"Type": "System.Collections.Generic.List\u00601[[Domain.Dtos.Stock.ProductImportPreviewDto, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]",
|
||||||
|
"IsRequired": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnTypes": [
|
||||||
|
{
|
||||||
|
"Type": "Domain.Dtos.Stock.ProductImportResultDto",
|
||||||
|
"MediaTypes": [
|
||||||
|
"text/plain",
|
||||||
|
"application/json",
|
||||||
|
"text/json"
|
||||||
|
],
|
||||||
|
"StatusCode": 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ContainingType": "API.Controllers.Stock.LSProductController",
|
||||||
|
"Method": "PreviewImport",
|
||||||
|
"RelativePath": "api/LSProduct/preview-import",
|
||||||
|
"HttpMethod": "POST",
|
||||||
|
"IsController": true,
|
||||||
|
"Order": 0,
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "File",
|
||||||
|
"Type": "Microsoft.AspNetCore.Http.IFormFile",
|
||||||
|
"IsRequired": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnTypes": [
|
||||||
|
{
|
||||||
|
"Type": "System.Collections.Generic.List\u00601[[Domain.Dtos.Stock.ProductImportPreviewDto, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]",
|
||||||
|
"MediaTypes": [
|
||||||
|
"text/plain",
|
||||||
|
"application/json",
|
||||||
|
"text/json"
|
||||||
|
],
|
||||||
|
"StatusCode": 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ContainingType": "API.Controllers.Stock.LSProductController",
|
"ContainingType": "API.Controllers.Stock.LSProductController",
|
||||||
"Method": "Search",
|
"Method": "Search",
|
||||||
|
|||||||
@ -591,7 +591,11 @@
|
|||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net8.0": {
|
"net8.0": {
|
||||||
"targetAlias": "net8.0",
|
"targetAlias": "net8.0",
|
||||||
"projectReferences": {}
|
"projectReferences": {
|
||||||
|
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\Domain.csproj": {
|
||||||
|
"projectPath": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\Domain.csproj"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warningProperties": {
|
"warningProperties": {
|
||||||
|
|||||||
@ -3294,6 +3294,7 @@
|
|||||||
"type": "project",
|
"type": "project",
|
||||||
"framework": ".NETCoreApp,Version=v8.0",
|
"framework": ".NETCoreApp,Version=v8.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"Domain": "1.0.0",
|
||||||
"EPPlus": "7.7.2",
|
"EPPlus": "7.7.2",
|
||||||
"PuppeteerSharp": "6.0.0"
|
"PuppeteerSharp": "6.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,6 +4,72 @@
|
|||||||
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\phronCare.Test\\phronCare.Test.csproj": {}
|
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\phronCare.Test\\phronCare.Test.csproj": {}
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
|
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\Domain.csproj": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"restore": {
|
||||||
|
"projectUniqueName": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\Domain.csproj",
|
||||||
|
"projectName": "Domain",
|
||||||
|
"projectPath": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\Domain.csproj",
|
||||||
|
"packagesPath": "C:\\Users\\maski\\.nuget\\packages\\",
|
||||||
|
"outputPath": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\obj\\",
|
||||||
|
"projectStyle": "PackageReference",
|
||||||
|
"fallbackFolders": [
|
||||||
|
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||||
|
],
|
||||||
|
"configFilePaths": [
|
||||||
|
"C:\\Users\\maski\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||||
|
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||||
|
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||||
|
],
|
||||||
|
"originalTargetFrameworks": [
|
||||||
|
"net8.0"
|
||||||
|
],
|
||||||
|
"sources": {
|
||||||
|
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||||
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
|
"https://api.nuget.org/v3/index.json": {}
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net8.0": {
|
||||||
|
"targetAlias": "net8.0",
|
||||||
|
"projectReferences": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"warningProperties": {
|
||||||
|
"warnAsError": [
|
||||||
|
"NU1605"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"restoreAuditProperties": {
|
||||||
|
"enableAudit": "true",
|
||||||
|
"auditLevel": "low",
|
||||||
|
"auditMode": "direct"
|
||||||
|
},
|
||||||
|
"SdkAnalysisLevel": "9.0.300"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"net8.0": {
|
||||||
|
"targetAlias": "net8.0",
|
||||||
|
"imports": [
|
||||||
|
"net461",
|
||||||
|
"net462",
|
||||||
|
"net47",
|
||||||
|
"net471",
|
||||||
|
"net472",
|
||||||
|
"net48",
|
||||||
|
"net481"
|
||||||
|
],
|
||||||
|
"assetTargetFallback": true,
|
||||||
|
"warn": true,
|
||||||
|
"frameworkReferences": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"privateAssets": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.300/PortableRuntimeIdentifierGraph.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\phronCare.Test\\phronCare.Test.csproj": {
|
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\phronCare.Test\\phronCare.Test.csproj": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"restore": {
|
"restore": {
|
||||||
@ -132,7 +198,11 @@
|
|||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net8.0": {
|
"net8.0": {
|
||||||
"targetAlias": "net8.0",
|
"targetAlias": "net8.0",
|
||||||
"projectReferences": {}
|
"projectReferences": {
|
||||||
|
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\Domain.csproj": {
|
||||||
|
"projectPath": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\Domain.csproj"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warningProperties": {
|
"warningProperties": {
|
||||||
|
|||||||
@ -911,10 +911,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Domain/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"framework": ".NETCoreApp,Version=v8.0",
|
||||||
|
"compile": {
|
||||||
|
"bin/placeholder/Domain.dll": {}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"bin/placeholder/Domain.dll": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Transversal/1.0.0": {
|
"Transversal/1.0.0": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"framework": ".NETCoreApp,Version=v8.0",
|
"framework": ".NETCoreApp,Version=v8.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"Domain": "1.0.0",
|
||||||
"EPPlus": "7.7.2",
|
"EPPlus": "7.7.2",
|
||||||
"PuppeteerSharp": "6.0.0"
|
"PuppeteerSharp": "6.0.0"
|
||||||
},
|
},
|
||||||
@ -2369,6 +2380,11 @@
|
|||||||
"version.txt"
|
"version.txt"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"Domain/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"path": "../Domain/Domain.csproj",
|
||||||
|
"msbuildProject": "../Domain/Domain.csproj"
|
||||||
|
},
|
||||||
"Transversal/1.0.0": {
|
"Transversal/1.0.0": {
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"path": "../Transversal/Transversal.csproj",
|
"path": "../Transversal/Transversal.csproj",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
@page "/stock/productimport"
|
@page "/stock/productimport"
|
||||||
|
@using Domain.Dtos.Stock
|
||||||
@using phronCare.UIBlazor.Services.Stock
|
@using phronCare.UIBlazor.Services.Stock
|
||||||
|
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@ -22,9 +23,11 @@
|
|||||||
|
|
||||||
@if (UploadedFile != null)
|
@if (UploadedFile != null)
|
||||||
{
|
{
|
||||||
|
<div class="mb-4">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<button class="btn btn-warning" @onclick="ProcessFile">Procesar archivo Excel</button>
|
<button class="btn btn-warning" @onclick="ProcessFile">Procesar archivo Excel</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (PreviewItems != null)
|
@if (PreviewItems != null)
|
||||||
@ -64,27 +67,17 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="mt-3 d-flex justify-content-end gap-2">
|
<div class="mt-3 d-flex justify-content-end">
|
||||||
<button class="btn btn-outline-secondary" @onclick="SimulateImport">Simular importación</button>
|
<button class="btn btn-success" @onclick="ConfirmImport" disabled="@(PreviewItems.Any(x => x.HasError))">
|
||||||
<button class="btn btn-success" @onclick="ConfirmImport" disabled="@(PreviewItems.All(x => x.HasError))">Importar productos</button>
|
Importar productos
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<ProductImportPreviewDto>? PreviewItems;
|
private List<ProductImportPreviewDto>? PreviewItems;
|
||||||
private IBrowserFile? UploadedFile;
|
private IBrowserFile? UploadedFile;
|
||||||
|
private bool _isUploading;
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
PreviewItems = new List<ProductImportPreviewDto>
|
|
||||||
{
|
|
||||||
new() { FactoryCode = "ZIM001", Name = "Clavo tibial largo", Description = "Clavo para tibia", ProductType = 1, TraceabilityType = 3, DivisionCode = "ZIM", UnitCode = "UN", PlusProcess = true, ExternalCode = "EXT001" },
|
|
||||||
new() { FactoryCode = "ZIM002", Name = "Tornillo esponjoso", Description = "Tornillo de 6.5mm", ProductType = 1, TraceabilityType = 3, DivisionCode = "ZIM", UnitCode = "UN", PlusProcess = false, ExternalCode = "EXT002" },
|
|
||||||
new() { FactoryCode = "ZIM003", Name = "Placa de compresión", Description = "Placa DCP 4.5", ProductType = 1, TraceabilityType = 2, DivisionCode = "ZIM", UnitCode = "UN", PlusProcess = false, ExternalCode = "EXT003" },
|
|
||||||
new() { FactoryCode = "ZIM004", Name = "Caja instrumental", Description = "Caja para implantes", ProductType = 2, TraceabilityType = 1, DivisionCode = "ZIM", UnitCode = "CJ", PlusProcess = false, ExternalCode = "EXT004", ErrorMessage = "Unidad no reconocida" },
|
|
||||||
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()
|
private async Task DownloadTemplate()
|
||||||
{
|
{
|
||||||
@ -98,48 +91,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleFileSelected(InputFileChangeEventArgs e)
|
private void HandleFileSelected(InputFileChangeEventArgs e)
|
||||||
{
|
{
|
||||||
UploadedFile = e.File;
|
UploadedFile = e.File;
|
||||||
//PreviewItems = null; // Limpiar preview anterior
|
PreviewItems = null; // limpiar vista previa anterior
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessFile()
|
private async Task ProcessFile()
|
||||||
{
|
{
|
||||||
if (UploadedFile == null) return;
|
if (UploadedFile == null) return;
|
||||||
|
|
||||||
using var stream = UploadedFile.OpenReadStream(10 * 1024 * 1024);
|
try
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
// Lógica futura para simular importación
|
_isUploading = true;
|
||||||
|
PreviewItems = await productService.PreviewImportAsync(UploadedFile);
|
||||||
|
toastService.ShowSuccess("Vista previa generada correctamente.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
toastService.ShowError($"Error al procesar el archivo: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isUploading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ConfirmImport()
|
private async Task ConfirmImport()
|
||||||
{
|
{
|
||||||
// Lógica futura para guardar los productos
|
if (PreviewItems is null || PreviewItems.Any(x => x.HasError))
|
||||||
|
{
|
||||||
|
toastService.ShowWarning("Existen errores; corríjalos antes de importar.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ProductImportPreviewDto
|
try
|
||||||
{
|
{
|
||||||
public string FactoryCode { get; set; } = string.Empty;
|
var result = await productService.ConfirmImportAsync(PreviewItems);
|
||||||
public string Name { get; set; } = string.Empty;
|
toastService.ShowSuccess($"Se importaron {result?.Inserted ?? 0} productos.");
|
||||||
public string Description { get; set; } = string.Empty;
|
UploadedFile = null;
|
||||||
public int ProductType { get; set; }
|
PreviewItems = null;
|
||||||
public int TraceabilityType { get; set; }
|
}
|
||||||
public string DivisionCode { get; set; } = string.Empty;
|
catch (Exception ex)
|
||||||
public string UnitCode { get; set; } = string.Empty;
|
{
|
||||||
public bool PlusProcess { get; set; }
|
toastService.ShowError($"Error al importar: {ex.Message}");
|
||||||
public string ExternalCode { get; set; } = string.Empty;
|
}
|
||||||
public string? ErrorMessage { get; set; }
|
|
||||||
public bool HasError => !string.IsNullOrWhiteSpace(ErrorMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
using Domain.Entities;
|
using Domain.Dtos.Stock;
|
||||||
|
using Domain.Entities;
|
||||||
using Domain.Generics;
|
using Domain.Generics;
|
||||||
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@ -87,6 +89,28 @@ namespace phronCare.UIBlazor.Services.Stock
|
|||||||
var base64 = Convert.ToBase64String(bytes);
|
var base64 = Convert.ToBase64String(bytes);
|
||||||
await _js.InvokeVoidAsync("saveAsFile", "plantilla_productos.xlsx", base64);
|
await _js.InvokeVoidAsync("saveAsFile", "plantilla_productos.xlsx", base64);
|
||||||
}
|
}
|
||||||
|
public async Task<List<ProductImportPreviewDto>> PreviewImportAsync(IBrowserFile file)
|
||||||
|
{
|
||||||
|
using var content = new MultipartFormDataContent();
|
||||||
|
var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024); // 10MB
|
||||||
|
content.Add(new StreamContent(stream), "file", file.Name);
|
||||||
|
|
||||||
|
var response = await _http.PostAsync("api/LSProduct/preview-import", content);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<List<ProductImportPreviewDto>>();
|
||||||
|
return result ?? new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProductImportResultDto?> ConfirmImportAsync(IEnumerable<ProductImportPreviewDto> items)
|
||||||
|
{
|
||||||
|
// solo envía los registros válidos
|
||||||
|
var valid = items.Where(x => !x.HasError).ToList();
|
||||||
|
|
||||||
|
var response = await _http.PostAsJsonAsync("api/LSProduct/import", valid);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadFromJsonAsync<ProductImportResultDto>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -225,7 +225,11 @@
|
|||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net8.0": {
|
"net8.0": {
|
||||||
"targetAlias": "net8.0",
|
"targetAlias": "net8.0",
|
||||||
"projectReferences": {}
|
"projectReferences": {
|
||||||
|
"C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\Domain.csproj": {
|
||||||
|
"projectPath": "C:\\Users\\maski\\source\\repos\\SaludLAB\\phronCare\\Domain\\Domain.csproj"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warningProperties": {
|
"warningProperties": {
|
||||||
|
|||||||
@ -2323,6 +2323,7 @@
|
|||||||
"type": "project",
|
"type": "project",
|
||||||
"framework": ".NETCoreApp,Version=v8.0",
|
"framework": ".NETCoreApp,Version=v8.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"Domain": "1.0.0",
|
||||||
"EPPlus": "7.7.2",
|
"EPPlus": "7.7.2",
|
||||||
"PuppeteerSharp": "6.0.0"
|
"PuppeteerSharp": "6.0.0"
|
||||||
},
|
},
|
||||||
@ -4658,6 +4659,7 @@
|
|||||||
"type": "project",
|
"type": "project",
|
||||||
"framework": ".NETCoreApp,Version=v8.0",
|
"framework": ".NETCoreApp,Version=v8.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"Domain": "1.0.0",
|
||||||
"EPPlus": "7.7.2",
|
"EPPlus": "7.7.2",
|
||||||
"PuppeteerSharp": "6.0.0"
|
"PuppeteerSharp": "6.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user