@page "/stock/expeditions/create" @using Blazored.Typeahead @using Domain.Dtos.Stock @using Domain.Entities @using Services.Lookups @using Services.Stock.Expeditions @using phronCare.UIBlazor.Pages.Stock.Shared @inject NavigationManager Navigation @inject ExpeditionService expeditionService @inject ISalesLookupService lookUpService @inject IToastService toastService @inject IModalService Modal

Nueva Expedición

@item.Nombre @item.Nombre
@if (!string.IsNullOrWhiteSpace(DispatchInstruction)) {
Instrucciones desde presupuesto:
@DispatchInstruction
}
Productos a Expedir
@if (Details.Any()) { @foreach (var item in Details) { }
Producto Cant. Lote Serial Vencimiento Ubicación
@item.ProductId @item.Quantity @item.Batch @item.Serial @item.Expiration?.ToString("yyyy-MM-dd") @item.LocationId
} else {

No hay productos agregados.

}
@code { private ELSExpeditionHeader Model = new(); private ExtraInfoModel ExtraInfo = new(); private ELookUpItem? SelectedQuote; private List Details = new(); private List ProductSetItems = new(); private string DispatchInstruction = string.Empty; private string ticketIdString { get => Model.TicketId?.ToString() ?? string.Empty; set => Model.TicketId = Guid.TryParse(value, out var guid) ? guid : null; } private async Task> SearchQuotes(string filter) { return await lookUpService.SearchApprovedQuotesAsync(filter); } private async Task OnQuoteSelected(ELookUpItem? selected) { if (selected is null || string.IsNullOrWhiteSpace(selected.Nombre)) { SelectedQuote = null; ExtraInfo = new(); // Limpiar datos cargados DispatchInstruction = ""; return; } SelectedQuote = selected; var quoteNumber = selected.Nombre.Split(" - ")[0]; var quote = await expeditionService.GetQuoteByNumberAsync(quoteNumber); if (quote is null) { toastService.ShowError("No se pudo cargar el presupuesto."); return; } ExtraInfo.Professional = quote.ProfessionalName; ExtraInfo.Institution = quote.InstitutionName; ExtraInfo.Patient = quote.PatientName; ExtraInfo.SurgeryDate = quote.EstimatedDate; DispatchInstruction = quote.Observations ?? ""; } private void AddProduct() { // TODO: abrir modal de producto individual } private void AddSet() { // TODO: abrir modal de set } private void ScanProduct() { // TODO: activar input de escáner } private void RemoveItem(ELSExpeditionDetail item) { Details.Remove(item); } private async Task HandleValidSubmit() { // 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("", parameters, options); // var result = await modal.Result; // if (!result.Cancelled && result.Data is List 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() { var parameters = new ModalParameters(); parameters.Add(nameof(StockItemSelectorModal.SetItems), ProductSetItems); parameters.Add(nameof(StockItemSelectorModal.Snapshot), BuildSnapshotFromDetails()); // ← clave var options = new ModalOptions { Size = ModalSize.Large, HideHeader = true }; var modal = Modal.Show("", parameters, options); var result = await modal.Result; if (!result.Cancelled && result.Data is List selectedItems) { MergeSelectionsByBusinessKey(selectedItems); // ← usar merge (ver abajo) StateHasChanged(); toastService.ShowSuccess($"{selectedItems.Count} item(s) agregados/actualizados."); } } private void MergeSelectionsByBusinessKey(List selected) { 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, ProductName = s.ProductName, Quantity = newQty, Batch = s.Batch, Expiration = exp, TraceabilityType = s.TraceabilityType, // UI only (no DB) Serial = s.Serial, LocationId = s.LocationId }); } // Si newQty == 0 y no existía, no hay nada que hacer } } } private class ExtraInfoModel { public string? Professional { get; set; } public string? Institution { get; set; } public string? Patient { get; set; } public DateTime? SurgeryDate { get; set; } } private List 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(); } }