All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (pull_request) Successful in 9m37s
close #68
264 lines
10 KiB
Plaintext
264 lines
10 KiB
Plaintext
@page "/salesdocuments/create"
|
|
@using Domain.Constants
|
|
@using Domain.Dtos.Sales
|
|
@using Domain.Generics
|
|
@using phronCare.UIBlazor.Services.Sales.SalesDocuments
|
|
@inject NavigationManager Navigation
|
|
@inject ISalesDocumentService SalesDocumentService
|
|
@inject IToastService toastService
|
|
|
|
<div class="container mt-4" style="zoom:.8;">
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-header d-flex justify-content-center align-items-center">
|
|
<h3 class="mb-0">Nuevo Sales Document desde remitos</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="alert alert-info mb-3">
|
|
Sales Document representa el documento comercial facturable. No emite comprobante fiscal ni integra ARCA/AFIP.
|
|
</div>
|
|
|
|
<div class="row g-2 mb-3">
|
|
<div class="col-md-3">
|
|
<label class="form-label">Cliente fiscal</label>
|
|
<input class="form-control" @bind="CustomerText" placeholder="Buscar cliente..." />
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Remito</label>
|
|
<input class="form-control" @bind="DeliveryNoteNumber" placeholder="Número de remito..." />
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label">Presupuesto ID</label>
|
|
<input type="number" class="form-control" @bind="QuoteId" />
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label">Desde</label>
|
|
<input type="date" class="form-control" @bind="IssueDateFrom" />
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label">Hasta</label>
|
|
<input type="date" class="form-control" @bind="IssueDateTo" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-end gap-2">
|
|
<button type="button" class="btn btn-outline-secondary rounded-pill" @onclick="ClearFilters" disabled="@IsLoading">Limpiar</button>
|
|
<button type="button" class="btn btn-primary rounded-pill" @onclick="SearchCandidatesAsync" disabled="@IsLoading">
|
|
@if (IsLoading)
|
|
{
|
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
}
|
|
Buscar remitos pendientes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">Remitos emitidos pendientes</h5>
|
|
<span class="badge bg-secondary">Seleccionados: @SelectedIds.Count</span>
|
|
</div>
|
|
<div class="card-body p-2">
|
|
@if (Candidates.Items.Any())
|
|
{
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-hover align-middle mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th style="width:48px;"></th>
|
|
<th>Remito</th>
|
|
<th>Fecha</th>
|
|
<th>Cliente fiscal</th>
|
|
<th>Presupuesto</th>
|
|
<th class="text-end">Ítems</th>
|
|
<th class="text-end">Importe aprobado</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var item in Candidates.Items)
|
|
{
|
|
<tr class="@(SelectedIds.Contains(item.Id) ? "table-primary" : string.Empty)">
|
|
<td class="text-center">
|
|
<input type="checkbox" class="form-check-input" checked="@SelectedIds.Contains(item.Id)" @onchange="args => ToggleSelection(item, args)" />
|
|
</td>
|
|
<td>@item.DeliveryNoteNumber</td>
|
|
<td>@item.IssueDate.ToString("dd/MM/yyyy")</td>
|
|
<td>@item.CustomerName</td>
|
|
<td>@(item.QuoteNumber ?? item.QuoteId?.ToString() ?? "-")</td>
|
|
<td class="text-end">@item.ItemCount</td>
|
|
<td class="text-end">@item.ApprovedAmount.ToString("N2")</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="text-center text-muted py-4">Buscá remitos emitidos pendientes de facturación.</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-header"><h5 class="mb-0">Resumen comercial</h5></div>
|
|
<div class="card-body">
|
|
@if (SelectedCandidates.Any())
|
|
{
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-4">
|
|
<strong>Cliente fiscal:</strong><br />
|
|
@SelectedCustomerLabel
|
|
</div>
|
|
<div class="col-md-2">
|
|
<strong>Remitos:</strong><br />@SelectedCandidates.Count
|
|
</div>
|
|
<div class="col-md-3">
|
|
<strong>Total:</strong><br />@SelectedTotal.ToString("N2")
|
|
</div>
|
|
<div class="col-md-3">
|
|
<strong>Validación:</strong><br />
|
|
@if (HasSingleFiscalCustomer)
|
|
{
|
|
<span class="badge bg-success">Cliente único</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-danger">Clientes fiscales distintos</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Observaciones</label>
|
|
<textarea class="form-control" rows="3" @bind="Observations"></textarea>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="text-muted">Seleccioná uno o más remitos para ver el resumen.</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-end gap-2">
|
|
<button type="button" class="btn btn-secondary rounded-pill" @onclick="BackToList" disabled="@IsSaving">
|
|
<i class="fas fa-arrow-left me-1"></i> Volver
|
|
</button>
|
|
<button type="button" class="btn btn-primary rounded-pill" @onclick="CreateAsync" disabled="@(IsSaving || !CanCreate)">
|
|
@if (IsSaving)
|
|
{
|
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
}
|
|
<i class="fas fa-save me-1"></i> Crear Sales Document
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
private string? CustomerText;
|
|
private string? DeliveryNoteNumber;
|
|
private int? QuoteId;
|
|
private DateTime? IssueDateFrom;
|
|
private DateTime? IssueDateTo;
|
|
private string? Observations;
|
|
private bool IsLoading;
|
|
private bool IsSaving;
|
|
private PagedResult<SalesDocumentDeliveryNoteCandidateDto> Candidates = new();
|
|
private readonly HashSet<int> SelectedIds = new();
|
|
private readonly Dictionary<int, SalesDocumentDeliveryNoteCandidateDto> SelectedMap = new();
|
|
|
|
private List<SalesDocumentDeliveryNoteCandidateDto> SelectedCandidates => SelectedMap.Values.OrderBy(x => x.IssueDate).ThenBy(x => x.Id).ToList();
|
|
private bool HasSingleFiscalCustomer => SelectedCandidates.Select(x => x.CustomerId).Distinct().Count() <= 1;
|
|
private bool CanCreate => SelectedIds.Count > 0 && HasSingleFiscalCustomer && SelectedTotal > 0;
|
|
private decimal SelectedTotal => SelectedCandidates.Sum(x => x.ApprovedAmount);
|
|
private string SelectedCustomerLabel => SelectedCandidates.FirstOrDefault()?.CustomerName ?? "-";
|
|
|
|
private async Task SearchCandidatesAsync()
|
|
{
|
|
try
|
|
{
|
|
IsLoading = true;
|
|
Candidates = await SalesDocumentService.SearchDeliveryNoteCandidatesAsync(
|
|
null,
|
|
CustomerText,
|
|
DeliveryNoteNumber,
|
|
QuoteId,
|
|
IssueDateFrom,
|
|
IssueDateTo,
|
|
1,
|
|
50);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
toastService.ShowError(ex.Message);
|
|
}
|
|
finally
|
|
{
|
|
IsLoading = false;
|
|
}
|
|
}
|
|
|
|
private void ToggleSelection(SalesDocumentDeliveryNoteCandidateDto item, ChangeEventArgs args)
|
|
{
|
|
var selected = args.Value is bool value && value;
|
|
if (selected)
|
|
{
|
|
SelectedIds.Add(item.Id);
|
|
SelectedMap[item.Id] = item;
|
|
}
|
|
else
|
|
{
|
|
SelectedIds.Remove(item.Id);
|
|
SelectedMap.Remove(item.Id);
|
|
}
|
|
}
|
|
|
|
private void ClearFilters()
|
|
{
|
|
CustomerText = null;
|
|
DeliveryNoteNumber = null;
|
|
QuoteId = null;
|
|
IssueDateFrom = null;
|
|
IssueDateTo = null;
|
|
Candidates = new PagedResult<SalesDocumentDeliveryNoteCandidateDto>();
|
|
SelectedIds.Clear();
|
|
SelectedMap.Clear();
|
|
}
|
|
|
|
private async Task CreateAsync()
|
|
{
|
|
if (!CanCreate)
|
|
{
|
|
toastService.ShowError("Debe seleccionar remitos pendientes de un único cliente fiscal.");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
IsSaving = true;
|
|
var created = await SalesDocumentService.CreateFromDeliveryNotesAsync(new SalesDocumentCreateFromDeliveryNotesRequest
|
|
{
|
|
DeliveryNoteIds = SelectedIds.ToList(),
|
|
DocumentType = (int)SalesDocumentType.Invoice,
|
|
IssueDate = DateTime.Today,
|
|
Currency = "ARS",
|
|
ExchangeRate = 1,
|
|
Observations = Observations
|
|
});
|
|
|
|
toastService.ShowSuccess("Sales Document creado correctamente desde remitos.");
|
|
Navigation.NavigateTo($"/salesdocuments/{created.Id}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
toastService.ShowError(ex.Message);
|
|
}
|
|
finally
|
|
{
|
|
IsSaving = false;
|
|
}
|
|
}
|
|
|
|
private void BackToList() => Navigation.NavigateTo("/salesdocuments");
|
|
}
|