Add StockScanController
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 6m39s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 6m39s
This commit is contained in:
parent
b10f427399
commit
852eab0c32
@ -2,6 +2,7 @@
|
|||||||
using Domain.Dtos.Stock; // StockItemSearchParams, StockItemScanResultDto
|
using Domain.Dtos.Stock; // StockItemSearchParams, StockItemScanResultDto
|
||||||
using Domain.Generics; // PagedResult<T>
|
using Domain.Generics; // PagedResult<T>
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Transversal.Services;
|
||||||
|
|
||||||
namespace API.Controllers.Stock
|
namespace API.Controllers.Stock
|
||||||
{
|
{
|
||||||
@ -43,5 +44,38 @@ namespace API.Controllers.Stock
|
|||||||
var result = await _service.SearchParsedAsync(searchParams);
|
var result = await _service.SearchParsedAsync(searchParams);
|
||||||
return Ok(result);
|
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))
|
||||||
|
return BadRequest("Raw is required.");
|
||||||
|
|
||||||
|
// 1) Parseo GS1 en backend
|
||||||
|
var parsed = Gs1CodeParser.Parse(req.Raw.Trim());
|
||||||
|
|
||||||
|
// 2) Armar parámetros "ya parseados"
|
||||||
|
var sp = new StockItemParsedSearchParams
|
||||||
|
{
|
||||||
|
Gtin = string.IsNullOrWhiteSpace(parsed.Gtin) ? parsed.Variant : parsed.Gtin, // (22) como fallback
|
||||||
|
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 = req.LocationId,
|
||||||
|
Page = req.Page,
|
||||||
|
PageSize = req.PageSize
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3) Delegar a la misma lógica que ya tenés implementada
|
||||||
|
var result = await _service.SearchParsedAsync(sp);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1226,6 +1226,32 @@
|
|||||||
],
|
],
|
||||||
"ReturnTypes": []
|
"ReturnTypes": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ContainingType": "API.Controllers.Stock.LSStockScanController",
|
||||||
|
"Method": "ParseAndSearch",
|
||||||
|
"RelativePath": "api/LSStockScan/parse-and-search",
|
||||||
|
"HttpMethod": "POST",
|
||||||
|
"IsController": true,
|
||||||
|
"Order": 0,
|
||||||
|
"Parameters": [
|
||||||
|
{
|
||||||
|
"Name": "req",
|
||||||
|
"Type": "API.Controllers.Stock.LSStockScanController\u002BStockScanRawRequest",
|
||||||
|
"IsRequired": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReturnTypes": [
|
||||||
|
{
|
||||||
|
"Type": "Domain.Generics.PagedResult\u00601[[Domain.Dtos.Stock.StockItemScanResultDto, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]",
|
||||||
|
"MediaTypes": [
|
||||||
|
"text/plain",
|
||||||
|
"application/json",
|
||||||
|
"text/json"
|
||||||
|
],
|
||||||
|
"StatusCode": 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ContainingType": "API.Controllers.Stock.LSStockScanController",
|
"ContainingType": "API.Controllers.Stock.LSStockScanController",
|
||||||
"Method": "Search",
|
"Method": "Search",
|
||||||
|
|||||||
@ -11,64 +11,18 @@ public class StockScanService : IStockScanService
|
|||||||
{
|
{
|
||||||
_http = http;
|
_http = http;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<StockItemSelectionDto?> ParseAndMatchAsync(string rawInput, int locationId)
|
public async Task<StockItemSelectionDto?> ParseAndMatchAsync(string rawInput, int locationId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(rawInput))
|
if (string.IsNullOrWhiteSpace(rawInput)) return null;
|
||||||
return null;
|
|
||||||
|
|
||||||
try
|
var payload = new { Raw = rawInput, LocationId = locationId, Page = 1, PageSize = 10 };
|
||||||
{
|
var resp = await _http.PostAsJsonAsync("/api/lsstockscan/parse-and-search", payload);
|
||||||
var parsed = new Gs1ScanResult();
|
if (!resp.IsSuccessStatusCode) return null;
|
||||||
//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 pr = await resp.Content.ReadFromJsonAsync<PagedResult<StockItemScanResultDto>>();
|
||||||
var first = pr?.Items?.FirstOrDefault();
|
var first = pr?.Items?.FirstOrDefault();
|
||||||
if (first == null)
|
if (first is null) return null;
|
||||||
{
|
|
||||||
Console.WriteLine("[ParseAndMatchAsync] No se encontró ningún ítem que coincida.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. Mapear a DTO de selección
|
|
||||||
return new StockItemSelectionDto
|
return new StockItemSelectionDto
|
||||||
{
|
{
|
||||||
StockItemId = first.StockItemId,
|
StockItemId = first.StockItemId,
|
||||||
@ -77,24 +31,94 @@ public class StockScanService : IStockScanService
|
|||||||
Batch = first.Batch ?? string.Empty,
|
Batch = first.Batch ?? string.Empty,
|
||||||
Expiration = first.Expiration?.ToDateTime(TimeOnly.MinValue),
|
Expiration = first.Expiration?.ToDateTime(TimeOnly.MinValue),
|
||||||
Quantity = first.AvailableQty,
|
Quantity = first.AvailableQty,
|
||||||
LocationId = first.LocationId ?? 0
|
LocationId = first.LocationId ?? locationId,
|
||||||
|
Serial = first.Serial // si lo devolvés en el DTO de scan
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"[ParseAndMatchAsync] Error inesperado: {ex}");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
//public async Task<StockItemSelectionDto?> ParseAndMatchAsync(string rawInput, int locationId)
|
||||||
private static bool IsPlainCode(string s)
|
//{
|
||||||
{
|
// if (string.IsNullOrWhiteSpace(rawInput))
|
||||||
// sin FNC1 ($), sin espacios y sin prefijos AI típicos
|
// return null;
|
||||||
if (s.Contains('$') || s.Contains((char)29) || s.Contains(' ')) return false;
|
|
||||||
// evita raws que empiezan como AIs "01","10","17","21","22"
|
// try
|
||||||
var prefix = s.Length >= 2 ? s[..2] : s;
|
// {
|
||||||
if (prefix is "01" or "10" or "11" or "17" or "21" or "22") return false;
|
// var parsed = new Gs1ScanResult();
|
||||||
// permite letras/dígitos y algunos separadores comunes
|
// //var parsed = "";
|
||||||
return s.All(c => char.IsLetterOrDigit(c) || c is '-' or '/' or '_');
|
// 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