Fix ParseGS1 to Backend
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 6m15s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 6m15s
This commit is contained in:
parent
852eab0c32
commit
c574a48c4f
2
Domain/Dtos/Stock/StockScanRawRequest.cs
Normal file
2
Domain/Dtos/Stock/StockScanRawRequest.cs
Normal file
@ -0,0 +1,2 @@
|
||||
namespace Domain.Dtos.Stock;
|
||||
public record StockScanRawRequest(string Raw, int LocationId, int Page = 1, int PageSize = 10);
|
||||
@ -45,36 +45,68 @@ namespace API.Controllers.Stock
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// DTO liviano para el request RAW
|
||||
public record StockScanRawRequest(string Raw, int LocationId, int Page = 1, int PageSize = 10);
|
||||
|
||||
/// <summary>
|
||||
/// Recibe un escaneo RAW (GS1-128/DataMatrix), lo parsea y ejecuta la búsqueda paginada.
|
||||
/// </summary>
|
||||
[HttpPost("parse-and-search")]
|
||||
public async Task<ActionResult<PagedResult<StockItemScanResultDto>>> ParseAndSearch([FromBody] StockScanRawRequest req)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(req.Raw))
|
||||
if (req is null || string.IsNullOrWhiteSpace(req.Raw))
|
||||
return BadRequest("Raw is required.");
|
||||
|
||||
// 1) Parseo GS1 en backend
|
||||
var parsed = Gs1CodeParser.Parse(req.Raw.Trim());
|
||||
var raw = req.Raw.Trim();
|
||||
|
||||
// 2) Armar parámetros "ya parseados"
|
||||
// 1) Parseo GS1
|
||||
var parsed = Gs1CodeParser.Parse(raw);
|
||||
|
||||
// 2) ¿Hay AIs útiles?
|
||||
bool hasAis =
|
||||
!string.IsNullOrWhiteSpace(parsed.Lot)
|
||||
|| parsed.ExpirationDate.HasValue
|
||||
|| !string.IsNullOrWhiteSpace(parsed.Serial)
|
||||
|| !string.IsNullOrWhiteSpace(parsed.Variant);
|
||||
|
||||
// 3) Elegir "code" con prioridades
|
||||
string? code = LooksLikeGtin(parsed.Gtin) ? parsed.Gtin : null;
|
||||
|
||||
// Opcional: si NO hay serial y querés usar Variant como último intento:
|
||||
// if (code is null && string.IsNullOrWhiteSpace(parsed.Serial))
|
||||
// code = parsed.Variant;
|
||||
|
||||
// Fallback a "código plano" (ej. factory/regulatory tipeado) si no hay AIs ni GTIN
|
||||
if (code is null && !hasAis && IsPlainCode(raw))
|
||||
code = raw;
|
||||
|
||||
// 4) Armar parámetros de búsqueda
|
||||
var sp = new StockItemParsedSearchParams
|
||||
{
|
||||
Gtin = string.IsNullOrWhiteSpace(parsed.Gtin) ? parsed.Variant : parsed.Gtin, // (22) como fallback
|
||||
Gtin = code, // tratado como "code" genérico en el repo
|
||||
Batch = string.IsNullOrWhiteSpace(parsed.Lot) ? null : parsed.Lot,
|
||||
Expiration = parsed.ExpirationDate.HasValue ? DateOnly.FromDateTime(parsed.ExpirationDate.Value) : null,
|
||||
Expiration = parsed.ExpirationDate.HasValue ? DateOnly.FromDateTime(parsed.ExpirationDate.Value) : (DateOnly?)null,
|
||||
Serial = string.IsNullOrWhiteSpace(parsed.Serial) ? null : parsed.Serial,
|
||||
LocationId = req.LocationId,
|
||||
Page = req.Page,
|
||||
PageSize = req.PageSize
|
||||
Page = req.Page <= 0 ? 1 : req.Page,
|
||||
PageSize = req.PageSize <= 0 ? 10 : req.PageSize
|
||||
};
|
||||
|
||||
// 3) Delegar a la misma lógica que ya tenés implementada
|
||||
// 5) Delegar al servicio existente
|
||||
var result = await _service.SearchParsedAsync(sp);
|
||||
return Ok(result);
|
||||
|
||||
// === Helpers locales ===
|
||||
static bool LooksLikeGtin(string? x)
|
||||
=> !string.IsNullOrWhiteSpace(x)
|
||||
&& x!.All(char.IsDigit)
|
||||
&& (x.Length == 8 || x.Length == 12 || x.Length == 13 || x.Length == 14);
|
||||
|
||||
static bool IsPlainCode(string s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s)) return false;
|
||||
if (s.Contains('$') || s.Contains((char)29) || s.Contains(' ')) return false; // FNC1/espacios
|
||||
var prefix = s.Length >= 2 ? s[..2] : s;
|
||||
if (prefix is "01" or "10" or "11" or "17" or "21" or "22") return false; // evita confundir AIs
|
||||
return s.All(c => char.IsLetterOrDigit(c) || c is '-' or '/' or '_'); // agrega '.' si lo necesitás
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1236,7 +1236,7 @@
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "req",
|
||||
"Type": "API.Controllers.Stock.LSStockScanController\u002BStockScanRawRequest",
|
||||
"Type": "Domain.Dtos.Stock.StockScanRawRequest",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
using System.Net.Http.Json;
|
||||
using Domain.Dtos.Stock;
|
||||
using Domain.Generics;
|
||||
//using Transversal.Services;
|
||||
|
||||
public class StockScanService : IStockScanService
|
||||
{
|
||||
@ -11,13 +10,19 @@ public class StockScanService : IStockScanService
|
||||
{
|
||||
_http = http;
|
||||
}
|
||||
/// <summary>
|
||||
/// Envía el RAW al backend (parsea + busca) y devuelve el primer match mapeado a la UI.
|
||||
/// </summary>
|
||||
public async Task<StockItemSelectionDto?> ParseAndMatchAsync(string rawInput, int locationId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawInput)) return null;
|
||||
if (string.IsNullOrWhiteSpace(rawInput))
|
||||
return null;
|
||||
|
||||
var payload = new { Raw = rawInput, LocationId = locationId, Page = 1, PageSize = 10 };
|
||||
var resp = await _http.PostAsJsonAsync("/api/lsstockscan/parse-and-search", payload);
|
||||
if (!resp.IsSuccessStatusCode) return null;
|
||||
var payload = new StockScanRawRequest(rawInput, locationId, Page: 1, PageSize: 10);
|
||||
|
||||
using var resp = await _http.PostAsJsonAsync("/api/lsstockscan/parse-and-search", payload);
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
var pr = await resp.Content.ReadFromJsonAsync<PagedResult<StockItemScanResultDto>>();
|
||||
var first = pr?.Items?.FirstOrDefault();
|
||||
@ -35,90 +40,4 @@ public class StockScanService : IStockScanService
|
||||
Serial = first.Serial // si lo devolvés en el DTO de scan
|
||||
};
|
||||
}
|
||||
|
||||
//public async Task<StockItemSelectionDto?> ParseAndMatchAsync(string rawInput, int locationId)
|
||||
//{
|
||||
// if (string.IsNullOrWhiteSpace(rawInput))
|
||||
// return null;
|
||||
|
||||
// try
|
||||
// {
|
||||
// var parsed = new Gs1ScanResult();
|
||||
// //var parsed = "";
|
||||
// var raw = rawInput.Trim();
|
||||
|
||||
// bool hasParsedAis = !string.IsNullOrWhiteSpace(parsed.Lot)
|
||||
// || parsed.ExpirationDate.HasValue
|
||||
// || !string.IsNullOrWhiteSpace(parsed.Serial)
|
||||
// || !string.IsNullOrWhiteSpace(parsed.Variant); // incluir (22)
|
||||
|
||||
// string? gtinToSend = parsed.Gtin ?? parsed.Variant; // (22) como fallback
|
||||
// if (gtinToSend is null && !hasParsedAis && IsPlainCode(raw))
|
||||
// gtinToSend = raw; // código plano tipeado (factory/regulatory)
|
||||
|
||||
|
||||
// // 3. Armar parámetros de búsqueda
|
||||
// var sp = new StockItemParsedSearchParams
|
||||
// {
|
||||
// Gtin = gtinToSend,
|
||||
// Batch = string.IsNullOrWhiteSpace(parsed.Lot) ? null : parsed.Lot,
|
||||
// Expiration = parsed.ExpirationDate.HasValue
|
||||
// ? DateOnly.FromDateTime(parsed.ExpirationDate.Value)
|
||||
// : null,
|
||||
// Serial = string.IsNullOrWhiteSpace(parsed.Serial) ? null : parsed.Serial,
|
||||
// LocationId = locationId,
|
||||
// Page = 1,
|
||||
// PageSize = 10
|
||||
// };
|
||||
|
||||
// // 4. Log para depuración (quitar en producción)
|
||||
// Console.WriteLine($"[ParseAndMatchAsync] Gtin={sp.Gtin}, Batch={sp.Batch}, Exp={sp.Expiration}, Serial={sp.Serial}, Loc={sp.LocationId}");
|
||||
|
||||
// // 5. Llamar a la API
|
||||
// var resp = await _http.PostAsJsonAsync("/api/lsstockscan/search-parsed", sp);
|
||||
// if (!resp.IsSuccessStatusCode)
|
||||
// {
|
||||
// var err = await resp.Content.ReadAsStringAsync();
|
||||
// Console.WriteLine($"[ParseAndMatchAsync] API devolvió error {resp.StatusCode}: {err}");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// // 6. Leer resultado
|
||||
// var pr = await resp.Content.ReadFromJsonAsync<PagedResult<StockItemScanResultDto>>();
|
||||
// var first = pr?.Items?.FirstOrDefault();
|
||||
// if (first == null)
|
||||
// {
|
||||
// Console.WriteLine("[ParseAndMatchAsync] No se encontró ningún ítem que coincida.");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// // 7. Mapear a DTO de selección
|
||||
// return new StockItemSelectionDto
|
||||
// {
|
||||
// StockItemId = first.StockItemId,
|
||||
// ProductId = first.ProductId,
|
||||
// ProductName = first.ProductName,
|
||||
// Batch = first.Batch ?? string.Empty,
|
||||
// Expiration = first.Expiration?.ToDateTime(TimeOnly.MinValue),
|
||||
// Quantity = first.AvailableQty,
|
||||
// LocationId = first.LocationId ?? 0
|
||||
// };
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Console.WriteLine($"[ParseAndMatchAsync] Error inesperado: {ex}");
|
||||
// throw;
|
||||
// }
|
||||
|
||||
//}
|
||||
//private static bool IsPlainCode(string s)
|
||||
//{
|
||||
// // sin FNC1 ($), sin espacios y sin prefijos AI típicos
|
||||
// if (s.Contains('$') || s.Contains((char)29) || s.Contains(' ')) return false;
|
||||
// // evita raws que empiezan como AIs "01","10","17","21","22"
|
||||
// var prefix = s.Length >= 2 ? s[..2] : s;
|
||||
// if (prefix is "01" or "10" or "11" or "17" or "21" or "22") return false;
|
||||
// // permite letras/dígitos y algunos separadores comunes
|
||||
// return s.All(c => char.IsLetterOrDigit(c) || c is '-' or '/' or '_');
|
||||
//}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user