Some checks failed
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Failing after 15m47s
222 lines
7.4 KiB
Plaintext
222 lines
7.4 KiB
Plaintext
@using Blazored.Modal
|
|
@using Blazored.Modal.Services
|
|
@using Domain.Dtos.Stock
|
|
@using Microsoft.AspNetCore.Components.Web
|
|
|
|
@inject IToastService toastService
|
|
@inject IStockScanService stockScanService
|
|
@inject IModalService Modal
|
|
|
|
@inherits LayoutComponentBase
|
|
|
|
<div class="modal-header bg-dark text-white">
|
|
<h5 class="modal-title">Seleccionar artículos de stock</h5>
|
|
<button type="button" class="btn-close" aria-label="Close" @onclick="Cancel"></button>
|
|
</div>
|
|
|
|
<div class="modal-body" style="zoom:0.8;">
|
|
<div class="mb-3">
|
|
<label for="scan" class="form-label">Escanear o ingresar código</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text">
|
|
<i class="fas fa-qrcode"></i>
|
|
</span>
|
|
|
|
<!-- Input nativo: @ref es ElementReference; capturamos Enter -->
|
|
<input class="form-control"
|
|
id="scan"
|
|
@bind="InputCode"
|
|
@bind:event="oninput"
|
|
@onkeydown="HandleKeyDown"
|
|
autocomplete="off"
|
|
spellcheck="false"
|
|
@ref="scanInput" />
|
|
</div>
|
|
|
|
<button type="button" class="btn btn-secondary mt-2" @onclick="HandleScan">Buscar</button>
|
|
</div>
|
|
|
|
@if (StockList.Any())
|
|
{
|
|
var last = StockList.First();
|
|
<div class="alert alert-info small">
|
|
Último escaneado: <strong>@last.ProductName</strong> | Lote <strong>@last.Batch</strong> | Venc: <strong>@last.Expiration?.ToShortDateString()</strong>
|
|
</div>
|
|
}
|
|
|
|
<table class="table table-sm table-bordered">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Producto</th>
|
|
<th>Lote</th>
|
|
<th>Vencimiento</th>
|
|
<th>Disponible</th>
|
|
<th>Cantidad a usar</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var item in StockList)
|
|
{
|
|
<tr>
|
|
<td>@item.ProductName</td>
|
|
<td>@item.Batch</td>
|
|
<td>@item.Expiration?.ToShortDateString()</td>
|
|
<td>@item.Available</td>
|
|
<td>
|
|
<InputNumber @bind-Value="item.Selected" class="form-control form-control-sm" min="0" max="@item.Available" />
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
|
|
<button class="btn btn-primary" @onclick="Confirm">Agregar a lista</button>
|
|
</div>
|
|
|
|
@code {
|
|
[CascadingParameter] BlazoredModalInstance ModalInstance { get; set; } = default!;
|
|
|
|
[Parameter] public int? ProductId { get; set; }
|
|
[Parameter] public int? LocationId { get; set; }
|
|
[Parameter] public List<ProductSetItemDto>? SetItems { get; set; }
|
|
|
|
private string InputCode { get; set; } = string.Empty;
|
|
private ElementReference scanInput;
|
|
|
|
private readonly List<StockItemSelectionDto> SelectedItems = new();
|
|
private List<StockDisplayRow> StockList = new();
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadMockStock();
|
|
}
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
await scanInput.FocusAsync();
|
|
}
|
|
private async Task OnKeyDown(KeyboardEventArgs e)
|
|
{
|
|
if (e.Key == "Enter")
|
|
await HandleScan();
|
|
}
|
|
private async Task HandleKeyDown(KeyboardEventArgs e)
|
|
{
|
|
if (e.Key is "Enter" or "NumpadEnter")
|
|
{
|
|
await HandleScan();
|
|
await scanInput.FocusAsync();
|
|
}
|
|
}
|
|
private async Task HandleScan()
|
|
{
|
|
var code = (InputCode ?? string.Empty).Trim(); // limpia CR/LF del lector
|
|
if (string.IsNullOrWhiteSpace(code))
|
|
{
|
|
toastService.ShowWarning("Ingrese un código válido.");
|
|
await Refocus();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var matchedItem = await stockScanService.ParseAndMatchAsync(code, LocationId ?? 1);
|
|
|
|
if (matchedItem is null)
|
|
{
|
|
toastService.ShowWarning("No se encontró el producto en stock.");
|
|
await Refocus();
|
|
return;
|
|
}
|
|
|
|
if (StockList.Any(s => s.StockItemId == matchedItem.StockItemId))
|
|
{
|
|
toastService.ShowInfo("Este ítem ya está listado.");
|
|
await Refocus();
|
|
return;
|
|
}
|
|
|
|
StockList.Insert(0, new StockDisplayRow
|
|
{
|
|
StockItemId = matchedItem.StockItemId,
|
|
ProductId = matchedItem.ProductId,
|
|
ProductName = matchedItem.ProductName,
|
|
Batch = matchedItem.Batch,
|
|
Expiration = matchedItem.Expiration,
|
|
Available = matchedItem.Quantity,
|
|
Selected = 1,
|
|
LocationId = matchedItem.LocationId
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
toastService.ShowError($"Error en escaneo: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
InputCode = string.Empty;
|
|
await Refocus();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task Refocus()
|
|
{
|
|
await Task.Yield(); // asegura que el DOM está listo
|
|
await scanInput.FocusAsync(); // deja el cursor listo para el próximo escaneo
|
|
}
|
|
|
|
private Task Cancel() => ModalInstance.CancelAsync();
|
|
|
|
private async Task Confirm()
|
|
{
|
|
var selected = StockList
|
|
.Where(x => x.Selected > 0)
|
|
.Select(x => new StockItemSelectionDto
|
|
{
|
|
StockItemId = x.StockItemId,
|
|
ProductId = x.ProductId,
|
|
ProductName = x.ProductName,
|
|
Batch = x.Batch,
|
|
Expiration = x.Expiration,
|
|
Quantity = x.Selected,
|
|
LocationId = x.LocationId
|
|
})
|
|
.ToList();
|
|
|
|
if (!selected.Any())
|
|
{
|
|
toastService.ShowWarning("No se seleccionó ningún producto.");
|
|
return;
|
|
}
|
|
|
|
await ModalInstance.CloseAsync(ModalResult.Ok(selected));
|
|
}
|
|
|
|
private async Task LoadMockStock()
|
|
{
|
|
StockList = new List<StockDisplayRow>
|
|
{
|
|
new StockDisplayRow { StockItemId = 101, ProductId = 1, ProductName = "Tornillo 4mm x 20mm", Batch = "LOTE001", Expiration = DateTime.Today.AddMonths(12), Available = 5, Selected = 0, LocationId = LocationId ?? 1 },
|
|
new StockDisplayRow { StockItemId = 102, ProductId = 1, ProductName = "Tornillo 4mm x 20mm", Batch = "LOTE002", Expiration = DateTime.Today.AddMonths(18), Available = 10, Selected = 0, LocationId = LocationId ?? 1 },
|
|
new StockDisplayRow { StockItemId = 103, ProductId = 2, ProductName = "Placa LCP 6 orificios", Batch = "PL001-A", Expiration = DateTime.Today.AddYears(2), Available = 3, Selected = 0, LocationId = LocationId ?? 1 }
|
|
};
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
public class StockDisplayRow
|
|
{
|
|
public int StockItemId { get; set; }
|
|
public int ProductId { get; set; }
|
|
public string ProductName { get; set; } = string.Empty;
|
|
public string Batch { get; set; } = string.Empty;
|
|
public DateTime? Expiration { get; set; }
|
|
public decimal Available { get; set; }
|
|
public decimal Selected { get; set; }
|
|
public int LocationId { get; set; }
|
|
}
|
|
}
|