using Core.Interfaces.Stock; // ILSStockScanDom using Domain.Dtos.Stock; // StockItemSearchParams, StockItemScanResultDto using Domain.Generics; // PagedResult using Microsoft.AspNetCore.Mvc; using Transversal.Services; namespace API.Controllers.Stock { [Route("api/[controller]")] [ApiController] public class LSStockScanController : ControllerBase { private readonly ILSStockScanDom _service; public LSStockScanController(ILSStockScanDom service) { _service = service; } /// /// Búsqueda paginada de ítems de stock por código/texto, lote y filtros opcionales. /// [HttpPost("search")] public async Task>> Search([FromBody] StockItemSearchParams searchParams) { var result = await _service.SearchAsync(searchParams); return Ok(result); } /// Realiza una búsqueda paginada de ítems de stock utilizando datos ya parseados /// (por ejemplo, provenientes de un código GS1 escaneado). /// /// /// Parámetros de búsqueda ya procesados y listos para filtrar en base de datos, /// incluyendo código de producto, lote, fecha de vencimiento, ubicación, etc. /// /// /// Lista paginada de ítems de stock que cumplen con los filtros especificados. /// [HttpPost("search-parsed")] public async Task>> SearchParsed([FromBody] StockItemParsedSearchParams searchParams) { var result = await _service.SearchParsedAsync(searchParams); return Ok(result); } /// /// 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 (req is null || string.IsNullOrWhiteSpace(req.Raw)) return BadRequest("Raw is required."); var raw = req.Raw.Trim(); // 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 = 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) : (DateOnly?)null, Serial = string.IsNullOrWhiteSpace(parsed.Serial) ? null : parsed.Serial, LocationId = req.LocationId, Page = req.Page <= 0 ? 1 : req.Page, PageSize = req.PageSize <= 0 ? 10 : req.PageSize }; // 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 } } } }