Arreglo Fix1 SelectorScam
Some checks failed
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Failing after 1m2s

This commit is contained in:
Leandro Hernan Rojas 2025-08-24 02:31:12 -03:00
parent d884970019
commit 345bc817c4
8 changed files with 401 additions and 110 deletions

View File

@ -20,7 +20,7 @@
/// <summary> /// <summary>
/// Nombre o descripción corta del producto (solo para visualización en UI) /// Nombre o descripción corta del producto (solo para visualización en UI)
/// </summary> /// </summary>
public string ProductName { get; set; } = string.Empty; public string? ProductName { get; set; } = string.Empty;
/// <summary> /// <summary>
/// Lote del stock seleccionado /// Lote del stock seleccionado
@ -37,6 +37,10 @@
/// </summary> /// </summary>
public DateTime? Expiration { get; set; } public DateTime? Expiration { get; set; }
/// <summary>
/// Tipo de trazabilidad: 1=No aplica, 2=Por cantidad, 3=Por lote y vencimiento
/// </summary>
public int TraceabilityType { get; set; }
/// <summary> /// <summary>
/// Cantidad que el usuario desea usar (puede ser decimal si es por peso o volumen) /// Cantidad que el usuario desea usar (puede ser decimal si es por peso o volumen)

View File

@ -0,0 +1,44 @@
namespace Domain.Dtos.Stock
{
/// <summary>
/// Contexto de lo ya agregado en la expedición (no se persiste).
/// Sirve para evitar duplicados y calcular disponible efectivo.
/// </summary>
public class StockSnapshotItem
{
public int ProductId { get; set; }
public string? ProductName { get; set; } = string.Empty;
public int LocationId { get; set; }
public string Batch { get; set; } = string.Empty;
public DateOnly? Expiration { get; set; }
public string Serial { get; set; } = string.Empty;
/// <summary> 1=No aplica, 2=Por cantidad, 3=Por lote y vencimiento (solo UI/validación; no se persiste). </summary>
public int TraceabilityType { get; set; }
/// <summary>Cantidad ya agregada en la expedición para esta clave de negocio.</summary>
public decimal Quantity { get; set; }
/// <summary>Clave de fusión para idempotencia (no se persiste).</summary>
public string BusinessKey { get; set; } = string.Empty;
}
public static class StockKeys
{
public static string BuildBusinessKey(int productId, int locationId, string batch, DateOnly? expiration, string serial)
{
if (!string.IsNullOrWhiteSpace(serial))
return $"P{productId}-S{serial.Trim()}";
batch = batch?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(batch) && expiration.HasValue)
return $"P{productId}-L{locationId}-B{batch}-E{expiration.Value:yyyyMMdd}";
if (!string.IsNullOrEmpty(batch))
return $"P{productId}-L{locationId}-B{batch}";
return $"P{productId}-L{locationId}";
}
}
}

View File

@ -47,6 +47,19 @@
/// </summary> /// </summary>
public string? Description { get; set; } public string? Description { get; set; }
/// <summary>
/// Tipo de trazabilidad: 1=No aplica, 2=Por cantidad, 3=Por lote y vencimiento
/// ATENCION: Campo auxiliar de UI/servicio para aplicar reglas de trazabilidad.
/// No se persiste: la DB no tiene esta columna.
/// </summary>
public int TraceabilityType { get; set; }
/// <summary>
/// Nombre del producto: Nombre descriptivo de la tabla Products
/// ATENCION: Campo auxiliar de UI/servicio para aplicar reglas de trazabilidad.
/// No se persiste: la DB no tiene esta columna.
/// </summary>
public string? ProductName { get; set; } = string.Empty;
/// <summary> /// <summary>
/// Precio estimado unitario del producto (sin efecto contable) /// Precio estimado unitario del producto (sin efecto contable)
/// </summary> /// </summary>

View File

