Add Fix Scanner GS1, Factory Code
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 9m18s

This commit is contained in:
Leandro Hernan Rojas 2025-08-19 01:35:58 -03:00
parent 914702e4da
commit 2719bd4d9e
3 changed files with 149 additions and 29 deletions

View File

@ -30,7 +30,7 @@
/// <summary> /// <summary>
/// Número de serie de la unidad individual, según etiqueta de trazabilidad del fabricante. /// Número de serie de la unidad individual, según etiqueta de trazabilidad del fabricante.
/// </summary> /// </summary>
public string? Serial { get; set; } public string Serial { get; set; } = string.Empty ;
/// <summary> /// <summary>
/// Fecha de vencimiento (si aplica) /// Fecha de vencimiento (si aplica)

View File

@ -109,7 +109,7 @@ namespace Models.Repositories.Stock
int page, int page,
int take) int take)
{ {
// 0) Si no hay NINGÚN dato parseado, no traigas todo // 0) Sin ningún dato parseado -> vacío (evitar traer todo)
if (string.IsNullOrWhiteSpace(gtin) && if (string.IsNullOrWhiteSpace(gtin) &&
string.IsNullOrWhiteSpace(batch) && string.IsNullOrWhiteSpace(batch) &&
!expiration.HasValue && !expiration.HasValue &&
@ -128,7 +128,6 @@ namespace Models.Repositories.Stock
.Where(p => p.ExternalCode == g || p.FactoryCode == g || p.RegulatoryCode == g) .Where(p => p.ExternalCode == g || p.FactoryCode == g || p.RegulatoryCode == g)
.Select(p => p.Id).ToListAsync(); .Select(p => p.Id).ToListAsync();
// Si se pidió GTIN y no matchea ningún producto, devolvé vacío
if (productIds.Count == 0) if (productIds.Count == 0)
{ {
return new PagedResult<StockItemScanResultDto> return new PagedResult<StockItemScanResultDto>
@ -151,34 +150,150 @@ namespace Models.Repositories.Stock
if (locationId.HasValue) if (locationId.HasValue)
baseQuery = baseQuery.Where(x => x.si.LocationId == locationId.Value); baseQuery = baseQuery.Where(x => x.si.LocationId == locationId.Value);
// 3) Reglas por tipo de trazabilidad (sin fuzzy) // 3) Reglas por tipo de trazabilidad — PRIORIDAD: tipo REAL del producto si hay productIds
baseQuery = baseQuery.Where(x => if (productIds.Count > 0)
// None: solo producto + ubicación {
(x.p.TraceabilityType == 1) var traces = await _context.PhLsmProducts.AsNoTracking()
// BatchOnly: requiere batch exacto .Where(p => productIds.Contains(p.Id))
|| (x.p.TraceabilityType == 2 .Select(p => p.TraceabilityType)
&& !string.IsNullOrWhiteSpace(batch) .Distinct()
&& x.si.Batch == batch!.Trim()) .ToListAsync();
// Batch+Exp: requiere batch + expiration exactos
|| (x.p.TraceabilityType == 3 // Nota: normalmente habrá un solo tipo; si hubiera mezcla, estas ramas las contemplan.
&& !string.IsNullOrWhiteSpace(batch) && expiration.HasValue
&& x.si.Batch == batch!.Trim() if (traces.Contains(4) || traces.Contains(5))
&& x.si.Expiration.HasValue && x.si.Expiration.Value == expiration.Value) {
// SerialUnit: requiere serial exacto (ignora expiration) // T4/T5: requieren serial. T5 además requiere expiration exacta.
|| (x.p.TraceabilityType == 4 if (string.IsNullOrWhiteSpace(serial))
&& !string.IsNullOrWhiteSpace(serial) return new PagedResult<StockItemScanResultDto>
&& x.si.Serial != null && x.si.Serial == serial!.Trim()) { Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };
// Serial+Exp: requiere serial + expiration exactos
|| (x.p.TraceabilityType == 5 var s = serial.Trim();
&& !string.IsNullOrWhiteSpace(serial) && expiration.HasValue
&& x.si.Serial != null && x.si.Serial == serial!.Trim() if (traces.Contains(5) && expiration.HasValue)
&& x.si.Expiration.HasValue && x.si.Expiration.Value == expiration.Value) {
); var e = expiration.Value;
baseQuery = baseQuery.Where(x =>
(x.p.TraceabilityType == 5 &&
x.si.Serial != null && x.si.Serial == s &&
x.si.Expiration.HasValue && x.si.Expiration.Value == e)
||
// si el conjunto tuviera también productos T4, permitimos T4 por serial
(x.p.TraceabilityType == 4 &&
x.si.Serial != null && x.si.Serial == s)
);
}
else
{
baseQuery = baseQuery.Where(x =>
x.p.TraceabilityType == 4 &&
x.si.Serial != null && x.si.Serial == s
);
}
}
else if (traces.Contains(3))
{
// T3: exige batch + expiration exactos
if (string.IsNullOrWhiteSpace(batch) || !expiration.HasValue)
return new PagedResult<StockItemScanResultDto>
{ Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };
var b = batch.Trim();
var e = expiration.Value;
baseQuery = baseQuery.Where(x =>
x.p.TraceabilityType == 3 &&
x.si.Batch == b &&
x.si.Expiration.HasValue && x.si.Expiration.Value == e
);
}
else if (traces.Contains(2))
{
// T2: exige batch exacto (ignorar serial/expiration si vinieran)
if (string.IsNullOrWhiteSpace(batch))
return new PagedResult<StockItemScanResultDto>
{ Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };
var b = batch.Trim();
baseQuery = baseQuery.Where(x =>
x.p.TraceabilityType == 2 &&
x.si.Batch == b
);
}
else if (traces.Contains(1))
{
// T1: sin traza (solo producto + ubicación)
baseQuery = baseQuery.Where(x => x.p.TraceabilityType == 1);
}
else
{
return new PagedResult<StockItemScanResultDto>
{ Items = [], TotalItems = 0, Page = page <= 0 ? 1 : page, PageSize = take <= 0 ? 20 : take };
}
}
else
{
// Fallback: sin productIds -> decidir por presencia de datos (tu heurística original)
if (!string.IsNullOrWhiteSpace(serial))
{
var s = serial.Trim();
if (expiration.HasValue)
{
var e = expiration.Value;
baseQuery = baseQuery.Where(x =>
(x.p.TraceabilityType == 5 &&
x.si.Serial != null && x.si.Serial == s &&
x.si.Expiration.HasValue && x.si.Expiration.Value == e)
||
(x.p.TraceabilityType == 4 &&
x.si.Serial != null && x.si.Serial == s)
);
}
else
{
baseQuery = baseQuery.Where(x =>
x.p.TraceabilityType == 4 &&
x.si.Serial != null && x.si.Serial == s
);
}
}
else if (!string.IsNullOrWhiteSpace(batch))
{
var b = batch.Trim();
if (expiration.HasValue)
{
var e = expiration.Value;
baseQuery = baseQuery.Where(x =>
(x.p.TraceabilityType == 3 &&
x.si.Batch == b &&
x.si.Expiration.HasValue && x.si.Expiration.Value == e)
||
(x.p.TraceabilityType == 2 &&
x.si.Batch == b)
);
}
else
{
baseQuery = baseQuery.Where(x =>
x.p.TraceabilityType == 2 &&
x.si.Batch == b
);
}
}
else
{
// Solo aceptar T1 cuando no hay otra evidencia
baseQuery = baseQuery.Where(x => x.p.TraceabilityType == 1);
}
}
// 4) Orden y proyección // 4) Orden y proyección
baseQuery = baseQuery.OrderBy(x => x.si.Expiration).ThenBy(x => x.p.Name); baseQuery = baseQuery.OrderBy(x => x.si.Expiration).ThenBy(x => x.p.Name);
var dtoQuery = baseQuery.Select(x => new StockItemScanResultDto var dtoQuery = baseQuery.Select(x => new StockItemScanResultDto
{ {
StockItemId = x.si.Id, StockItemId = x.si.Id,
ProductId = x.p.Id, ProductId = x.p.Id,
@ -189,8 +304,8 @@ namespace Models.Repositories.Stock
LocationId = x.si.LocationId, LocationId = x.si.LocationId,
LocationName = x.loc != null ? x.loc.Descripcion : null, LocationName = x.loc != null ? x.loc.Descripcion : null,
Batch = x.si.Batch, Batch = x.si.Batch,
Expiration = x.si.Expiration, // DateOnly? Expiration = x.si.Expiration,
Serial = x.si.Serial, // si aplica Serial = x.si.Serial,
TraceabilityType = x.p.TraceabilityType, TraceabilityType = x.p.TraceabilityType,
AvailableQty = x.si.Quantity, AvailableQty = x.si.Quantity,
PlusProcess = x.p.PlusProcess PlusProcess = x.p.PlusProcess

View File

@ -49,6 +49,7 @@
<tr> <tr>
<th>Producto</th> <th>Producto</th>
<th>Lote</th> <th>Lote</th>
<th>Serial</th>
<th>Vencimiento</th> <th>Vencimiento</th>
<th>Disponible</th> <th>Disponible</th>
<th>Cantidad a usar</th> <th>Cantidad a usar</th>
@ -60,6 +61,7 @@
<tr> <tr>
<td>@item.ProductName</td> <td>@item.ProductName</td>
<td>@item.Batch</td> <td>@item.Batch</td>
<td>@item.Serial</td>
<td>@item.Expiration?.ToShortDateString()</td> <td>@item.Expiration?.ToShortDateString()</td>
<td>@item.Available</td> <td>@item.Available</td>
<td> <td>
@ -145,6 +147,7 @@
ProductId = matchedItem.ProductId, ProductId = matchedItem.ProductId,
ProductName = matchedItem.ProductName, ProductName = matchedItem.ProductName,
Batch = matchedItem.Batch, Batch = matchedItem.Batch,
Serial = matchedItem.Serial,
Expiration = matchedItem.Expiration, Expiration = matchedItem.Expiration,
Available = matchedItem.Quantity, Available = matchedItem.Quantity,
Selected = 1, Selected = 1,
@ -181,6 +184,7 @@
ProductId = x.ProductId, ProductId = x.ProductId,
ProductName = x.ProductName, ProductName = x.ProductName,
Batch = x.Batch, Batch = x.Batch,
Serial= x.Serial,
Expiration = x.Expiration, Expiration = x.Expiration,
Quantity = x.Selected, Quantity = x.Selected,
LocationId = x.LocationId LocationId = x.LocationId
@ -213,6 +217,7 @@
public int ProductId { get; set; } public int ProductId { get; set; }
public string ProductName { get; set; } = string.Empty; public string ProductName { get; set; } = string.Empty;
public string Batch { get; set; } = string.Empty; public string Batch { get; set; } = string.Empty;
public string Serial { get; set; } = string.Empty;
public DateTime? Expiration { get; set; } public DateTime? Expiration { get; set; }
public decimal Available { get; set; } public decimal Available { get; set; }
public decimal Selected { get; set; } public decimal Selected { get; set; }