All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 5m24s
425 lines
18 KiB
Plaintext
425 lines
18 KiB
Plaintext
@page "/sales/customerform"
|
|
@page "/sales/customerform/{CustomerId:int}"
|
|
@using System.ComponentModel.DataAnnotations
|
|
@using phronCare.UIBlazor.Pages.Shared.Modals
|
|
@using phronCare.UIBlazor.Services.Sales
|
|
@inject IModalService Modal
|
|
@inject IToastService toastService
|
|
@inject HttpClient _httpClient
|
|
@inject NavigationManager Navigation
|
|
@inject AuthenticationStateProvider authenticationStateProvider
|
|
@inject AccountTypeService accountTypeService
|
|
@inject TaxConditionService taxConditionService
|
|
|
|
<div class="card" style="zoom:80%">
|
|
<div class="card-header d-flex justify-content-center align-items-center">
|
|
<h3 class="card-title m-0">Información del cliente</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<EditForm Model="@customer" OnValidSubmit="@HandleValidSubmit">
|
|
<DataAnnotationsValidator />
|
|
<ValidationSummary />
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label for="Name">Nombre / Razón Social:</label>
|
|
<InputText id="Name" @bind-Value="customer.Name" class="form-control" />
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="BusinessName">Sucursal / Nombre Comercial:</label>
|
|
<InputText id="BusinessName" @bind-Value="customer.BusinessName" class="form-control" />
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label for="AccounttypesId">Tipo de Cuenta:</label>
|
|
<InputSelect id="AccounttypesId" @bind-Value="customer.AccounttypesId" class="form-control">
|
|
<option value="">-- Seleccionar --</option>
|
|
@foreach (var type in accountTypes)
|
|
{
|
|
<option value="@type.Id">@type.Name</option>
|
|
}
|
|
</InputSelect>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="TaxConditionId">Condición Fiscal:</label>
|
|
<InputSelect id="TaxConditionId" @bind-Value="customer.TaxConditionId" class="form-control">
|
|
<option value="">-- Seleccionar --</option>
|
|
@foreach (var tax in taxConditions)
|
|
{
|
|
<option value="@tax.Id">@tax.Description</option>
|
|
}
|
|
</InputSelect>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-4 d-flex align-items-center justify-content-start mt-4">
|
|
<div class="form-check form-switch">
|
|
<InputCheckbox id="HasCreditAccount" @bind-Value="customer.HasCreditAccount" class="form-check-input" />
|
|
<label class="form-check-label ms-2" for="HasCreditAccount">¿Cuenta Corriente?</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="CreditLimit">Límite de Crédito:</label>
|
|
<InputNumber id="CreditLimit" @bind-Value="customer.CreditLimit" class="form-control" />
|
|
</div>
|
|
<div class="col-md-4 d-flex align-items-center justify-content-start mt-4">
|
|
<div class="form-check form-switch">
|
|
<InputCheckbox id="Active" @bind-Value="customer.Active" class="form-check-input" />
|
|
<label class="form-check-label ms-2" for="Active">Activo</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr />
|
|
<h5>Documentos</h5>
|
|
<div class="row align-items-end">
|
|
<div class="col-sm-4">
|
|
<label for="TipoDocumento">Tipo:</label>
|
|
<InputSelect id="TipoDocumento" class="form-control" @bind-Value="documentFormModel.DocumenttypesId">
|
|
<option value="">-- Seleccionar --</option>
|
|
@foreach (var tipo in documentTypes)
|
|
{
|
|
<option value="@tipo.Id">@tipo.Name</option>
|
|
}
|
|
</InputSelect>
|
|
</div>
|
|
<div class="col-sm-4">
|
|
<label for="NumeroDocumento">Número:</label>
|
|
<InputText id="NumeroDocumento" class="form-control" @bind-Value="documentFormModel.DocumentNumber" />
|
|
</div>
|
|
<div class="col-sm-2">
|
|
<button type="button" class="btn btn-sm btn-success" @onclick="AddCustomerDocument">Agregar documento</button>
|
|
</div>
|
|
</div>
|
|
<br />
|
|
@if (customer.PhSCustomerDocuments?.Any() == true)
|
|
{
|
|
<table class="table table-sm table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>Tipo</th>
|
|
<th>Número</th>
|
|
<th style="width:50px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var doc in customer.PhSCustomerDocuments)
|
|
{
|
|
<tr>
|
|
<td>@documentTypes.FirstOrDefault(t => t.Id == doc.DocumenttypesId)?.Name</td>
|
|
<td>@doc.DocumentNumber</td>
|
|
<td>
|
|
<button type="button" class="btn btn-sm btn-danger" @onclick="() => RemoveCustomerDocument(doc)">🗑</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
}
|
|
<hr />
|
|
<h5>Direcciones</h5>
|
|
@if (customer.PhSCustomerAddresses.Any())
|
|
{
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>Sucursal</th>
|
|
<th>Dirección</th>
|
|
<th>Ciudad</th>
|
|
<th>Provincia</th>
|
|
<th>País</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var address in customer.PhSCustomerAddresses.Select((value, index) => new { value, index }))
|
|
{
|
|
<tr>
|
|
<td>@address.value.BusinessName</td>
|
|
<td>@address.value.Streetaddress1</td>
|
|
<td>@address.value.City</td>
|
|
<td>@address.value.Stateprovince</td>
|
|
<td>@address.value.Country</td>
|
|
<td>
|
|
<button type="button" class="btn btn-sm btn-primary me-1" @onclick="() => EditAddress(address.index)">Editar</button>
|
|
<button type="button" class="btn btn-sm btn-danger" @onclick="() => RemoveAddress(address.index)">Eliminar</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
}
|
|
<div class="row align-items-end">
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<input class="form-control" placeholder="Nombre de sucursal" @bind="editingAddress.BusinessName" />
|
|
</div>
|
|
<div class="col-md-6">
|
|
<input class="form-control" placeholder="Dirección línea 1" @bind="editingAddress.Streetaddress1" />
|
|
</div>
|
|
</div>
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<input class="form-control" placeholder="Dirección línea 2" @bind="editingAddress.Streetaddress2" />
|
|
</div>
|
|
<div class="col-md-6">
|
|
<input class="form-control" placeholder="Ciudad" @bind="editingAddress.City" />
|
|
</div>
|
|
</div>
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<select class="form-control" value="@editingAddress.Country" @onchange="OnCountryChanged">
|
|
<option value="">Seleccionar país</option>
|
|
@foreach (var country in countries)
|
|
{
|
|
<option value="@country">@country</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
@if (editingAddress.Country == "Argentina" && provincesByCountry.TryGetValue("Argentina", out var provincias))
|
|
{
|
|
<select class="form-control" @bind="editingAddress.Stateprovince">
|
|
<option value="">Seleccionar provincia</option>
|
|
@foreach (var provincia in provincias)
|
|
{
|
|
<option value="@provincia">@provincia</option>
|
|
}
|
|
</select>
|
|
}
|
|
else
|
|
{
|
|
<input class="form-control" placeholder="Estado/Provincia" @bind="editingAddress.Stateprovince" />
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="row mb-3">
|
|
<div class="col-md-4">
|
|
<input class="form-control" placeholder="Código Postal" @bind="editingAddress.Postalcode" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<input class="form-control" placeholder="Teléfono" @bind="editingAddress.Phonenumber" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<input class="form-control" placeholder="Email" @bind="editingAddress.Email" />
|
|
</div>
|
|
</div>
|
|
<div class="row mb-3 row align-items-end align-items-center">
|
|
<div class="col-sm-8">
|
|
<textarea class="form-control" placeholder="Notas" @bind="editingAddress.Notes"></textarea>
|
|
</div>
|
|
<div class="col-sm-2">
|
|
<button type="button" class="btn btn-sm btn-success" @onclick="AddOrUpdateAddress">
|
|
@((editingIndex == -1) ? "Agregar dirección" : "Actualizar dirección")
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
@if (editingIndex != -1)
|
|
{
|
|
<button type="button" class="btn btn-sm btn-secondary ms-2" @onclick="CancelAddressEdit">Cancelar</button>
|
|
}
|
|
</div>
|
|
|
|
</EditForm>
|
|
</div>
|
|
<div class="card-footer">
|
|
<div class="d-flex justify-content-end align-items-center py-3">
|
|
<button class="btn btn-primary me-2" type="button" @onclick="HandleValidSubmit" disabled="@isSaving">
|
|
@(isSaving ? "Guardando..." : "Guardar Cliente") </button>
|
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
[Parameter]
|
|
public int? CustomerId { get; set; }
|
|
private ECustomer customer { get; set; } = new();
|
|
private List<EAccountType> accountTypes = new();
|
|
private List<ETaxCondition> taxConditions = new();
|
|
private List<EDocumentType> documentTypes = new();
|
|
private ECustomerDocument documentFormModel = new();
|
|
private bool isSaving = false;
|
|
private string returnUrl = "/sales/customers";
|
|
private List<string> countries = new() {
|
|
"Argentina", "Brasil", "Chile", "Uruguay", "Paraguay", "Estados Unidos", "Canadá",
|
|
"México", "Alemania", "Reino Unido", "Francia", "Italia", "España"
|
|
};
|
|
private Dictionary<string, List<string>> provincesByCountry = new()
|
|
{
|
|
{
|
|
"Argentina", new List<string>
|
|
{
|
|
"Buenos Aires", "CABA", "Catamarca", "Chaco", "Chubut", "Córdoba", "Corrientes",
|
|
"Entre Ríos", "Formosa", "Jujuy", "La Pampa", "La Rioja", "Mendoza", "Misiones",
|
|
"Neuquén", "Río Negro", "Salta", "San Juan", "San Luis", "Santa Cruz",
|
|
"Santa Fe", "Santiago del Estero", "Tierra del Fuego", "Tucumán"
|
|
}
|
|
}
|
|
};
|
|
private ECustomerAddress editingAddress = new();
|
|
private int editingIndex = -1;
|
|
private void OnCountryChanged(ChangeEventArgs e)
|
|
{
|
|
var selectedCountry = e.Value?.ToString();
|
|
editingAddress.Country = selectedCountry;
|
|
editingAddress.Stateprovince = string.Empty; // Resetear la provincia si cambia el país
|
|
}
|
|
private void AddOrUpdateAddress()
|
|
{
|
|
var context = new ValidationContext(editingAddress, null, null);
|
|
var results = new List<ValidationResult>();
|
|
|
|
if (!Validator.TryValidateObject(editingAddress, context, results, true))
|
|
{
|
|
// Mostrar errores en un toast o en pantalla
|
|
foreach (var error in results)
|
|
{
|
|
toastService.ShowError(error.ErrorMessage);
|
|
}
|
|
return;
|
|
}
|
|
if (editingIndex == -1)
|
|
{
|
|
// Agregar nueva dirección
|
|
customer.PhSCustomerAddresses.Add(editingAddress);
|
|
}
|
|
else
|
|
{
|
|
// Actualizar dirección existente
|
|
var existing = customer.PhSCustomerAddresses.ElementAt(editingIndex);
|
|
existing.BusinessName = editingAddress.BusinessName;
|
|
existing.Streetaddress1 = editingAddress.Streetaddress1;
|
|
existing.Streetaddress2 = editingAddress.Streetaddress2;
|
|
existing.City = editingAddress.City;
|
|
existing.Stateprovince = editingAddress.Stateprovince;
|
|
existing.Postalcode = editingAddress.Postalcode;
|
|
existing.Country = editingAddress.Country;
|
|
existing.Latitude = editingAddress.Latitude;
|
|
existing.Longitude = editingAddress.Longitude;
|
|
existing.Phonenumber = editingAddress.Phonenumber;
|
|
existing.Email = editingAddress.Email;
|
|
existing.Notes = editingAddress.Notes;
|
|
}
|
|
ResetAddressForm();
|
|
}
|
|
private void EditAddress(int index)
|
|
{
|
|
var addr = customer.PhSCustomerAddresses.ElementAt(index);
|
|
|
|
editingAddress = new ECustomerAddress
|
|
{
|
|
Id = addr.Id, // Incluí el Id como mencionamos antes, para no perder referencia
|
|
BusinessName = addr.BusinessName,
|
|
Streetaddress1 = addr.Streetaddress1,
|
|
Streetaddress2 = addr.Streetaddress2,
|
|
City = addr.City,
|
|
Stateprovince = addr.Stateprovince,
|
|
Postalcode = addr.Postalcode,
|
|
Country = addr.Country,
|
|
Latitude = addr.Latitude,
|
|
Longitude = addr.Longitude,
|
|
Phonenumber = addr.Phonenumber,
|
|
Email = addr.Email,
|
|
Notes = addr.Notes
|
|
};
|
|
|
|
editingIndex = index;
|
|
}
|
|
private void RemoveAddress(int index)
|
|
{
|
|
var itemToRemove = customer.PhSCustomerAddresses.ElementAt(index);
|
|
customer.PhSCustomerAddresses.Remove(itemToRemove);
|
|
ResetAddressForm();
|
|
}
|
|
private void CancelAddressEdit()
|
|
{
|
|
ResetAddressForm();
|
|
}
|
|
private void ResetAddressForm()
|
|
{
|
|
editingAddress = new();
|
|
editingIndex = -1;
|
|
}
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadAccountTypes();
|
|
await LoadTaxConditions();
|
|
await LoadDocumentTypes();
|
|
|
|
if (CustomerId.HasValue)
|
|
{
|
|
// Cargar datos del cliente existente desde la API
|
|
customer = await _httpClient.GetFromJsonAsync<ECustomer>($"/api/Customer/{CustomerId.Value}") ?? new();
|
|
}
|
|
}
|
|
private async Task LoadAccountTypes()
|
|
{
|
|
accountTypes = await accountTypeService.GetAllAsync();
|
|
}
|
|
private async Task LoadTaxConditions()
|
|
{
|
|
taxConditions = await taxConditionService.GetAllAsync();
|
|
}
|
|
private async Task LoadDocumentTypes()
|
|
{
|
|
var result = await _httpClient.GetFromJsonAsync<List<EDocumentType>>("/api/DocumentType/GetAll");
|
|
documentTypes = result ?? new();
|
|
}
|
|
private void AddCustomerDocument()
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(documentFormModel.DocumentNumber))
|
|
{
|
|
customer.PhSCustomerDocuments.Add(documentFormModel);
|
|
documentFormModel = new();
|
|
}
|
|
}
|
|
private void RemoveCustomerDocument(ECustomerDocument document)
|
|
{
|
|
customer.PhSCustomerDocuments.Remove(document);
|
|
}
|
|
private void RemoveCustomerAddress(ECustomerAddress address)
|
|
{
|
|
customer.PhSCustomerAddresses.Remove(address);
|
|
}
|
|
private async Task HandleValidSubmit()
|
|
{
|
|
var parameters = new ModalParameters();
|
|
parameters.Add("Message", "¿Desea guardar los cambios del cliente?");
|
|
var modal = Modal.Show<ConfirmModal>("Confirmacion", parameters);
|
|
var result = await modal.Result;
|
|
if (result.Cancelled)
|
|
return;
|
|
try
|
|
{
|
|
HttpResponseMessage response;
|
|
if (CustomerId.HasValue)
|
|
{
|
|
response = await _httpClient.PutAsJsonAsync("/api/Customer/Update", customer);
|
|
}
|
|
else
|
|
{
|
|
response = await _httpClient.PostAsJsonAsync("/api/Customer/Create", customer);
|
|
}
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
toastService.ShowSuccess("Cliente guardado exitosamente");
|
|
Navigation.NavigateTo(returnUrl);
|
|
}
|
|
else
|
|
{
|
|
var error = await response.Content.ReadAsStringAsync();
|
|
toastService.ShowError($"Error: {error}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
toastService.ShowError($"Error: {ex.Message}");
|
|
}
|
|
}
|
|
private void Cancel()
|
|
{
|
|
Navigation.NavigateTo(returnUrl);
|
|
}
|
|
} |