Add ProductForm Stock
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 11m36s

This commit is contained in:
Leandro Hernan Rojas 2025-07-15 09:26:15 -03:00
parent 369190695b
commit 20dd5d6943
3 changed files with 226 additions and 4 deletions

View File

@ -80,9 +80,12 @@
<button class="btn btn-primary rounded-pill" @onclick="Buscar">
<i class="fas fa-binoculars me-1"></i> Buscar
</button>
<button class="btn btn-success rounded-pill" @onclick="Nuevo">
<button class="btn btn-success rounded-pill" @onclick="NuevoProducto">
<i class="fas fa-plus me-1"></i> Nuevo
</button>
<button class="btn btn-warning rounded-pill" @onclick="ImportarProductos">
<i class="fas fa-file-upload me-1"></i> Importar
</button>
<button class="btn btn-success rounded-pill" @onclick="ExportarExcel">
<i class="fas fa-file-excel me-1"></i> Excel
</button>
@ -246,7 +249,10 @@
}
}
private void Nuevo() => Navigation.NavigateTo("/stock/productimport");
private void NuevoProducto() => Navigation.NavigateTo("/stock/productform");
private void ImportarProductos() => Navigation.NavigateTo("/stock/productimport");
private void Cancelar() => Navigation.NavigateTo("/DashboardPanel");
private int TotalPaginas => PagedResult is null ? 1 : (int)Math.Ceiling(PagedResult.TotalItems / (double)SearchParams.PageSize);

View File

