@page "/deliverynotes/create" @using System.ComponentModel.DataAnnotations @using Blazored.Typeahead @using Domain.Constants @using Domain.Dtos @using Domain.Dtos.Sales @using phronCare.UIBlazor.Services.Lookups @using phronCare.UIBlazor.Services.Sales.DeliveryNotes @using phronCare.UIBlazor.Services.Sales.Quotes @using phronCare.UIBlazor.Shared.Modals @inject NavigationManager Navigation @inject IDeliveryNoteService DeliveryNoteService @inject ISalesLookupService SalesLookupService @inject IQuoteService QuoteService @inject IToastService toastService @inject IModalService Modal Emisión de Remito Número de remito Fecha de emisión Presupuesto aprobado (opcional) @item.Nombre @item.Nombre Cliente @item.Nombre @item.Nombre Observaciones @if (SelectedQuote is not null) { Presupuesto vinculado: @SelectedQuote.Nombre } Ítems del remito Agregar ítem @if (Items.Any()) { # Descripción Cantidad Origen Notas @foreach (var item in Items) { @item.LineNumber Manual Presupuesto Producto venta Producto stock RemoveItem(item)"> } } else { No hay ítems cargados. } Volver @if (IsSaving) { } Emitir remito @code { private DeliveryNoteCreatePageModel Model = new() { IssueDate = DateTime.Today }; private ELookUpItem? SelectedCustomer; private ELookUpItem? SelectedQuote; private List Items = new(); private bool IsSaving; private void AddItem() { Items.Add(new DeliveryNoteItemRow { LineNumber = Items.Count + 1, OriginType = (byte)DeliveryNoteItemOriginType.Manual, Quantity = 1 }); } private void RemoveItem(DeliveryNoteItemRow item) { if (Items.Remove(item)) { ReindexItems(); } } private void ReindexItems() { for (var i = 0; i < Items.Count; i++) { Items[i].LineNumber = i + 1; } } private Task OnCustomerSelected(ELookUpItem? customer) { SelectedCustomer = customer; Model.CustomerId = customer?.Id; return Task.CompletedTask; } private async Task OnQuoteSelected(ELookUpItem? quote) { SelectedQuote = quote; Model.QuoteId = quote?.Id; if (quote is null) return; var quoteDto = await QuoteService.GetDtoByIdAsync(quote.Id); if (quoteDto is null) { toastService.ShowError("No se pudo cargar el presupuesto seleccionado."); return; } var mappedItems = BuildItemsFromApprovedQuote(quoteDto); if (mappedItems.Count == 0) { toastService.ShowWarning("El presupuesto seleccionado no tiene ítems aprobados para precargar."); return; } if (Items.Any()) { var parameters = new ModalParameters(); parameters.Add(nameof(ConfirmModal.Title), "Reemplazar ítems"); parameters.Add(nameof(ConfirmModal.Message), "Ya hay ítems cargados. ¿Desea reemplazarlos por los ítems aprobados del presupuesto?"); var modal = Modal.Show("Confirmación", parameters); var result = await modal.Result; if (result.Cancelled) return; } Items = mappedItems; ReindexItems(); StateHasChanged(); } private List BuildItemsFromApprovedQuote(QuoteDto quote) { return quote.Items .Where(item => item.Approved) .Select(item => new { Item = item, Quantity = item.ApprovedQuantity.HasValue && item.ApprovedQuantity.Value > 0 ? item.ApprovedQuantity.Value : item.Quantity }) .Where(x => x.Quantity > 0) .Select((x, index) => new DeliveryNoteItemRow { LineNumber = index + 1, OriginType = (byte)DeliveryNoteItemOriginType.QuoteDetail, QuoteDetailId = x.Item.Id, Description = x.Item.Description, Quantity = x.Quantity }) .ToList(); } private string? ValidateBeforeSave() { if (Items.Count == 0) return "Debe incluir al menos un ítem."; if (Items.Any(x => string.IsNullOrWhiteSpace(x.Description))) return "Todos los ítems deben tener descripción."; if (Items.Any(x => x.Quantity <= 0)) return "Todos los ítems deben tener cantidad mayor a cero."; return null; } private async Task HandleValidSubmit() { var validationError = ValidateBeforeSave(); if (!string.IsNullOrWhiteSpace(validationError)) { toastService.ShowError(validationError); return; } try { IsSaving = true; var request = new DeliveryNoteCreateRequest { DeliveryNoteNumber = Model.DeliveryNoteNumber.Trim(), IssueDate = Model.IssueDate!.Value, CustomerId = Model.CustomerId!.Value, QuoteId = Model.QuoteId, Observations = Model.Observations, ExtraInfoJson = Model.ExtraInfoJson, Items = Items.Select(x => new DeliveryNoteCreateItemRequest { OriginType = x.OriginType, OriginId = x.OriginId, QuoteDetailId = x.QuoteDetailId, Description = x.Description.Trim(), Quantity = x.Quantity, Notes = string.IsNullOrWhiteSpace(x.Notes) ? null : x.Notes.Trim() }).ToList() }; var response = await DeliveryNoteService.CreateAndIssueAsync(request); toastService.ShowSuccess($"Remito {response.DeliveryNoteNumber} emitido correctamente."); Navigation.NavigateTo("/deliverynotes"); } catch (Exception ex) { toastService.ShowError(ex.Message); } finally { IsSaving = false; } } private void BackToList() { Navigation.NavigateTo("/deliverynotes"); } private sealed class DeliveryNoteCreatePageModel { [Required(ErrorMessage = "El número de remito es obligatorio.")] public string DeliveryNoteNumber { get; set; } = string.Empty; [Required(ErrorMessage = "La fecha de emisión es obligatoria.")] public DateTime? IssueDate { get; set; } [Required(ErrorMessage = "El cliente es obligatorio.")] public int? CustomerId { get; set; } public int? QuoteId { get; set; } public string? Observations { get; set; } public string? ExtraInfoJson { get; set; } } private sealed class DeliveryNoteItemRow { public int LineNumber { get; set; } public byte OriginType { get; set; } public int? OriginId { get; set; } public int? QuoteDetailId { get; set; } public string Description { get; set; } = string.Empty; public decimal Quantity { get; set; } public string? Notes { get; set; } } }