@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 Presupuesto aprobado @item.Nombre @item.Nombre Ticket ID Profesional Institución Paciente Fecha de Cirugía Observaciones @if (!string.IsNullOrWhiteSpace(DispatchInstruction)) { Instrucciones desde presupuesto: @DispatchInstruction } Productos a Expedir Producto Set-Box Scanner @if (Details.Any()) { Producto Cant. Lote Serial Vencimiento Ubicación @foreach (var item in Details) { @item.ProductId @item.Quantity @item.Batch @item.Serial @item.Expiration?.ToString("yyyy-MM-dd") @item.LocationId RemoveItem(item)"> } } else { No hay productos agregados. } Guardar Expedición @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); 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 ); var newQty = s.Quantity < 0 ? 0 : s.Quantity; // Serial ⇒ siempre 1 if (!string.IsNullOrWhiteSpace(s.Serial)) newQty = 1; if (existing is not null) { if (newQty == 0) { if (existing.Quantity > 0) { // ⚠️ Caso snapshot con 0 → ignorar, mantener la fila continue; } // 0 explícito válido → borrar Details.Remove(existing); } else { // SET: exactamente lo que vino del modal existing.Quantity = newQty; existing.ProductName = s.ProductName; existing.Batch = s.Batch; existing.Serial = s.Serial; existing.Expiration = exp; existing.LocationId = s.LocationId; existing.TraceabilityType = s.TraceabilityType; } } else { if (newQty > 0) { Details.Add(new ELSExpeditionDetail { ProductId = s.ProductId, ProductName = s.ProductName, Quantity = newQty, Batch = s.Batch, Expiration = exp, TraceabilityType = s.TraceabilityType, Serial = s.Serial, LocationId = s.LocationId }); } } } } 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(); } }
No hay productos agregados.