@ -0,0 +1,216 @@
@page "/stock/productform"
@page "/stock/productform/{ProductId:int}"
@using Domain.Entities
@using phronCare.UIBlazor.Services.Lookups
@using phronCare.UIBlazor.Services.Stock
@using phronCare.UIBlazor.Shared.Modals
@using System.ComponentModel.DataAnnotations
@using Blazored.Typeahead
@inject NavigationManager Navigation
@inject LSProductService productService
@inject IStockLookUpService lookUpService
@inject IToastService toastService
@inject IModalService modalService
<div class="card" style="zoom:90%">
<div class="card-header text-center">
<h3 class="card-title">@(ProductId.HasValue ? "Editar producto" : "Nuevo producto")</h3>
</div>
<div class="card-body">
<EditForm Model="product" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="row">
<div class="col-md-6">
<label>Código Fábrica *</label>
<InputText class="form-control" @bind-Value="product.FactoryCode" />
</div>
<div class="col-md-6">
<label>Código Externo</label>
<InputText class="form-control" @bind-Value="product.ExternalCode" />
</div>
</div>
<div class="row mt-2">
<div class="col-md-6">
<label>Nombre *</label>
<InputText class="form-control" @bind-Value="product.Name" />
</div>
<div class="col-md-6">
<label>Descripción *</label>
<InputText class="form-control" @bind-Value="product.Descripcion" />
</div>
</div>
<div class="row mt-2">
<div class="col-md-6">
<label>Tipo de Producto *</label>
<InputSelect class="form-control" @bind-Value="product.ProductType">
<option value="">-- Seleccionar --</option>
<option value="1">Implantable</option>
<option value="2">Instrumental</option>
<option value="3">Inyectable</option>
</InputSelect>
</div>
<div class="col-md-6">
<label>Trazabilidad *</label>
<InputSelect class="form-control" @bind-Value="product.TraceabilityType">
<option value="">-- Seleccionar --</option>
<option value="1">No aplica</option>
<option value="2">Por cantidad</option>
<option value="3">Por lote y vencimiento</option>
</InputSelect>
</div>
</div>
<div class="row mt-2">
<div class="col-md-6">
<label>División *</label>
<BlazoredTypeahead
TItem="ELookUpItem" TValue="ELookUpItem"
SearchMethod="@(filter => lookUpService.GetProductDivisionsAsync(filter).ContinueWith(t => t.Result.AsEnumerable()))"
Value="_selectedDivision" ValueChanged="OnDivisionSelected"
ValueExpression="@(() => _selectedDivision)"
Placeholder="Buscar división..." TextProperty="Nombre"
MaximumSuggestions="5" class="form-control form-control-sm" style="height: 38px;">
<ResultTemplate Context="item">@item.Nombre</ResultTemplate>
<SelectedTemplate Context="item">@item.Nombre</SelectedTemplate>
</BlazoredTypeahead>
</div>
<div class="col-md-6">
<label>Unidad de Medida *</label>
<BlazoredTypeahead id="unit" TItem="ELookUpItem" TValue="ELookUpItem"
SearchMethod="@(filter => lookUpService.GetUnitsOfMeasureAsync(filter).ContinueWith(t => t.Result.AsEnumerable()))"
Value="_selectedUnit" ValueChanged="OnUnitSelected"
ValueExpression="@(() => _selectedUnit)"
Placeholder="Buscar unidad..." TextProperty="Nombre" MaximumSuggestions="5"
class="form-control form-control-sm" style="height: 38px;">
<ResultTemplate Context="item">@item.Nombre</ResultTemplate>
<SelectedTemplate Context="item">@item.Nombre</SelectedTemplate>
</BlazoredTypeahead>
</div>
</div>
<div class="row mt-3">
<div class="col-md-6 d-flex align-items-center">
<div class="form-check form-switch">
<InputCheckbox id="PlusProcess" class="form-check-input" @bind-Value="product.PlusProcess" />
<label class="form-check-label ms-2">Requiere proceso adicional</label>
</div>
</div>
</div>
</EditForm>
</div>
<div class="card-footer d-flex justify-content-end">
<button class="btn btn-primary me-2" @onclick="HandleValidSubmit" disabled="@isSaving">
@(isSaving ? "Guardando..." : "Guardar")
</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
</div>
</div>
@code {
[Parameter] public int? ProductId { get; set; }
private ELSProduct product = new();
private bool isSaving = false;
private ELookUpItem? _selectedDivision;
private ELookUpItem? _selectedUnit;
private ELSProductDivision? selectedDivision;
private ELSUnitOfMeasure? selectedUnit;
private string returnUrl = "/stock/products";
protected override async Task OnInitializedAsync()
{
if (ProductId.HasValue)
{
product = await productService.GetByIdAsync(ProductId.Value) ?? new();
// Precargar División
var divisionList = await lookUpService.GetProductDivisionsAsync("");
_selectedDivision = divisionList.FirstOrDefault(d => d.Id == product.DivisionId);
// Precargar Unidad
var unitList = await lookUpService.GetUnitsOfMeasureAsync("");
_selectedUnit = unitList.FirstOrDefault(u => u.Id == product.UnitId);
}
else
{
product = new();
}
}
private void OnDivisionSelected(ELookUpItem? selected)
{
_selectedDivision = selected;
if (selected != null)
product.DivisionId = selected.Id;
}
private void OnUnitSelected(ELookUpItem? selected)
{
_selectedUnit = selected;
if (selected != null)
product.UnitId = selected.Id;
}
private async Task HandleValidSubmit()
{
if (string.IsNullOrWhiteSpace(product.FactoryCode) ||
string.IsNullOrWhiteSpace(product.Name) ||
string.IsNullOrWhiteSpace(product.Descripcion) ||
product.ProductType <= 0 ||
product.TraceabilityType <= 0 ||
_selectedDivision is null ||
_selectedUnit is null)
{
toastService.ShowWarning("Por favor complete todos los campos obligatorios antes de guardar.");
return;
}
var modal = modalService.Show<ConfirmModal>("Confirmación", new ModalParameters
{
{ "Message", "¿Desea guardar este producto?" }
});
var result = await modal.Result;
if (result.Cancelled) return;
try
{
isSaving = true;
product.DivisionId = _selectedDivision?.Id;
product.UnitId = _selectedUnit?.Id ?? 0;
HttpResponseMessage response;
if (ProductId.HasValue)
response = await productService.UpdateAsync(product);
else
response = await productService.CreateAsync(product);
if (response.IsSuccessStatusCode)
{
toastService.ShowSuccess("Producto guardado correctamente.");
Navigation.NavigateTo("/stock/products");
}
else
{
var error = await response.Content.ReadAsStringAsync();
toastService.ShowError($"Error: {error}");
}
}
catch (Exception ex)
{
toastService.ShowError($"Error: {ex.Message}");
}
finally
{
isSaving = false;
}
}
private void Cancel() => Navigation.NavigateTo(returnUrl);
}

View File

@ -36,12 +36,12 @@ namespace phronCare.UIBlazor.Services.Stock
public async Task<HttpResponseMessage> CreateAsync(ELSProduct product)
{
return await _http.PostAsJsonAsync("/api/LSProduct", product);
return await _http.PostAsJsonAsync("/api/LSProduct/create", product);
}
public async Task<HttpResponseMessage> UpdateAsync(ELSProduct product)
{
return await _http.PutAsJsonAsync("/api/LSProduct", product);
return await _http.PutAsJsonAsync("/api/LSProduct/update", product);
}
public async Task<HttpResponseMessage> DeleteAsync(int id)