Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions WebDevSecOps/Controllers/VentaController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WebDevSecOps.Models;
using WebDevSecOps.Services;

namespace WebDevSecOps.Controllers;

[Authorize]
public class VentaController : Controller
{
private readonly IVentaService _service;
private readonly IEstadoVentaService _estadoVentaService;
private readonly IClienteService _clienteService;
private readonly IUsuarioService _usuarioService;
private readonly ILogger<VentaController> _logger;

public VentaController(IVentaService service, IEstadoVentaService estadoVentaService, IClienteService clienteService, IUsuarioService usuarioService, ILogger<VentaController> logger)
{
_service = service;
_estadoVentaService = estadoVentaService;
_clienteService = clienteService;
_usuarioService = usuarioService;
_logger = logger;
}

[HttpGet]
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client, NoStore = false)]
public async Task<IActionResult> Index(string? texto = null, DateTime? dteFechaInicio = null, DateTime? dteFechaFin = null, int pageNumber = 1, int pageSize = 10, CancellationToken ct = default)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);

await CargarEstadoVentasAsync(ct);

var result = (!string.IsNullOrEmpty(texto) || dteFechaInicio.HasValue || dteFechaFin.HasValue)
? await _service.SearchVentasAsync(texto, dteFechaInicio, dteFechaFin, pageNumber, pageSize, ct)
: await _service.GetVentasAsync(pageNumber, pageSize, ct);

if (result is null)
{
_logger.LogWarning("Failed to load ventas — service returned null");
TempData["Error"] = "No se pudieron cargar las ventas.";
}

return View(result);
}

[HttpGet]
public async Task<IActionResult> Create(string? returnUrl = null, CancellationToken ct = default)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(VentaCreateViewModel model, CancellationToken ct, string? returnUrl = null)
{
if (!ModelState.IsValid)
{
ViewData["ReturnUrl"] = returnUrl;
return View(model);
}

var result = await _service.CreateVentaAsync(model, ct);

if (result.Success)
{
_logger.LogInformation("Venta created successfully");
TempData["Success"] = "Venta creada exitosamente.";
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
return Redirect(returnUrl);
return RedirectToAction("Index");
}

if (result.FieldErrors is not null)
{
foreach (var kvp in result.FieldErrors)
{
foreach (var msg in kvp.Value)
{
var key = kvp.Key switch
{
"idCliCliente" => nameof(VentaCreateViewModel.IdCliCliente),
"idSegUsuario" => nameof(VentaCreateViewModel.IdSegUsuario),
_ => kvp.Key
};
ModelState.AddModelError(key, msg);
}
}
}

if (result.ErrorMessage is not null)
ModelState.AddModelError(string.Empty, result.ErrorMessage);

ViewData["ReturnUrl"] = returnUrl;
return View(model);
}

[HttpGet]
public async Task<JsonResult> ClientesAutocomplete(string texto, int maxResultados = 10, CancellationToken ct = default)
{
if (!ModelState.IsValid)
return Json(new List<CliClienteAutocompleteDto>());

if (string.IsNullOrWhiteSpace(texto) || texto.Length < 2)
return Json(new List<CliClienteAutocompleteDto>());

var result = await _clienteService.AutocompleteClientesAsync(texto, maxResultados, ct);
return Json(result ?? []);
}

[HttpGet]
public async Task<JsonResult> UsuariosAutocomplete(string texto, int maxResultados = 10, CancellationToken ct = default)
{
if (!ModelState.IsValid)
return Json(new List<SegUsuarioAutocompleteDto>());

if (string.IsNullOrWhiteSpace(texto) || texto.Length < 2)
return Json(new List<SegUsuarioAutocompleteDto>());

var result = await _usuarioService.AutocompleteUsuariosAsync(texto, maxResultados, ct);
return Json(result ?? []);
}

private async Task CargarEstadoVentasAsync(CancellationToken ct = default)
{
var estados = await _estadoVentaService.GetAllAsync(ct: ct);
ViewBag.EstadoVentaDict = estados?.Items?.ToDictionary(t => t.Id, t => t.StrValor) ?? [];
}
}
12 changes: 12 additions & 0 deletions WebDevSecOps/Models/CliClienteAutocompleteDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;

namespace WebDevSecOps.Models;

public class CliClienteAutocompleteDto
{
[JsonPropertyName("id")]
public int Id { get; set; }

[JsonPropertyName("strNombreCliente")]
public string StrNombreCliente { get; set; } = string.Empty;
}
12 changes: 12 additions & 0 deletions WebDevSecOps/Models/SegUsuarioAutocompleteDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;

namespace WebDevSecOps.Models;

