phronCare/Models/Repositories/Stock/PhLSMStockItemRepository.cs

214 lines
9.3 KiB
C#
Raw Normal View History

2025-08-18 00:47:37 -03:00
using Domain.Dtos.Stock; // StockItemScanResultDto
using Domain.Generics; // PagedResult<T>
using Microsoft.EntityFrameworkCore;
using Models.Helpers; // ToPagedResultAsync
using Models.Interfaces; // IPhLSMStockItemRepository
using Models.Models; // PhronCareOperationsHubContext
namespace Models.Repositories.Stock
{
public class PhLSMStockItemRepository(PhronCareOperationsHubContext context) : IPhLSMStockItemRepository
{
private readonly PhronCareOperationsHubContext _context = context;
public async Task<PagedResult<StockItemScanResultDto>> SearchStockItemsAsync(
string? codeOrText,
string? batch,
int? locationId,
int? productType,
int? traceabilityType,
bool? plusProcess,
int page,
int take)
{
// Base: stock disponible (>0) + joins necesarios
var baseQuery =
from si in _context.PhLsmStockItems.AsNoTracking()
join p in _context.PhLsmProducts.AsNoTracking() on si.ProductId equals p.Id
join l in _context.PhLsmStockLocations.AsNoTracking() on si.LocationId equals l.Id into _loc
from loc in _loc.DefaultIfEmpty()
where si.Quantity > 0
select new { si, p, loc };
// ---- Filtros (case-insensitive donde aplica) ----
if (!string.IsNullOrWhiteSpace(codeOrText))
{
var t = codeOrText.Trim().ToLower();
baseQuery = baseQuery.Where(x =>
(!string.IsNullOrEmpty(x.p.FactoryCode) && x.p.FactoryCode.ToLower().Contains(t)) ||
(!string.IsNullOrEmpty(x.p.ExternalCode) && x.p.ExternalCode.ToLower().Contains(t)) ||
(!string.IsNullOrEmpty(x.p.Name) && x.p.Name.ToLower().Contains(t)) ||
(!string.IsNullOrEmpty(x.p.Descripcion) && x.p.Descripcion.ToLower().Contains(t)) ||
(!string.IsNullOrEmpty(x.si.Batch) && x.si.Batch.ToLower().Contains(t))
);
}
if (!string.IsNullOrWhiteSpace(batch))
{
var b = batch.Trim().ToLower();
baseQuery = baseQuery.Where(x => x.si.Batch != null && x.si.Batch.ToLower().Contains(b));
}
if (locationId.HasValue)
baseQuery = baseQuery.Where(x => x.si.LocationId == locationId.Value);
if (productType.HasValue)
baseQuery = baseQuery.Where(x => x.p.ProductType == productType.Value);
if (traceabilityType.HasValue)
baseQuery = baseQuery.Where(x => x.p.TraceabilityType == traceabilityType.Value);
if (plusProcess.HasValue)
baseQuery = baseQuery.Where(x => x.p.PlusProcess == plusProcess.Value);
// Orden lógico
baseQuery = baseQuery
.OrderBy(x => x.si.Expiration)
.ThenBy(x => x.p.Name);
// Proyección final a DTO (IQueryable<StockItemScanResultDto>)
var dtoQuery = baseQuery.Select(x => new StockItemScanResultDto
{
StockItemId = x.si.Id,
ProductId = x.p.Id,
FactoryCode = x.p.FactoryCode ?? string.Empty,
ExternalCode = x.p.ExternalCode,
ProductName = x.p.Name ?? string.Empty,
Description = x.p.Descripcion,
LocationId = x.si.LocationId,
LocationName = x.loc != null ? x.loc.Descripcion : null,
Batch = x.si.Batch,
Expiration = x.si.Expiration,
TraceabilityType = x.p.TraceabilityType,
AvailableQty = x.si.Quantity,
PlusProcess = x.p.PlusProcess
});
page = page <= 0 ? 1 : page;
take = take <= 0 ? 20 : take;
// Paginado con tu helper (manteniendo el mismo patrón que ProductRepository)
var paged = await dtoQuery.ToPagedResultAsync(page, take);
return new PagedResult<StockItemScanResultDto>
{
Items = paged.Items,
TotalItems = paged.TotalItems,
Page = paged.Page,
PageSize = paged.PageSize
};
}
// -------- Búsqueda PARSEADA (GS1/DataMatrix) ----------
public async Task<PagedResult<StockItemScanResultDto>> SearchStockItemsParsedAsync(
string? gtin,
string? batch,
DateOnly? expiration,
string? serial,
int? locationId,
int page,
int take)
{
// 0) Si no hay NINGÚN dato parseado, no traigas todo
if (string.IsNullOrWhiteSpace(gtin) &&
string.IsNullOrWhiteSpace(batch) &&
!expiration.HasValue &&
string.IsNullOrWhiteSpace(serial))
{
return new PagedResult<StockItemScanResultDto>
{ Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };
}
// 1) Resolver productos por GTIN/Factory/Regulatory si vino GTIN
var productIds = new List<int>();
if (!string.IsNullOrWhiteSpace(gtin))
{
var g = gtin.Trim();
productIds = await _context.PhLsmProducts.AsNoTracking()
.Where(p => p.ExternalCode == g || p.FactoryCode == g || p.RegulatoryCode == g)
.Select(p => p.Id).ToListAsync();
// Si se pidió GTIN y no matchea ningún producto, devolvé vacío
if (productIds.Count == 0)
{
return new PagedResult<StockItemScanResultDto>
{ Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };
}
}
// 2) Base query (stock disponible)
var baseQuery =
from si in _context.PhLsmStockItems.AsNoTracking()
join p in _context.PhLsmProducts.AsNoTracking() on si.ProductId equals p.Id
join l in _context.PhLsmStockLocations.AsNoTracking() on si.LocationId equals l.Id into _loc
from loc in _loc.DefaultIfEmpty()
where si.Quantity > 0
select new { si, p, loc };
if (productIds.Count > 0)
baseQuery = baseQuery.Where(x => productIds.Contains(x.p.Id));
if (locationId.HasValue)
baseQuery = baseQuery.Where(x => x.si.LocationId == locationId.Value);
// 3) Reglas por tipo de trazabilidad (sin fuzzy)
baseQuery = baseQuery.Where(x =>
// None: solo producto + ubicación
(x.p.TraceabilityType == 1)
// BatchOnly: requiere batch exacto
|| (x.p.TraceabilityType == 2
&& !string.IsNullOrWhiteSpace(batch)
&& x.si.Batch == batch!.Trim())
// Batch+Exp: requiere batch + expiration exactos
|| (x.p.TraceabilityType == 3
&& !string.IsNullOrWhiteSpace(batch) && expiration.HasValue
&& x.si.Batch == batch!.Trim()
&& x.si.Expiration.HasValue && x.si.Expiration.Value == expiration.Value)
// SerialUnit: requiere serial exacto (ignora expiration)
|| (x.p.TraceabilityType == 4
&& !string.IsNullOrWhiteSpace(serial)
&& x.si.Serial != null && x.si.Serial == serial!.Trim())
// Serial+Exp: requiere serial + expiration exactos
|| (x.p.TraceabilityType == 5
&& !string.IsNullOrWhiteSpace(serial) && expiration.HasValue
&& x.si.Serial != null && x.si.Serial == serial!.Trim()
&& x.si.Expiration.HasValue && x.si.Expiration.Value == expiration.Value)
);
// 4) Orden y proyección
baseQuery = baseQuery.OrderBy(x => x.si.Expiration).ThenBy(x => x.p.Name);
var dtoQuery = baseQuery.Select(x => new StockItemScanResultDto
{
StockItemId = x.si.Id,
ProductId = x.p.Id,
FactoryCode = x.p.FactoryCode ?? string.Empty,
ExternalCode = x.p.ExternalCode,
ProductName = x.p.Name ?? string.Empty,
Description = x.p.Descripcion,
LocationId = x.si.LocationId,
LocationName = x.loc != null ? x.loc.Descripcion : null,
Batch = x.si.Batch,
Expiration = x.si.Expiration, // DateOnly?
Serial = x.si.Serial, // si aplica
TraceabilityType = x.p.TraceabilityType,
AvailableQty = x.si.Quantity,
PlusProcess = x.p.PlusProcess
});
page = page <= 0 ? 1 : page;
take = take <= 0 ? 20 : take;
var paged = await dtoQuery.ToPagedResultAsync(page, take);
return new PagedResult<StockItemScanResultDto>
{
Items = paged.Items,
TotalItems = paged.TotalItems,
Page = paged.Page,
PageSize = paged.PageSize
};
}
}
}