@page "/salesdocuments/create" @using System.ComponentModel.DataAnnotations @using Blazored.Typeahead @using Domain.Constants @using Domain.Dtos @using Domain.Dtos.Sales @using Domain.Entities @using phronCare.UIBlazor.Services.Lookups @using phronCare.UIBlazor.Services.Sales.SalesDocuments @inject NavigationManager Navigation @inject ISalesDocumentService SalesDocumentService @inject ISalesLookupService SalesLookupService @inject IToastService toastService

Nuevo Sales Document

@foreach (var item in DocumentTypeOptions) { }
@item.Nombre @item.Nombre
@item.Nombre @item.Nombre
@foreach (var item in CoverageTypeOptions) { }
Detalles
@if (Items.Any()) {
@foreach (var item in Items) { }
# Origen Origin ID Quote detail Descripción Cantidad Unitario Impuesto Total
@item.LineNumber @foreach (var option in OriginTypeOptions) { } @GetItemTotal(item).ToString("N2")
} else {
No hay ítems cargados.
}
@code { private SalesDocumentCreatePageModel Model = new() { IssueDate = DateTime.Today, DocumentType = (int)SalesDocumentType.Invoice, Currency = "ARS", ExchangeRate = 1, CoverageType = (int)SalesDocumentCoverageType.Manual, CoveragePercentage = 100 }; private ELookUpItem? SelectedCustomer; private ELookUpItem? SelectedBillToCustomer; private List Items = new(); private bool IsSaving; private static readonly List DocumentTypeOptions = Enum.GetValues() .Select(x => new SelectOption((int)x, GetDocumentTypeLabel((int)x))) .ToList(); private static readonly List CoverageTypeOptions = Enum.GetValues() .Select(x => new SelectOption((int)x, GetCoverageTypeLabel((int)x))) .ToList(); private static readonly List OriginTypeOptions = Enum.GetValues() .Select(x => new SelectOption((int)x, GetOriginTypeLabel(x))) .ToList(); protected override void OnInitialized() { AddItem(); } private void AddItem() { Items.Add(new SalesDocumentItemRow { LineNumber = Items.Count + 1, OriginType = (int)SalesDocumentOriginType.Manual, Quantity = 1, UnitPrice = 0, TaxAmount = 0 }); } private void RemoveItem(SalesDocumentItemRow 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; if (SelectedBillToCustomer is null && customer is not null) { SelectedBillToCustomer = customer; Model.BillToCustomerId = customer.Id; } return Task.CompletedTask; } private Task OnBillToCustomerSelected(ELookUpItem? customer) { SelectedBillToCustomer = customer; Model.BillToCustomerId = customer?.Id; return Task.CompletedTask; } private string? ValidateBeforeSave() { if (Model.CustomerId is null or <= 0) return "Debe seleccionar un cliente."; if (Model.BillToCustomerId is null or <= 0) return "Debe seleccionar un cliente de facturación."; if (Model.QuoteId is null or <= 0) return "Debe informar un Presupuesto ID para coverage."; if (Items.Count == 0) return "Debe incluir al menos un detail."; if (Items.Any(x => string.IsNullOrWhiteSpace(x.Description))) return "Todos los detalles deben tener descripción."; if (Items.Any(x => x.Quantity <= 0)) return "Todos los detalles deben tener cantidad mayor a cero."; if (Items.Any(x => x.UnitPrice < 0 || x.TaxAmount < 0)) return "Los importes no pueden ser negativos."; if (Items.Any(x => x.OriginType != (int)SalesDocumentOriginType.Manual && (!x.OriginId.HasValue || x.OriginId.Value <= 0) && (!x.QuoteDetailId.HasValue || x.QuoteDetailId.Value <= 0))) return "Los detalles no manuales deben informar Origin ID o Quote detail."; if (Items.Sum(GetItemTotal) <= 0) return "El total del documento debe ser mayor a cero."; return null; } private async Task HandleValidSubmit() { var validationError = ValidateBeforeSave(); if (!string.IsNullOrWhiteSpace(validationError)) { toastService.ShowError(validationError); return; } try { IsSaving = true; ReindexItems(); var request = new SalesDocumentCreateRequest { DocumentType = Model.DocumentType, QuoteId = Model.QuoteId, CustomerId = Model.CustomerId!.Value, BillToCustomerId = Model.BillToCustomerId!.Value, IssueDate = Model.IssueDate, Currency = Model.Currency.Trim(), ExchangeRate = Model.ExchangeRate <= 0 ? 1 : Model.ExchangeRate, Observations = Model.Observations, PeriodFrom = Model.PeriodFrom, PeriodTo = Model.PeriodTo, Details = Items.Select(x => { var netAmount = GetItemNet(x); var totalAmount = GetItemTotal(x); return new SalesDocumentCreateDetailRequest { LineNumber = x.LineNumber, OriginType = (SalesDocumentOriginType)x.OriginType, OriginId = x.OriginId, QuoteDetailId = x.QuoteDetailId, Description = x.Description.Trim(), Quantity = x.Quantity, UnitPrice = x.UnitPrice, NetAmount = netAmount, TaxAmount = x.TaxAmount, TotalAmount = totalAmount }; }).ToList(), Coverage = new List { new() { QuoteId = Model.QuoteId!.Value, CoverageType = Model.CoverageType, CoveragePercentage = Model.CoveragePercentage, CoverageAmount = Items.Sum(GetItemTotal), PeriodFrom = Model.PeriodFrom, PeriodTo = Model.PeriodTo, Notes = "Coverage manual desde UI" } } }; var created = await SalesDocumentService.CreateAsync(request); toastService.ShowSuccess("Sales Document creado correctamente."); Navigation.NavigateTo($"/salesdocuments/{created.Id}"); } catch (Exception ex) { toastService.ShowError(ex.Message); } finally { IsSaving = false; } } private void BackToList() => Navigation.NavigateTo("/salesdocuments"); private static decimal GetItemNet(SalesDocumentItemRow item) => item.Quantity * item.UnitPrice; private static decimal GetItemTotal(SalesDocumentItemRow item) => GetItemNet(item) + item.TaxAmount; private static string GetDocumentTypeLabel(int value) => Enum.IsDefined(typeof(SalesDocumentType), value) ? ((SalesDocumentType)value) switch { SalesDocumentType.Invoice => "Factura", SalesDocumentType.DebitNote => "Nota de débito", SalesDocumentType.CreditNote => "Nota de crédito", SalesDocumentType.CreditInvoice => "Factura crédito", SalesDocumentType.CreditDebitNote => "N/D crédito", SalesDocumentType.CreditCreditNote => "N/C crédito", _ => value.ToString() } : value.ToString(); private static string GetCoverageTypeLabel(int value) => Enum.IsDefined(typeof(SalesDocumentCoverageType), value) ? ((SalesDocumentCoverageType)value) switch { SalesDocumentCoverageType.Direct => "Directa", SalesDocumentCoverageType.Capita => "Cápita", SalesDocumentCoverageType.Adjustment => "Ajuste", SalesDocumentCoverageType.Manual => "Manual", _ => value.ToString() } : value.ToString(); private static string GetOriginTypeLabel(SalesDocumentOriginType value) => value switch { SalesDocumentOriginType.Manual => "Manual", SalesDocumentOriginType.QuoteDetail => "Presupuesto", SalesDocumentOriginType.Adjustment => "Ajuste", SalesDocumentOriginType.Capita => "Cápita", SalesDocumentOriginType.DeliveryNote => "Remito", _ => value.ToString() }; private sealed class SalesDocumentCreatePageModel { [Required(ErrorMessage = "La fecha es obligatoria.")] public DateTime? IssueDate { get; set; } public int DocumentType { get; set; } [Required(ErrorMessage = "El cliente es obligatorio.")] public int? CustomerId { get; set; } [Required(ErrorMessage = "El cliente de facturación es obligatorio.")] public int? BillToCustomerId { get; set; } [Required(ErrorMessage = "El presupuesto es obligatorio para coverage.")] public int? QuoteId { get; set; } [Required(ErrorMessage = "La moneda es obligatoria.")] public string Currency { get; set; } = string.Empty; public decimal ExchangeRate { get; set; } public int CoverageType { get; set; } public decimal? CoveragePercentage { get; set; } public DateTime? PeriodFrom { get; set; } public DateTime? PeriodTo { get; set; } public string? Observations { get; set; } } private sealed class SalesDocumentItemRow { public int LineNumber { get; set; } public int 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 decimal UnitPrice { get; set; } public decimal TaxAmount { get; set; } } private sealed record SelectOption(int Value, string Label); }