diff --git a/Domain/Dtos/Stock/StockScanRawRequest.cs b/Domain/Dtos/Stock/StockScanRawRequest.cs
new file mode 100644
index 0000000..5f0b207
--- /dev/null
+++ b/Domain/Dtos/Stock/StockScanRawRequest.cs
@@ -0,0 +1,2 @@
+namespace Domain.Dtos.Stock;
+public record StockScanRawRequest(string Raw, int LocationId, int Page = 1, int PageSize = 10);
diff --git a/phronCare.API/Controllers/Stock/LSStockScanController.cs b/phronCare.API/Controllers/Stock/LSStockScanController.cs
index 6d50698..00479a3 100644
--- a/phronCare.API/Controllers/Stock/LSStockScanController.cs
+++ b/phronCare.API/Controllers/Stock/LSStockScanController.cs
@@ -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);
-
///
/// Recibe un escaneo RAW (GS1-128/DataMatrix), lo parsea y ejecuta la búsqueda paginada.
///
[HttpPost("parse-and-search")]
public async Task>> 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
+ }
}
}
diff --git a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json
index cbed662..58dba96 100644
--- a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json
+++ b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json
@@ -1236,7 +1236,7 @@
"Parameters": [
{
"Name": "req",
- "Type": "API.Controllers.Stock.LSStockScanController\u002BStockScanRawRequest",
+ "Type": "Domain.Dtos.Stock.StockScanRawRequest",
"IsRequired": true
}
],
diff --git a/phronCare.UIBlazor/Services/Stock/StockScanService.cs b/phronCare.UIBlazor/Services/Stock/StockScanService.cs
index fdab276..f2ae4dd 100644
--- a/phronCare.UIBlazor/Services/Stock/StockScanService.cs
+++ b/phronCare.UIBlazor/Services/Stock/StockScanService.cs
@@ -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;
}
+ ///
+ /// Envía el RAW al backend (parsea + busca) y devuelve el primer match mapeado a la UI.
+ ///
public async Task 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>();
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 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>();
- // 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 '_');
- //}
}