public class SegUsuarioAutocompleteDto
{
[JsonPropertyName("id")]
public int Id { get; set; }

[JsonPropertyName("strNombre")]
public string StrNombre { get; set; } = string.Empty;
}
15 changes: 15 additions & 0 deletions WebDevSecOps/Models/VenCatEstado.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace WebDevSecOps.Models;

public class VenCatEstado
{
[JsonPropertyName("id")]
public int Id { get; set; }

[JsonPropertyName("strValor")]
public string StrValor { get; set; } = string.Empty;

[JsonPropertyName("strDescripcion")]
public string? StrDescripcion { get; set; }
}
36 changes: 36 additions & 0 deletions WebDevSecOps/Models/Venta.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Text.Json.Serialization;

namespace WebDevSecOps.Models;

public class Venta
{
[JsonPropertyName("id")]
public int Id { get; set; }

[JsonPropertyName("idCliCliente")]
public int IdCliCliente { get; set; }

[JsonPropertyName("strNombreCliente")]
public string? StrNombreCliente { get; set; }

[JsonPropertyName("idSegUsuario")]
public int IdSegUsuario { get; set; }

[JsonPropertyName("strNombreUsuario")]
public string? StrNombreUsuario { get; set; }

[JsonPropertyName("idVenCatEstado")]
public int IdVenCatEstado { get; set; }

[JsonPropertyName("strEstado")]
public string? StrEstado { get; set; }

[JsonPropertyName("dteFechaHoraCompra")]
public DateTime? DteFechaHoraCompra { get; set; }

[JsonPropertyName("strClaveVenta")]
public string StrClaveVenta { get; set; } = string.Empty;

[JsonPropertyName("rowVersion")]
public byte[]? RowVersion { get; set; }
}
23 changes: 23 additions & 0 deletions WebDevSecOps/Models/VentaCreateViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;

namespace WebDevSecOps.Models;

public class VentaCreateViewModel
{
[Required(ErrorMessage = "El cliente es obligatorio.")]
[JsonRequired]
[JsonPropertyName("idCliCliente")]
[Display(Name = "Cliente")]
public int IdCliCliente { get; set; }

[Required(ErrorMessage = "El usuario es obligatorio.")]
[JsonRequired]
[JsonPropertyName("idSegUsuario")]
[Display(Name = "Usuario")]
public int IdSegUsuario { get; set; }

public string? StrNombreCliente { get; set; }

public string? StrNombreUsuario { get; set; }
}
3 changes: 3 additions & 0 deletions WebDevSecOps/Pages/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
<li class="nav-item">
<a class="nav-link text-dark" href="/Producto/Index">Productos</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="/Venta/Index">Ventas</a>
</li>
}
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
Expand Down
16 changes: 16 additions & 0 deletions WebDevSecOps/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@
})
.AddStandardResilienceHandler();

builder.Services.AddHttpClient<IEstadoVentaService, EstadoVentaService>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["ApiSettings:BaseUrl"]
?? throw new InvalidOperationException("ApiSettings:BaseUrl is not configured."));
client.Timeout = TimeSpan.FromSeconds(30);
})
.AddStandardResilienceHandler();

builder.Services.AddHttpClient<IVentaService, VentaService>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["ApiSettings:BaseUrl"]
?? throw new InvalidOperationException("ApiSettings:BaseUrl is not configured."));
client.Timeout = TimeSpan.FromSeconds(30);
})
.AddStandardResilienceHandler();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
Expand Down
38 changes: 38 additions & 0 deletions WebDevSecOps/Services/ClienteService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,44 @@
}
}

public async Task<List<CliClienteAutocompleteDto>> AutocompleteClientesAsync(string texto, int maxResultados = 10, CancellationToken ct = default)
{
var token = GetToken();

if (token is null)
{
_logger.LogWarning("No access token found");
return [];
}

try
{
var encodedTexto = Uri.EscapeDataString(texto);
using var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/Cliente/autocomplete?texto={encodedTexto}&maxResultados={maxResultados}");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

var response = await _httpClient.SendAsync(request, ct);
response.EnsureSuccessStatusCode();

return await response.Content.ReadFromJsonAsync<List<CliClienteAutocompleteDto>>(cancellationToken: ct) ?? [];
}
catch (OperationCanceledException ex)
{
_logger.LogWarning(ex, "Autocomplete clientes request was cancelled");
throw new OperationCanceledException("The autocomplete clientes request was cancelled.", ex);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Connection error autocomplete clientes");
return [];
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error autocomplete clientes");
return [];
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
Comment on lines +195 to +199
}

public async Task<OperationResult> CreateClienteAsync(ClienteCreateViewModel model, CancellationToken ct = default)
{
var token = GetToken();
Expand Down
Loading
Loading