@ -6,17 +6,12 @@
@using Services.Stock.Expeditions @using Services.Stock.Expeditions
@using phronCare.UIBlazor.Pages.Stock.Shared @using phronCare.UIBlazor.Pages.Stock.Shared
@* @using static phronCare.UIBlazor.Pages.Stock.Shared.StockItemSelectorModal
*@
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject ExpeditionService expeditionService @inject ExpeditionService expeditionService
@* @inject Lookup lookUpService *@
@inject ISalesLookupService lookUpService @inject ISalesLookupService lookUpService
@inject IToastService toastService @inject IToastService toastService
@inject IModalService Modal @inject IModalService Modal
<EditForm Model="Model" OnValidSubmit="HandleValidSubmit"> <EditForm Model="Model" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator /> <DataAnnotationsValidator />
<ValidationSummary /> <ValidationSummary />
@ -154,12 +149,12 @@
@code { @code {
private ELSExpeditionHeader Model = new(); private ELSExpeditionHeader Model = new();
private ExtraInfoModel ExtraInfo = new(); private ExtraInfoModel ExtraInfo = new();
private string DispatchInstruction = string.Empty;
private ELookUpItem? SelectedQuote; private ELookUpItem? SelectedQuote;
private List<ELSExpeditionDetail> Details = new();
private List<ELSExpeditionDetail> Details = new();
private List<ProductSetItemDto> ProductSetItems = new(); private List<ProductSetItemDto> ProductSetItems = new();
private string DispatchInstruction = string.Empty;
private string ticketIdString private string ticketIdString
{ {
@ -224,47 +219,133 @@
{ {
// TODO: Lógica de guardado de la expedición completa // TODO: Lógica de guardado de la expedición completa
} }
// private async Task OpenStockItemSelectorModal()
// {
// var parameters = new ModalParameters();
// parameters.Add(nameof(StockItemSelectorModal.SetItems), ProductSetItems); // o null
// //parameters.Add(nameof(StockItemSelectorModal.LocationId), SelectedLocationId);
// var options = new ModalOptions()
// {
// Size = ModalSize.Large,
// HideHeader = true
// };
// var modal = Modal.Show<StockItemSelectorModal>("", parameters, options);
// var result = await modal.Result;
// if (!result.Cancelled && result.Data is List<StockItemSelectionDto> selectedItems)
// {
// foreach (var s in selectedItems)
// {
// var detail = new ELSExpeditionDetail
// {
// ProductId = s.ProductId,
// Quantity = s.Quantity, // si es Serial*, probablemente 1
// Batch = s.Batch,
// Expiration = s.Expiration.HasValue
// ? DateOnly.FromDateTime(s.Expiration.Value)
// : (DateOnly?)null,
// TraceabilityType=s.TraceabilityType, //agregado al model pero no es registrable en la entidad
// Serial = s.Serial, // si es Serial*, probablemente null
// LocationId = s.LocationId // si tu detalle lo maneja
// };
// Details.Add(detail);
// }
// StateHasChanged();
// toastService.ShowSuccess($"{selectedItems.Count} item(s) agregados a la expedición.");
// }
// }
private async Task OpenStockItemSelectorModal() private async Task OpenStockItemSelectorModal()
{ {
var parameters = new ModalParameters(); var parameters = new ModalParameters();
parameters.Add(nameof(StockItemSelectorModal.SetItems), ProductSetItems); // o null parameters.Add(nameof(StockItemSelectorModal.SetItems), ProductSetItems);
//parameters.Add(nameof(StockItemSelectorModal.LocationId), SelectedLocationId); parameters.Add(nameof(StockItemSelectorModal.Snapshot), BuildSnapshotFromDetails()); // ← clave
var options = new ModalOptions() var options = new ModalOptions { Size = ModalSize.Large, HideHeader = true };
{
Size = ModalSize.Large,
HideHeader = true
};
var modal = Modal.Show<StockItemSelectorModal>("", parameters, options); var modal = Modal.Show<StockItemSelectorModal>("", parameters, options);
var result = await modal.Result; var result = await modal.Result;
if (!result.Cancelled && result.Data is List<StockItemSelectionDto> selectedItems) if (!result.Cancelled && result.Data is List<StockItemSelectionDto> selectedItems)
{ {
foreach (var s in selectedItems) MergeSelectionsByBusinessKey(selectedItems); // ← usar merge (ver abajo)
StateHasChanged();
toastService.ShowSuccess($"{selectedItems.Count} item(s) agregados/actualizados.");
}
}
private void MergeSelectionsByBusinessKey(List<StockItemSelectionDto> selected)
{ {
var detail = new ELSExpeditionDetail foreach (var s in selected)
{
var exp = s.Expiration.HasValue ? DateOnly.FromDateTime(s.Expiration.Value) : (DateOnly?)null;
var key = StockKeys.BuildBusinessKey(
s.ProductId,
s.LocationId,
s.Batch ?? string.Empty,
exp,
s.Serial ?? string.Empty
);
var existing = Details.FirstOrDefault(d =>
StockKeys.BuildBusinessKey(d.ProductId, d.LocationId, d.Batch ?? string.Empty, d.Expiration, d.Serial ?? string.Empty) == key
);
// Normalizo cantidad pedida desde el modal (total final)
var newQty = s.Quantity < 0 ? 0 : s.Quantity;
// Serial ⇒ siempre 1 (ignora lo que venga)
if (!string.IsNullOrWhiteSpace(s.Serial))
newQty = 1;
if (existing is not null)
{
if (newQty == 0)
{
// Si el modal dejó en 0, se elimina la fila
Details.Remove(existing);
}
else
{
// SET (no sumar): que quede exactamente como en el modal
existing.Quantity = newQty;
existing.ProductName = s.ProductName; // opcional, por si viene actualizado
existing.Batch = s.Batch;
existing.Serial = s.Serial;
existing.Expiration = exp;
existing.LocationId = s.LocationId;
existing.TraceabilityType = s.TraceabilityType; // UI only
}
}
else
{
// Si no existía y la cantidad es > 0, crear nueva fila
if (newQty > 0)
{
Details.Add(new ELSExpeditionDetail
{ {
ProductId = s.ProductId, ProductId = s.ProductId,
Quantity = s.Quantity, // si es Serial*, probablemente 1 ProductName = s.ProductName,
Quantity = newQty,
Batch = s.Batch, Batch = s.Batch,
Expiration = s.Expiration.HasValue Expiration = exp,
? DateOnly.FromDateTime(s.Expiration.Value) TraceabilityType = s.TraceabilityType, // UI only (no DB)
: (DateOnly?)null, Serial = s.Serial,
Serial = s.Serial, // si es Serial*, probablemente null LocationId = s.LocationId
LocationId = s.LocationId // si tu detalle lo maneja });
}; }
// Si newQty == 0 y no existía, no hay nada que hacer
Details.Add(detail);
} }
StateHasChanged();
toastService.ShowSuccess($"{selectedItems.Count} item(s) agregados a la expedición.");
} }
} }
private class ExtraInfoModel private class ExtraInfoModel
{ {
@ -273,4 +354,32 @@
public string? Patient { get; set; } public string? Patient { get; set; }
public DateTime? SurgeryDate { get; set; } public DateTime? SurgeryDate { get; set; }
} }
private List<StockSnapshotItem> BuildSnapshotFromDetails()
{
return Details.Select(d =>
{
var key = StockKeys.BuildBusinessKey(
d.ProductId,
d.LocationId,
d.Batch ?? string.Empty,
d.Expiration,
d.Serial ?? string.Empty
);
return new StockSnapshotItem
{
ProductId = d.ProductId,
ProductName = d.ProductName,
LocationId = d.LocationId,
Batch = d.Batch ?? string.Empty,
Expiration = d.Expiration,
Serial = d.Serial ?? string.Empty,
TraceabilityType = d.TraceabilityType, // UI only (no DB)
Quantity = d.Quantity,
BusinessKey = key
};
}).ToList();
}
} }

View File

@ -2,6 +2,7 @@
@using Blazored.Modal.Services @using Blazored.Modal.Services
@using Domain.Dtos.Stock @using Domain.Dtos.Stock
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@using System.Globalization
@inject IToastService toastService @inject IToastService toastService
@inject IStockScanService stockScanService @inject IStockScanService stockScanService
@ -36,14 +37,13 @@
<button type="button" class="btn btn-secondary mt-2" @onclick="HandleScan">Buscar</button> <button type="button" class="btn btn-secondary mt-2" @onclick="HandleScan">Buscar</button>
</div> </div>
@if (StockList.Any()) @if (HasLastScan)
{ {
var last = StockList.First(); var last = StockList.First();
<div class="alert alert-info small"> <div class="alert alert-info small">
Último escaneado: <strong>@last.ProductName</strong> | Lote <strong>@last.Batch</strong> | Venc: <strong>@last.Expiration?.ToShortDateString()</strong> Último escaneado: <strong>@last.ProductName</strong> | Lote <strong>@last.Batch</strong> | Venc: <strong>@last.Expiration?.ToShortDateString()</strong>
</div> </div>
} }
<table class="table table-sm table-bordered"> <table class="table table-sm table-bordered">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
@ -63,9 +63,14 @@
<td>@item.Batch</td> <td>@item.Batch</td>
<td>@item.Serial</td> <td>@item.Serial</td>
<td>@item.Expiration?.ToShortDateString()</td> <td>@item.Expiration?.ToShortDateString()</td>
<td>@item.Available</td> <td>@item.Available.ToString("N2", CultureInfo.CurrentCulture)</td>
<td> <td>
<InputNumber @bind-Value="item.Selected" class="form-control form-control-sm" min="0" max="@item.Available" /> <input type="number"
value="@(item.Selected.ToString("G29"))"
@onchange="@(n => item.Selected = decimal.Parse(n.Value.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture))"
min="0"
max="@(item.Available.ToString("G29"))"
class="form-control form-control-sm" />
</td> </td>
</tr> </tr>
} }
@ -83,28 +88,53 @@
[Parameter] public int? ProductId { get; set; } [Parameter] public int? ProductId { get; set; }
[Parameter] public int? LocationId { get; set; } [Parameter] public int? LocationId { get; set; }
[Parameter] public List<ProductSetItemDto>? SetItems { get; set; } //[Parameter] public List<ProductSetItemDto>? SetItems { get; set; }
[Parameter] public List<StockSnapshotItem> Snapshot { get; set; } = new();
// Mapa: BusinessKey -> Cantidad ya en la expedición
private Dictionary<string, decimal> _alreadyInExpedition = new();
private bool HasLastScan { get; set; }
//private readonly List<StockItemSelectionDto> SelectedItems = new();
private List<StockDisplayRow> StockList = new();
private string InputCode { get; set; } = string.Empty; private string InputCode { get; set; } = string.Empty;
private ElementReference scanInput; private ElementReference scanInput;
private readonly List<StockItemSelectionDto> SelectedItems = new(); protected override void OnParametersSet()
private List<StockDisplayRow> StockList = new();
protected override async Task OnInitializedAsync()
{ {
await LoadMockStock(); HasLastScan = false;
_alreadyInExpedition = Snapshot
.GroupBy(x => x.BusinessKey)
.ToDictionary(g => g.Key, g => g.Sum(x => x.Quantity));
// Precargar la tabla del modal con lo que ya estaba en la expedición
StockList = Snapshot.Select(s => new StockDisplayRow
{
ProductId = s.ProductId,
ProductName = s.ProductName ?? "<Producto sin descripción>",
Batch = s.Batch,
Serial = s.Serial,
Expiration = s.Expiration?.ToDateTime(TimeOnly.MinValue),
LocationId = s.LocationId,
TraceabilityType = s.TraceabilityType,
BusinessKey = s.BusinessKey,
Selected = s.Quantity, // cantidad actual en la expedición
Available = 0 // opcional: si querés recalcular, traer de BD
}).ToList();
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
await scanInput.FocusAsync(); await scanInput.FocusAsync();
} }
private async Task OnKeyDown(KeyboardEventArgs e) private async Task OnKeyDown(KeyboardEventArgs e)
{ {
if (e.Key == "Enter") if (e.Key == "Enter")
await HandleScan(); await HandleScan();
} }
private async Task HandleKeyDown(KeyboardEventArgs e) private async Task HandleKeyDown(KeyboardEventArgs e)
{ {
if (e.Key is "Enter" or "NumpadEnter") if (e.Key is "Enter" or "NumpadEnter")
@ -113,56 +143,136 @@
await scanInput.FocusAsync(); await scanInput.FocusAsync();
} }
} }
private async Task HandleScan() 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 try
{ {
var matchedItem = await stockScanService.ParseAndMatchAsync(code, LocationId ?? 1); var raw = InputCode?.Trim();
if (string.IsNullOrWhiteSpace(raw))
return;
// 3) BusinessKey (clave de fusión) y contexto // Parsear y resolver candidato desde backend
var s = await stockScanService.ParseAndMatchAsync(raw, LocationId ?? 1);
// limpiar input y devolver foco
InputCode = string.Empty;
//wait Refocus();
if (s is null)
{ {
toastService.ShowWarning("No se encontró el producto en stock."); toastService.ShowWarning("No se encontró un artículo para ese código.");
return;
}
HasLastScan = true;
// Mapear a fila del modal
var row = new StockDisplayRow
{
StockItemId = s.StockItemId,
ProductId = s.ProductId,
ProductName = s.ProductName ?? string.Empty,
Batch = s.Batch ?? string.Empty,
Serial = s.Serial ?? string.Empty,
Expiration = s.Expiration,
LocationId = s.LocationId,
Available = s.Quantity,
TraceabilityType = s.TraceabilityType,
Selected = 0
};
// Clave de negocio + contexto del snapshot
row.BusinessKey = BuildKeyFromRow(row);
var alreadyInExpedition = _alreadyInExpedition.TryGetValue(row.BusinessKey, out var q)
? q
: 0m;
var selectedInModal = StockList
.Where(x => x.BusinessKey == row.BusinessKey)
.Sum(x => x.Selected);
var effectiveAvailable = row.Available - selectedInModal;
// Reglas por trazabilidad
// SERIAL*: nunca duplicar ni incrementar
if (!string.IsNullOrWhiteSpace(row.Serial))
{
// buscar si ya existe la misma BusinessKey en la tabla del modal
var existingSerial = StockList.FirstOrDefault(x => x.BusinessKey == row.BusinessKey);
// 🔄 REFRESH siempre que tengamos la fila (si existe), antes de cualquier return
if (existingSerial is not null)
{
existingSerial.Available = row.Available ; // actualizar disponible con el del re-escaneo
}
var existsInModal = existingSerial is not null;
//var existsInModal = StockList.Any(x => x.BusinessKey == row.BusinessKey);
if (alreadyInExpedition > 0 || existsInModal)
{
toastService.ShowWarning("Este serial ya está agregado.");
await Refocus(); await Refocus();
return; return;
} }
if (StockList.Any(s => s.StockItemId == matchedItem.StockItemId)) if (effectiveAvailable <= 0)
{ {
toastService.ShowInfo("Este ítem ya está listado."); toastService.ShowWarning("No hay disponible para este serial.");
await Refocus(); await Refocus();
return; return;
} }
StockList.Insert(0, new StockDisplayRow row.Selected = 1; // serial siempre = 1
StockList.Insert(0, row);
await Refocus();
return;
}
// BATCH / NONE: sumar sin pasar el disponible efectivo
if (effectiveAvailable <= 0)
{ {
StockItemId = matchedItem.StockItemId, var existingSerial = StockList.FirstOrDefault(x => x.BusinessKey == row.BusinessKey);
ProductId = matchedItem.ProductId,
ProductName = matchedItem.ProductName, // 🔄 REFRESH siempre que tengamos la fila (si existe), antes de cualquier return
Batch = matchedItem.Batch, if (existingSerial is not null)
Serial = matchedItem.Serial, {
Expiration = matchedItem.Expiration, existingSerial.Available = row.Available; // actualizar disponible con el del re-escaneo
Available = matchedItem.Quantity, }
Selected = 1, toastService.ShowWarning("No hay más disponible para esta combinación.");
LocationId = matchedItem.LocationId await Refocus();
}); return;
}
var existingBN = StockList.FirstOrDefault(x => x.BusinessKey == row.BusinessKey);
if (existingBN is not null)
{
existingBN.Available = s.Quantity;
// suma +1 cap al disponible efectivo
var add = 1m;
var maxAdd = Math.Min(add, effectiveAvailable);
if (maxAdd <= 0)
{
toastService.ShowWarning("Se alcanzó el máximo disponible.");
await Refocus();
return;
}
existingBN.Selected += maxAdd;
await Refocus();
return;
}
// Nueva fila (otra ubicación/lote/etc.)
row.Selected = 1;
StockList.Insert(0, row);
await Refocus();
} }
catch (Exception ex) catch (Exception ex)
{ {
toastService.ShowError($"Error en escaneo: {ex.Message}"); toastService.ShowError($"Error al procesar el escaneo: {ex.Message}");
}
finally
{
InputCode = string.Empty;
await Refocus(); await Refocus();
StateHasChanged();
} }
} }
@ -172,8 +282,6 @@
await scanInput.FocusAsync(); // deja el cursor listo para el próximo escaneo await scanInput.FocusAsync(); // deja el cursor listo para el próximo escaneo
} }
private Task Cancel() => ModalInstance.CancelAsync();
private async Task Confirm() private async Task Confirm()
{ {
var selected = StockList var selected = StockList
@ -186,6 +294,7 @@
Batch = x.Batch, Batch = x.Batch,
Serial= x.Serial, Serial= x.Serial,
Expiration = x.Expiration, Expiration = x.Expiration,
TraceabilityType = x.TraceabilityType,
Quantity = x.Selected, Quantity = x.Selected,
LocationId = x.LocationId LocationId = x.LocationId
}) })
@ -196,20 +305,10 @@
toastService.ShowWarning("No se seleccionó ningún producto."); toastService.ShowWarning("No se seleccionó ningún producto.");
return; return;
} }
await ModalInstance.CloseAsync(ModalResult.Ok(selected)); await ModalInstance.CloseAsync(ModalResult.Ok(selected));
} }
private async Task LoadMockStock() private Task Cancel() => ModalInstance.CancelAsync();
{
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 class StockDisplayRow
{ {
@ -222,5 +321,20 @@
public decimal Available { get; set; } public decimal Available { get; set; }
public decimal Selected { get; set; } public decimal Selected { get; set; }
public int LocationId { get; set; } public int LocationId { get; set; }
public int TraceabilityType { get; set; }
/// <summary>Clave de fusión (no se persiste).</summary>
public string BusinessKey { get; set; } = string.Empty;
}
private static string BuildKeyFromRow(StockDisplayRow r)
{
return Domain.Dtos.Stock.StockKeys.BuildBusinessKey(
r.ProductId,
r.LocationId,
r.Batch,
r.Expiration.HasValue ? DateOnly.FromDateTime(r.Expiration.Value) : (DateOnly?)null,
r.Serial
);
} }
} }

View File

@ -1,23 +1,26 @@
using phronCare.UIBlazor;
using phronCare.UIBlazor.Services.UI;
using phronCare.UIBlazor.Services.Sales;
using phronCare.UIBlazor.Services.Lookups;
using phronCare.UIBlazor.Services.Tickets;
using phronCare.UIBlazor.Services.Authorization;
using phronCare.UIBlazor.Services.Sales.Quotes;
using phronCare.UIBlazor.Services.Integrations;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blazored.Modal; using Blazored.Modal;
using Blazored.Toast; using Blazored.Toast;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using phronCare.UIBlazor;
using phronCare.UIBlazor.Services.Authorization;
using phronCare.UIBlazor.Services.Integrations;
using phronCare.UIBlazor.Services.Lookups;
using phronCare.UIBlazor.Services.Sales;
using phronCare.UIBlazor.Services.Sales.Quotes;
using phronCare.UIBlazor.Services.Stock; using phronCare.UIBlazor.Services.Stock;
using phronCare.UIBlazor.Services.Stock.Expeditions; using phronCare.UIBlazor.Services.Stock.Expeditions;
using phronCare.UIBlazor.Services.Tickets;
using phronCare.UIBlazor.Services.UI;
using System.Globalization;
var builder = WebAssemblyHostBuilder.CreateDefault(args); var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after"); builder.RootComponents.Add<HeadOutlet>("head::after");
var culture = new CultureInfo("es-AR");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
#region Proveedor de Autorizacion #region Proveedor de Autorizacion
builder.Services.AddScoped<CustomAuthorizationProvider>(); builder.Services.AddScoped<CustomAuthorizationProvider>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthorizationProvider>(p => p.GetRequiredService<CustomAuthorizationProvider>()); builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthorizationProvider>(p => p.GetRequiredService<CustomAuthorizationProvider>());
@ -70,6 +73,4 @@ static void InjectDependencies(WebAssemblyHostBuilder builder)
builder.Services.AddScoped<LSProductService>(); builder.Services.AddScoped<LSProductService>();
builder.Services.AddScoped<LSUnitOfMeasureService>(); builder.Services.AddScoped<LSUnitOfMeasureService>();
builder.Services.AddScoped<ExpeditionService>(); builder.Services.AddScoped<ExpeditionService>();
} }

View File

@ -37,6 +37,7 @@ public class StockScanService : IStockScanService
Expiration = first.Expiration?.ToDateTime(TimeOnly.MinValue), Expiration = first.Expiration?.ToDateTime(TimeOnly.MinValue),
Quantity = first.AvailableQty, Quantity = first.AvailableQty,
LocationId = first.LocationId ?? locationId, LocationId = first.LocationId ?? locationId,
TraceabilityType = first.TraceabilityType,
Serial = first.Serial // si lo devolvés en el DTO de scan Serial = first.Serial // si lo devolvés en el DTO de scan
}; };
} }

View File

@ -5,6 +5,11 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<!-- Habilita datos de globalización (ICU) en WASM -->
<InvariantGlobalization>false</InvariantGlobalization>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="wwwroot\css\fontawesome\**" /> <Compile Remove="wwwroot\css\fontawesome\**" />