add new
This commit is contained in:
208
RS_system/Views/Articulos/Create.cshtml
Normal file
208
RS_system/Views/Articulos/Create.cshtml
Normal file
@@ -0,0 +1,208 @@
|
||||
@model Rs_system.Models.ViewModels.ArticuloViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Nuevo Artículo";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Nuevo Artículo</h4>
|
||||
<p class="text-muted mb-0">Registrar un nuevo activo en el inventario</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver a la lista
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form asp-action="Create" method="post" enctype="multipart/form-data">
|
||||
<div class="row">
|
||||
<!-- Left Column: Basic Info -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card-custom mb-4">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<h5 class="card-title mb-0 ps-2 border-start border-4 border-primary">Información General</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Codigo" class="form-label">Código <span class="text-danger">*</span></label>
|
||||
<input asp-for="Codigo" class="form-control" placeholder="Ej: SILLA-001" autofocus />
|
||||
<span asp-validation-for="Codigo" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label asp-for="Nombre" class="form-label">Nombre del Artículo <span class="text-danger">*</span></label>
|
||||
<input asp-for="Nombre" class="form-control" placeholder="Ej: Guitarra Acustica Yamaha" />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="3" placeholder="Detalles adicionales, características, color..."></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Marca" class="form-label">Marca</label>
|
||||
<input asp-for="Marca" class="form-control" placeholder="Ej: Yamaha, Sony" />
|
||||
<span asp-validation-for="Marca" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Modelo" class="form-label">Modelo</label>
|
||||
<input asp-for="Modelo" class="form-control" placeholder="Ej: C-40" />
|
||||
<span asp-validation-for="Modelo" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<h5 class="card-title mb-0 ps-2 border-start border-4 border-info">Detalles del Activo</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="NumeroSerie" class="form-label">Número de Serie</label>
|
||||
<input asp-for="NumeroSerie" class="form-control" placeholder="S/N..." />
|
||||
<span asp-validation-for="NumeroSerie" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Precio" class="form-label">Valor Estimado (Moneda)</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input asp-for="Precio" class="form-control" type="number" step="0.01" />
|
||||
</div>
|
||||
<span asp-validation-for="Precio" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="FechaAdquisicion" class="form-label">Fecha de Adquisición</label>
|
||||
<input asp-for="FechaAdquisicion" type="date" class="form-control" />
|
||||
<span asp-validation-for="FechaAdquisicion" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Classification & Image -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card-custom mb-4">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<h5 class="card-title mb-0 ps-2 border-start border-4 border-warning">Clasificación</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label asp-for="TipoControl" class="form-label fw-bold">Tipo de Inventario <span class="text-danger">*</span></label>
|
||||
<select asp-for="TipoControl" class="form-select" id="tipoControlSelect">
|
||||
<option value="UNITARIO">Unitario (Laptops, Proyectores)</option>
|
||||
<option value="LOTE">Por Lote (Sillas, Cables)</option>
|
||||
</select>
|
||||
<div class="form-text">Unitario: Control 1 a 1 por Serie. Lote: Control por Cantidad.</div>
|
||||
<span asp-validation-for="TipoControl" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="cantidadContainer" style="display:none;">
|
||||
<label asp-for="CantidadInicial" class="form-label fw-bold text-primary">Cantidad Inicial <span class="text-danger">*</span></label>
|
||||
<input asp-for="CantidadInicial" class="form-control" type="number" min="1" value="1" />
|
||||
<span asp-validation-for="CantidadInicial" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="CategoriaId" class="form-label">Categoría <span class="text-danger">*</span></label>
|
||||
<select asp-for="CategoriaId" class="form-select" asp-items="ViewBag.Categorias">
|
||||
<option value="">-- Seleccionar --</option>
|
||||
</select>
|
||||
<span asp-validation-for="CategoriaId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="UbicacionId" class="form-label">Ubicación Inicial <span class="text-danger">*</span></label>
|
||||
<select asp-for="UbicacionId" class="form-select" asp-items="ViewBag.Ubicaciones">
|
||||
<option value="">-- Seleccionar --</option>
|
||||
</select>
|
||||
<span asp-validation-for="UbicacionId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="EstadoId" class="form-label">Estado Físico <span class="text-danger">*</span></label>
|
||||
<select asp-for="EstadoId" class="form-select" asp-items="ViewBag.Estados">
|
||||
<option value="">-- Seleccionar --</option>
|
||||
</select>
|
||||
<span asp-validation-for="EstadoId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" role="switch" checked />
|
||||
<label asp-for="Activo" class="form-check-label">Artículo Activo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<h5 class="card-title mb-0 ps-2 border-start border-4 border-secondary">Imagen</h5>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<div class="image-preview-container bg-light rounded d-flex align-items-center justify-content-center mb-2" style="height: 200px; border: 2px dashed #ccc;">
|
||||
<img id="imagePreview" src="#" alt="Vista Previa" style="max-height: 100%; max-width: 100%; display: none;" />
|
||||
<span id="placeholderText" class="text-muted">Sin Imagen</span>
|
||||
</div>
|
||||
<input asp-for="ImagenFile" class="form-control" type="file" accept="image/*" onchange="previewImage(this)" />
|
||||
<span asp-validation-for="ImagenFile" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 mt-4">
|
||||
<button type="submit" class="btn btn-primary-custom btn-lg">
|
||||
<i class="bi bi-save me-2"></i> Guardar Artículo
|
||||
</button>
|
||||
<a asp-action="Index" class="btn btn-light border">Cancelar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
<script>
|
||||
function previewImage(input) {
|
||||
if (input.files && input.files[0]) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
$('#imagePreview').attr('src', e.target.result).show();
|
||||
$('#placeholderText').hide();
|
||||
}
|
||||
reader.readAsDataURL(input.files[0]);
|
||||
} else {
|
||||
$('#imagePreview').hide();
|
||||
$('#placeholderText').show();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
function toggleFields() {
|
||||
var tipo = $('#tipoControlSelect').val();
|
||||
if (tipo === 'LOTE') {
|
||||
$('#cantidadContainer').show();
|
||||
$('#NumeroSerie').parent().hide(); // Hide Serial for Lote
|
||||
$('#NumeroSerie').val('');
|
||||
} else {
|
||||
$('#cantidadContainer').hide();
|
||||
$('#NumeroSerie').parent().show(); // Show Serial for Unitario
|
||||
$('#CantidadInicial').val(1);
|
||||
}
|
||||
}
|
||||
|
||||
$('#tipoControlSelect').change(toggleFields);
|
||||
toggleFields(); // Init
|
||||
});
|
||||
</script>
|
||||
}
|
||||
128
RS_system/Views/Articulos/Details.cshtml
Normal file
128
RS_system/Views/Articulos/Details.cshtml
Normal file
@@ -0,0 +1,128 @@
|
||||
@model Rs_system.Models.ViewModels.ArticuloViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Ficha Técnica";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Ficha Técnica</h4>
|
||||
<p class="text-muted mb-0">Detalles del activo: @Model.Codigo</p>
|
||||
</div>
|
||||
<div>
|
||||
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-warning text-white me-2">
|
||||
<i class="bi bi-pencil me-1"></i> Editar
|
||||
</a>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Left Column: Image and Status -->
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card-custom text-center mb-3">
|
||||
<div class="card-body">
|
||||
@if (!string.IsNullOrEmpty(Model.ImagenUrl))
|
||||
{
|
||||
<img src="@Model.ImagenUrl" class="img-fluid rounded mb-3" style="max-height: 300px;" alt="Imagen del Artículo">
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="bg-light rounded d-flex align-items-center justify-content-center mx-auto mb-3" style="height: 250px; width: 100%;">
|
||||
<div class="text-center text-muted">
|
||||
<i class="bi bi-image fs-1 d-block mb-2"></i>
|
||||
<span>Sin Imagen</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h5 class="card-title fw-bold text-primary">@Model.Nombre</h5>
|
||||
<p class="card-text text-muted mb-3">@Model.Codigo</p>
|
||||
|
||||
<div class="d-flex justify-content-center gap-2">
|
||||
<span class="badge bg-@(Model.EstadoColor ?? "secondary") fs-6 px-3 py-2">
|
||||
Estado: @Model.EstadoNombre
|
||||
</span>
|
||||
@if (Model.Activo)
|
||||
{
|
||||
<span class="badge bg-success fs-6 px-3 py-2">Activo</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger fs-6 px-3 py-2">Inactivo</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Details -->
|
||||
<div class="col-md-8">
|
||||
<div class="card-custom">
|
||||
<div class="card-header bg-transparent py-3 border-bottom">
|
||||
<h5 class="card-title mb-0">Información Detallada</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-4">
|
||||
<div class="col-sm-4 text-muted">Descripción</div>
|
||||
<div class="col-sm-8 fw-semibold">
|
||||
@(!string.IsNullOrEmpty(Model.Descripcion) ? Model.Descripcion : "-")
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-sm-4 text-muted">Ubicación Actual</div>
|
||||
<div class="col-sm-8">
|
||||
<span class="d-flex align-items-center">
|
||||
<i class="bi bi-geo-alt-fill text-danger me-2"></i>
|
||||
<span class="fw-bold">@Model.UbicacionNombre</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-sm-4 text-muted">Categoría</div>
|
||||
<div class="col-sm-8 fw-semibold">@Model.CategoriaNombre</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4 text-muted" />
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-6">
|
||||
<small class="text-muted d-block mb-1">Marca</small>
|
||||
<span class="fs-5">@(!string.IsNullOrEmpty(Model.Marca) ? Model.Marca : "-")</span>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<small class="text-muted d-block mb-1">Modelo</small>
|
||||
<span class="fs-5">@(!string.IsNullOrEmpty(Model.Modelo) ? Model.Modelo : "-")</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-6">
|
||||
<small class="text-muted d-block mb-1">Número de Serie</small>
|
||||
<span class="font-monospace bg-light px-2 py-1 rounded">
|
||||
@(!string.IsNullOrEmpty(Model.NumeroSerie) ? Model.NumeroSerie : "N/A")
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<small class="text-muted d-block mb-1">Precio Estimado</small>
|
||||
<span class="text-success fw-bold">
|
||||
@Model.Precio.ToString("C")
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<small class="text-muted d-block mb-1">Fecha Adquisición</small>
|
||||
<span>
|
||||
@(Model.FechaAdquisicion.HasValue ? Model.FechaAdquisicion.Value.ToString("dd/MM/yyyy") : "-")
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
215
RS_system/Views/Articulos/Edit.cshtml
Normal file
215
RS_system/Views/Articulos/Edit.cshtml
Normal file
@@ -0,0 +1,215 @@
|
||||
@model Rs_system.Models.ViewModels.ArticuloViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Editar Artículo";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Editar Artículo</h4>
|
||||
<p class="text-muted mb-0">Modificar información del activo</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form asp-action="Edit" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<div class="row">
|
||||
<!-- Left Column: Basic Info -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card-custom mb-4">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<h5 class="card-title mb-0 ps-2 border-start border-4 border-primary">Información General</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<label asp-for="Codigo" class="form-label">Código <span class="text-danger">*</span></label>
|
||||
<input asp-for="Codigo" class="form-control" />
|
||||
<span asp-validation-for="Codigo" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label asp-for="Nombre" class="form-label">Nombre del Artículo <span class="text-danger">*</span></label>
|
||||
<input asp-for="Nombre" class="form-control" />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="3"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Marca" class="form-label">Marca</label>
|
||||
<input asp-for="Marca" class="form-control" />
|
||||
<span asp-validation-for="Marca" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Modelo" class="form-label">Modelo</label>
|
||||
<input asp-for="Modelo" class="form-control" />
|
||||
<span asp-validation-for="Modelo" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<h5 class="card-title mb-0 ps-2 border-start border-4 border-info">Detalles del Activo</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="NumeroSerie" class="form-label">Número de Serie</label>
|
||||
<input asp-for="NumeroSerie" class="form-control" />
|
||||
<span asp-validation-for="NumeroSerie" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Precio" class="form-label">Valor Estimado (Moneda)</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input asp-for="Precio" class="form-control" type="number" step="0.01" />
|
||||
</div>
|
||||
<span asp-validation-for="Precio" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="FechaAdquisicion" class="form-label">Fecha de Adquisición</label>
|
||||
<input asp-for="FechaAdquisicion" type="date" class="form-control" />
|
||||
<span asp-validation-for="FechaAdquisicion" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Classification & Image -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card-custom mb-4">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<h5 class="card-title mb-0 ps-2 border-start border-4 border-warning">Clasificación</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Tipo de Inventario</label>
|
||||
<input type="text" class="form-control" value="@Model.TipoControl" readonly disabled />
|
||||
<input type="hidden" asp-for="TipoControl" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
@if (Model.TipoControl == "LOTE")
|
||||
{
|
||||
<label class="form-label fw-bold text-primary">Cantidad Global</label>
|
||||
<input type="number" class="form-control" value="@Model.CantidadInicial" readonly disabled />
|
||||
<div class="form-text">Gestionar cantidad vía Movimientos.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<label class="form-label fw-bold">Cantidad</label>
|
||||
<input type="text" class="form-control" value="1 (Unitario)" readonly disabled />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="CategoriaId" class="form-label">Categoría <span class="text-danger">*</span></label>
|
||||
<select asp-for="CategoriaId" class="form-select" asp-items="ViewBag.Categorias">
|
||||
<option value="">-- Seleccionar --</option>
|
||||
</select>
|
||||
<span asp-validation-for="CategoriaId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="UbicacionId" class="form-label">Ubicación Actual <span class="text-danger">*</span></label>
|
||||
@if (Model.TipoControl == "LOTE")
|
||||
{
|
||||
<select asp-for="UbicacionId" class="form-select" asp-items="ViewBag.Ubicaciones" disabled>
|
||||
<option value="">-- Seleccionar --</option>
|
||||
</select>
|
||||
<input type="hidden" asp-for="UbicacionId" />
|
||||
<div class="form-text">La ubicación se gestiona por Existencias en Lotes.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<select asp-for="UbicacionId" class="form-select" asp-items="ViewBag.Ubicaciones">
|
||||
<option value="">-- Seleccionar --</option>
|
||||
</select>
|
||||
<span asp-validation-for="UbicacionId" class="text-danger"></span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="EstadoId" class="form-label">Estado Físico <span class="text-danger">*</span></label>
|
||||
<select asp-for="EstadoId" class="form-select" asp-items="ViewBag.Estados">
|
||||
<option value="">-- Seleccionar --</option>
|
||||
</select>
|
||||
<span asp-validation-for="EstadoId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" role="switch" />
|
||||
<label asp-for="Activo" class="form-check-label">Artículo Activo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<h5 class="card-title mb-0 ps-2 border-start border-4 border-secondary">Imagen</h5>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<div class="image-preview-container bg-light rounded d-flex align-items-center justify-content-center mb-2" style="height: 200px; border: 2px dashed #ccc;">
|
||||
@if (!string.IsNullOrEmpty(Model.ImagenUrl))
|
||||
{
|
||||
<img id="imagePreview" src="@Model.ImagenUrl" alt="Vista Previa" style="max-height: 100%; max-width: 100%;" />
|
||||
<span id="placeholderText" class="text-muted" style="display: none;">Sin Imagen</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<img id="imagePreview" src="#" alt="Vista Previa" style="max-height: 100%; max-width: 100%; display: none;" />
|
||||
<span id="placeholderText" class="text-muted">Sin Imagen</span>
|
||||
}
|
||||
</div>
|
||||
<input asp-for="ImagenFile" class="form-control" type="file" accept="image/*" onchange="previewImage(this)" />
|
||||
<span asp-validation-for="ImagenFile" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 mt-4">
|
||||
<button type="submit" class="btn btn-primary-custom btn-lg">
|
||||
<i class="bi bi-save me-2"></i> Guardar Cambios
|
||||
</button>
|
||||
<a asp-action="Index" class="btn btn-light border">Cancelar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
<script>
|
||||
function previewImage(input) {
|
||||
if (input.files && input.files[0]) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
$('#imagePreview').attr('src', e.target.result).show();
|
||||
$('#placeholderText').hide();
|
||||
}
|
||||
reader.readAsDataURL(input.files[0]);
|
||||
} else {
|
||||
// If clearing, we might want to keep the old image or show placebo
|
||||
// For simplicity here, if user cancels file dialog, it keeps previous
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
141
RS_system/Views/Articulos/Index.cshtml
Normal file
141
RS_system/Views/Articulos/Index.cshtml
Normal file
@@ -0,0 +1,141 @@
|
||||
@model IEnumerable<Rs_system.Models.ViewModels.ArticuloViewModel>
|
||||
@{
|
||||
ViewData["Title"] = "Inventario de Artículos";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Inventario de Artículos</h4>
|
||||
<p class="text-muted mb-0">Gestión de bienes y activos fijos</p>
|
||||
</div>
|
||||
<a asp-action="Create" class="btn btn-primary-custom">
|
||||
<i class="bi bi-plus-lg me-1"></i> Nuevo Artículo
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Filters Card -->
|
||||
<div class="card-custom mb-4">
|
||||
<div class="card-body py-3">
|
||||
<form asp-action="Index" method="get" class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light border-end-0"><i class="bi bi-search"></i></span>
|
||||
<input type="text" name="search" class="form-control border-start-0" placeholder="Buscar por nombre, código..." value="@ViewBag.CurrentSearch">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="categoriaId" class="form-select" asp-items="ViewBag.Categorias">
|
||||
<option value="">Todas las Categorías</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="ubicacionId" class="form-select" asp-items="ViewBag.Ubicaciones">
|
||||
<option value="">Todas las Ubicaciones</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="estadoId" class="form-select" asp-items="ViewBag.Estados">
|
||||
<option value="">Todos los Estados</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1 d-grid">
|
||||
<button type="submit" class="btn btn-secondary" title="Filtrar">
|
||||
<i class="bi bi-funnel"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 60px;">Img</th>
|
||||
<th>Código</th>
|
||||
<th>Nombre / Marca</th>
|
||||
<th>Ubicación</th>
|
||||
<th>Categoría</th>
|
||||
<th class="text-center">Estado</th>
|
||||
<th class="text-center">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted py-5">
|
||||
<i class="bi bi-box-seam fs-1 d-block mb-2"></i>
|
||||
No se encontraron artículos
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
@if (!string.IsNullOrEmpty(item.ImagenUrl))
|
||||
{
|
||||
<img src="@item.ImagenUrl" alt="Img" class="rounded" style="width: 40px; height: 40px; object-fit: cover;" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="rounded bg-light d-flex align-items-center justify-content-center text-muted" style="width: 40px; height: 40px;">
|
||||
<i class="bi bi-image"></i>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-light text-dark border">@item.Codigo</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-bold">@item.Nombre</div>
|
||||
@if (!string.IsNullOrEmpty(item.Marca))
|
||||
{
|
||||
<small class="text-muted">@item.Marca @item.Modelo</small>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<i class="bi bi-geo-alt-fill text-muted me-1" style="font-size: 0.8rem;"></i>
|
||||
@item.UbicacionNombre
|
||||
</td>
|
||||
<td>@item.CategoriaNombre</td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-@(item.EstadoColor ?? "secondary")">
|
||||
@item.EstadoNombre
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="btn-group">
|
||||
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-sm btn-outline-primary" title="Ver Ficha Técnica">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-outline-secondary" title="Editar">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<text>toastr.success('@TempData["SuccessMessage"]');</text>
|
||||
}
|
||||
@if (TempData["ErrorMessage"] != null)
|
||||
{
|
||||
<text>toastr.error('@TempData["ErrorMessage"]');</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
53
RS_system/Views/Categorias/Create.cshtml
Normal file
53
RS_system/Views/Categorias/Create.cshtml
Normal file
@@ -0,0 +1,53 @@
|
||||
@model Rs_system.Models.Categoria
|
||||
@{
|
||||
ViewData["Title"] = "Nueva Categoría";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Nueva Categoría</h4>
|
||||
<p class="text-muted mb-0">Registrar una nueva categoría de inventario</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom" style="max-width: 800px; margin: 0 auto;">
|
||||
<div class="card-body">
|
||||
<form asp-action="Create" method="post">
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Nombre" class="form-label">Nombre <span class="text-danger">*</span></label>
|
||||
<input asp-for="Nombre" class="form-control" placeholder="Ej: Electrónica, Muebles, Papelería" autofocus />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="3" placeholder="Breve descripción de la categoría (opcional)"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" role="switch" checked />
|
||||
<label asp-for="Activo" class="form-check-label">Categoría Activa</label>
|
||||
</div>
|
||||
<div class="form-text">Si está inactiva, no aparecerá en las selecciones de nuevos artículos.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Guardar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
}
|
||||
54
RS_system/Views/Categorias/Edit.cshtml
Normal file
54
RS_system/Views/Categorias/Edit.cshtml
Normal file
@@ -0,0 +1,54 @@
|
||||
@model Rs_system.Models.Categoria
|
||||
@{
|
||||
ViewData["Title"] = "Editar Categoría";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Editar Categoría</h4>
|
||||
<p class="text-muted mb-0">Modificar información de la categoría</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom" style="max-width: 800px; margin: 0 auto;">
|
||||
<div class="card-body">
|
||||
<form asp-action="Edit" method="post">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Nombre" class="form-label">Nombre <span class="text-danger">*</span></label>
|
||||
<input asp-for="Nombre" class="form-control" placeholder="Ej: Electrónica, Muebles, Papelería" />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="3" placeholder="Breve descripción de la categoría (opcional)"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" role="switch" />
|
||||
<label asp-for="Activo" class="form-check-label">Categoría Activa</label>
|
||||
</div>
|
||||
<div class="form-text">Si está inactiva, no aparecerá en las selecciones de nuevos artículos.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Guardar Cambios
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
}
|
||||
117
RS_system/Views/Categorias/Index.cshtml
Normal file
117
RS_system/Views/Categorias/Index.cshtml
Normal file
@@ -0,0 +1,117 @@
|
||||
@model IEnumerable<Rs_system.Models.Categoria>
|
||||
@{
|
||||
ViewData["Title"] = "Categorías";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Categorías de Inventario</h4>
|
||||
<p class="text-muted mb-0">Gestión de categorías para clasificación de artículos</p>
|
||||
</div>
|
||||
<a asp-action="Create" class="btn btn-primary-custom">
|
||||
<i class="bi bi-plus-lg me-1"></i> Nueva Categoría
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th class="text-center">Estado</th>
|
||||
<th class="text-center">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-4">
|
||||
<i class="bi bi-tags fs-1 d-block mb-2"></i>
|
||||
No hay categorías registradas
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<strong>@item.Nombre</strong>
|
||||
</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(item.Descripcion))
|
||||
{
|
||||
<span>@item.Descripcion</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">-</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@if (item.Activo)
|
||||
{
|
||||
<span class="badge bg-success">Activo</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Inactivo</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-outline-secondary" title="Editar">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="confirmDelete(@item.Id)" title="Eliminar">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Form -->
|
||||
<form id="deleteForm" asp-action="Delete" method="post" style="display: none;">
|
||||
<input type="hidden" name="id" id="deleteId" />
|
||||
</form>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function confirmDelete(id) {
|
||||
Swal.fire({
|
||||
title: '¿Eliminar categoría?',
|
||||
text: 'Esta acción moverá la categoría a la papelera.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#d33',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: 'Sí, eliminar',
|
||||
cancelButtonText: 'Cancelar'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('deleteId').value = id;
|
||||
document.getElementById('deleteForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show success/error messages
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.success('@TempData["SuccessMessage"]');
|
||||
</text>
|
||||
}
|
||||
@if (TempData["ErrorMessage"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.error('@TempData["ErrorMessage"]');
|
||||
</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
453
RS_system/Views/Colaboracion/Create.cshtml
Normal file
453
RS_system/Views/Colaboracion/Create.cshtml
Normal file
@@ -0,0 +1,453 @@
|
||||
@model Rs_system.Models.ViewModels.RegistrarColaboracionViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Nueva Colaboración";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Nueva Colaboración</h4>
|
||||
<p class="text-muted mb-0">Registrar colaboración económica mensual</p>
|
||||
</div>
|
||||
<div>
|
||||
<a asp-controller="TipoColaboracion" asp-action="Index" class="btn btn-outline-primary me-2">
|
||||
<i class="bi bi-gear me-1"></i> Gestionar Tipos
|
||||
</a>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<form asp-action="Create" method="post" id="colaboracionForm">
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<!-- Selección de Miembro con Búsqueda -->
|
||||
<div class="mb-4">
|
||||
<h6 class="text-primary border-bottom pb-2">Información del Miembro</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label for="buscarMiembro" class="form-label">Buscar Miembro</label>
|
||||
<input type="text"
|
||||
id="buscarMiembro"
|
||||
class="form-control"
|
||||
placeholder="Escriba el nombre del miembro..."
|
||||
autocomplete="off" />
|
||||
<div id="resultadosBusqueda" class="list-group mt-2" style="display: none; position: absolute; z-index: 1000; max-height: 300px; overflow-y: auto; width: 90%;"></div>
|
||||
|
||||
<input type="hidden" asp-for="MiembroId" id="miembroIdHidden" />
|
||||
|
||||
<!-- Mostrar miembro seleccionado -->
|
||||
<div id="miembroSeleccionado" class="mt-3" style="display: none;">
|
||||
<div class="alert alert-success d-flex justify-content-between align-items-center mb-2">
|
||||
<div>
|
||||
<i class="bi bi-person-check me-2"></i>
|
||||
<strong id="nombreMiembroSeleccionado"></strong>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="limpiarMiembro()">
|
||||
<i class="bi bi-x"></i> Cambiar
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Historial de Últimos Pagos -->
|
||||
<div id="infoUltimosPagos" style="display: none;">
|
||||
<div class="card border-info bg-light">
|
||||
<div class="card-header bg-transparent border-info py-1 px-3">
|
||||
<small class="text-info fw-bold"><i class="bi bi-clock-history me-1"></i> Últimos meses pagados</small>
|
||||
</div>
|
||||
<div class="card-body py-2 px-3">
|
||||
<div id="listaUltimosPagos" class="d-flex flex-wrap gap-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span asp-validation-for="MiembroId" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Período de Colaboración -->
|
||||
<div class="mb-4">
|
||||
<h6 class="text-primary border-bottom pb-2">Período a Cubrir</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label asp-for="MesInicial" class="form-label"></label>
|
||||
<select asp-for="MesInicial" class="form-select" id="mesInicial" onchange="calcularSugerido()">
|
||||
<option value="1">Enero</option>
|
||||
<option value="2">Febrero</option>
|
||||
<option value="3">Marzo</option>
|
||||
<option value="4">Abril</option>
|
||||
<option value="5">Mayo</option>
|
||||
<option value="6">Junio</option>
|
||||
<option value="7">Julio</option>
|
||||
<option value="8">Agosto</option>
|
||||
<option value="9">Septiembre</option>
|
||||
<option value="10">Octubre</option>
|
||||
<option value="11">Noviembre</option>
|
||||
<option value="12">Diciembre</option>
|
||||
</select>
|
||||
<span asp-validation-for="MesInicial" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label asp-for="AnioInicial" class="form-label"></label>
|
||||
<select asp-for="AnioInicial" class="form-select" id="anioInicial" onchange="calcularSugerido()">
|
||||
@for (int i = DateTime.Now.Year; i >= DateTime.Now.Year - 5; i--)
|
||||
{
|
||||
<option value="@i">@i</option>
|
||||
}
|
||||
</select>
|
||||
<span asp-validation-for="AnioInicial" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label asp-for="MesFinal" class="form-label"></label>
|
||||
<select asp-for="MesFinal" class="form-select" id="mesFinal" onchange="calcularSugerido()">
|
||||
<option value="1">Enero</option>
|
||||
<option value="2">Febrero</option>
|
||||
<option value="3">Marzo</option>
|
||||
<option value="4">Abril</option>
|
||||
<option value="5">Mayo</option>
|
||||
<option value="6">Junio</option>
|
||||
<option value="7">Julio</option>
|
||||
<option value="8">Agosto</option>
|
||||
<option value="9">Septiembre</option>
|
||||
<option value="10">Octubre</option>
|
||||
<option value="11">Noviembre</option>
|
||||
<option value="12">Diciembre</option>
|
||||
</select>
|
||||
<span asp-validation-for="MesFinal" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label asp-for="AnioFinal" class="form-label"></label>
|
||||
<select asp-for="AnioFinal" class="form-select" id="anioFinal" onchange="calcularSugerido()">
|
||||
@for (int i = DateTime.Now.Year + 1; i >= DateTime.Now.Year - 5; i--)
|
||||
{
|
||||
<option value="@i" selected="@(i == DateTime.Now.Year)">@i</option>
|
||||
}
|
||||
</select>
|
||||
<span asp-validation-for="AnioFinal" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tipos de Colaboración -->
|
||||
<div class="mb-4">
|
||||
<h6 class="text-primary border-bottom pb-2">Tipos de Colaboración</h6>
|
||||
<div class="row">
|
||||
@if (Model.TiposDisponibles != null && Model.TiposDisponibles.Any())
|
||||
{
|
||||
@foreach (var tipo in Model.TiposDisponibles)
|
||||
{
|
||||
var esTransporteOLimpieza = tipo.Nombre.Equals("Transporte", StringComparison.OrdinalIgnoreCase) ||
|
||||
tipo.Nombre.Equals("Limpieza", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
<div class="col-md-6 mb-2">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input tipo-checkbox"
|
||||
type="checkbox"
|
||||
name="TiposSeleccionados"
|
||||
value="@tipo.Id"
|
||||
id="tipo_@tipo.Id"
|
||||
data-monto="@tipo.MontoSugerido"
|
||||
@(esTransporteOLimpieza ? "checked" : "")
|
||||
onchange="calcularSugerido()">
|
||||
<label class="form-check-label" for="tipo_@tipo.Id">
|
||||
<strong>@tipo.Nombre</strong>
|
||||
<span class="badge bg-success ms-2">$@tipo.MontoSugerido.ToString("N2")/mes</span>
|
||||
@if (!string.IsNullOrEmpty(tipo.Descripcion))
|
||||
{
|
||||
<br><small class="text-muted">@tipo.Descripcion</small>
|
||||
}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="col-12">
|
||||
<div class="alert alert-warning">
|
||||
No hay tipos de colaboración activos. <a asp-controller="TipoColaboracion" asp-action="Index">Gestionar tipos</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<span asp-validation-for="TiposSeleccionados" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<!-- Tipo Prioritario -->
|
||||
<div class="mb-4">
|
||||
<h6 class="text-primary border-bottom pb-2">Priorización (Opcional)</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label asp-for="TipoPrioritario" class="form-label"></label>
|
||||
<select asp-for="TipoPrioritario" class="form-select" id="tipoPrioritario">
|
||||
<option value="">Sin prioridad - Distribuir equitativamente</option>
|
||||
@if (Model.TiposDisponibles != null)
|
||||
{
|
||||
@foreach (var tipo in Model.TiposDisponibles)
|
||||
{
|
||||
<option value="@tipo.Id">@tipo.Nombre (se pagará primero $@tipo.MontoSugerido por mes)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<small class="form-text text-muted">
|
||||
Si selecciona un tipo prioritario, el monto se asignará primero a ese tipo usando su monto sugerido,
|
||||
y el resto se distribuirá equitativamente entre los demás tipos.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Monto Total -->
|
||||
<div class="mb-4">
|
||||
<h6 class="text-primary border-bottom pb-2">Monto de la Colaboración</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="MontoTotal" class="form-label"></label>
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-text">$</span>
|
||||
<input asp-for="MontoTotal"
|
||||
type="number"
|
||||
step="0.01"
|
||||
class="form-control"
|
||||
id="montoTotal"
|
||||
placeholder="0.00"
|
||||
required />
|
||||
</div>
|
||||
<span asp-validation-for="MontoTotal" class="text-danger"></span>
|
||||
<small class="form-text text-muted">Monto total que entrega el miembro</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Resumen</label>
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Meses a cubrir:</span>
|
||||
<strong id="totalMeses">0</strong>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Tipos seleccionados:</span>
|
||||
<strong id="totalTipos">0</strong>
|
||||
</div>
|
||||
<hr class="my-2">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-info">Monto sugerido:</span>
|
||||
<strong class="text-info">$<span id="montoSugerido">0.00</span></strong>
|
||||
</div>
|
||||
<div id="alertaDiferencia" class="alert alert-warning py-1 px-2 mb-0 mt-2" style="display: none; font-size: 0.85rem;">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>
|
||||
<span id="mensajeDiferencia"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Observaciones -->
|
||||
<div class="mb-4">
|
||||
<label asp-for="Observaciones" class="form-label"></label>
|
||||
<textarea asp-for="Observaciones" class="form-control" rows="3" placeholder="Notas opcionales sobre esta colaboración"></textarea>
|
||||
<span asp-validation-for="Observaciones" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Registrar Colaboración
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
let timeoutBusqueda = null;
|
||||
|
||||
// Búsqueda de miembros
|
||||
document.getElementById('buscarMiembro').addEventListener('input', function(e) {
|
||||
const termino = e.target.value;
|
||||
const resultadosDiv = document.getElementById('resultadosBusqueda');
|
||||
|
||||
clearTimeout(timeoutBusqueda);
|
||||
|
||||
if (termino.length < 2) {
|
||||
resultadosDiv.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
timeoutBusqueda = setTimeout(async () => {
|
||||
try {
|
||||
const response = await fetch('@Url.Action("BuscarMiembros", "Colaboracion")?termino=' + encodeURIComponent(termino));
|
||||
const miembros = await response.json();
|
||||
|
||||
if (miembros.length === 0) {
|
||||
resultadosDiv.innerHTML = '<div class="list-group-item text-muted">No se encontraron resultados</div>';
|
||||
resultadosDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
miembros.forEach(miembro => {
|
||||
html += `
|
||||
<button type="button" class="list-group-item list-group-item-action" onclick="seleccionarMiembro(${miembro.id}, '${miembro.text}')">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<i class="bi bi-person me-2"></i>
|
||||
<strong>${miembro.text}</strong>
|
||||
</div>
|
||||
${miembro.telefono ? '<small class="text-muted">' + miembro.telefono + '</small>' : ''}
|
||||
</div>
|
||||
</button>
|
||||
`;
|
||||
});
|
||||
|
||||
resultadosDiv.innerHTML = html;
|
||||
resultadosDiv.style.display = 'block';
|
||||
} catch (error) {
|
||||
console.error('Error al buscar miembros:', error);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Cerrar resultados cuando se hace clic fuera
|
||||
document.addEventListener('click', function(e) {
|
||||
const buscarInput = document.getElementById('buscarMiembro');
|
||||
const resultadosDiv = document.getElementById('resultadosBusqueda');
|
||||
|
||||
if (!buscarInput.contains(e.target) && !resultadosDiv.contains(e.target)) {
|
||||
resultadosDiv.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
function seleccionarMiembro(id, nombre) {
|
||||
document.getElementById('miembroIdHidden').value = id;
|
||||
document.getElementById('nombreMiembroSeleccionado').textContent = nombre;
|
||||
document.getElementById('miembroSeleccionado').style.display = 'block';
|
||||
document.getElementById('buscarMiembro').value = '';
|
||||
document.getElementById('buscarMiembro').style.display = 'none';
|
||||
document.getElementById('resultadosBusqueda').style.display = 'none';
|
||||
|
||||
// Cargar historial de pagos
|
||||
cargarHistorialPagos(id);
|
||||
}
|
||||
|
||||
function limpiarMiembro() {
|
||||
document.getElementById('miembroIdHidden').value = '';
|
||||
document.getElementById('miembroSeleccionado').style.display = 'none';
|
||||
document.getElementById('buscarMiembro').style.display = 'block';
|
||||
document.getElementById('buscarMiembro').focus();
|
||||
|
||||
// Ocultar historial
|
||||
document.getElementById('infoUltimosPagos').style.display = 'none';
|
||||
document.getElementById('listaUltimosPagos').innerHTML = '';
|
||||
}
|
||||
|
||||
async function cargarHistorialPagos(miembroId) {
|
||||
const contenedor = document.getElementById('infoUltimosPagos');
|
||||
const lista = document.getElementById('listaUltimosPagos');
|
||||
|
||||
lista.innerHTML = '<div class="spinner-border spinner-border-sm text-info" role="status"></div> Cargando historial...';
|
||||
contenedor.style.display = 'block';
|
||||
|
||||
try {
|
||||
const response = await fetch('@Url.Action("ObtenerUltimosPagos", "Colaboracion")?miembroId=' + miembroId);
|
||||
const pagos = await response.json();
|
||||
|
||||
if (pagos && pagos.length > 0) {
|
||||
let html = '';
|
||||
pagos.forEach(p => {
|
||||
const colorClass = p.ultimoMes > 0 ? 'bg-white text-info border border-info' : 'bg-secondary text-white';
|
||||
html += `
|
||||
<span class="badge ${colorClass} fw-normal p-2">
|
||||
<strong>${p.nombreTipo}:</strong> ${p.ultimoPeriodoTexto}
|
||||
</span>
|
||||
`;
|
||||
});
|
||||
lista.innerHTML = html;
|
||||
} else {
|
||||
lista.innerHTML = '<span class="text-muted small">No hay historial de pagos registrado.</span>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error al cargar historial:', error);
|
||||
lista.innerHTML = '<span class="text-danger small"><i class="bi bi-exclamation-circle"></i> Error al cargar historial</span>';
|
||||
}
|
||||
}
|
||||
|
||||
function calcularSugerido() {
|
||||
try {
|
||||
// Obtener valores
|
||||
const mesInicial = parseInt(document.getElementById('mesInicial').value);
|
||||
const anioInicial = parseInt(document.getElementById('anioInicial').value);
|
||||
const mesFinal = parseInt(document.getElementById('mesFinal').value);
|
||||
const anioFinal = parseInt(document.getElementById('anioFinal').value);
|
||||
|
||||
// Calcular total de meses
|
||||
const fechaInicial = new Date(anioInicial, mesInicial - 1, 1);
|
||||
const fechaFinal = new Date(anioFinal, mesFinal - 1, 1);
|
||||
|
||||
let totalMeses = 0;
|
||||
if (fechaFinal >= fechaInicial) {
|
||||
totalMeses = ((anioFinal - anioInicial) * 12) + (mesFinal - mesInicial) + 1;
|
||||
}
|
||||
|
||||
// Obtener tipos seleccionados y sus montos
|
||||
const tiposCheckboxes = document.querySelectorAll('.tipo-checkbox:checked');
|
||||
const totalTipos = tiposCheckboxes.length;
|
||||
|
||||
// Calcular monto sugerido total
|
||||
let montoSugeridoTotal = 0;
|
||||
tiposCheckboxes.forEach(checkbox => {
|
||||
const montoPorMes = parseFloat(checkbox.getAttribute('data-monto')) || 0;
|
||||
montoSugeridoTotal += montoPorMes * totalMeses;
|
||||
});
|
||||
|
||||
// Actualizar UI
|
||||
document.getElementById('totalMeses').textContent = totalMeses;
|
||||
document.getElementById('totalTipos').textContent = totalTipos;
|
||||
document.getElementById('montoSugerido').textContent = montoSugeridoTotal.toFixed(2);
|
||||
|
||||
// Comparar con monto ingresado
|
||||
const montoIngresado = parseFloat(document.getElementById('montoTotal').value) || 0;
|
||||
const alertaDiv = document.getElementById('alertaDiferencia');
|
||||
const mensajeSpan = document.getElementById('mensajeDiferencia');
|
||||
|
||||
if (montoIngresado > 0) {
|
||||
if (Math.abs(montoIngresado - montoSugeridoTotal) > 0.01) {
|
||||
const diferencia = montoIngresado - montoSugeridoTotal;
|
||||
if (diferencia > 0) {
|
||||
mensajeSpan.textContent = `Sobra: $${diferencia.toFixed(2)}`;
|
||||
alertaDiv.className = 'alert alert-info py-1 px-2 mb-0 mt-2';
|
||||
} else {
|
||||
mensajeSpan.textContent = `Falta: $${Math.abs(diferencia).toFixed(2)}`;
|
||||
alertaDiv.className = 'alert alert-warning py-1 px-2 mb-0 mt-2';
|
||||
}
|
||||
alertaDiv.style.display = 'block';
|
||||
} else {
|
||||
alertaDiv.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
alertaDiv.style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error al calcular sugerido:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Calcular cuando cambia el monto total
|
||||
document.getElementById('montoTotal')?.addEventListener('input', calcularSugerido);
|
||||
|
||||
// Calcular al cargar la página
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
calcularSugerido();
|
||||
});
|
||||
|
||||
// Show error messages
|
||||
@if (TempData["Error"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.error('@TempData["Error"]');
|
||||
</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
130
RS_system/Views/Colaboracion/Details.cshtml
Normal file
130
RS_system/Views/Colaboracion/Details.cshtml
Normal file
@@ -0,0 +1,130 @@
|
||||
@model Rs_system.Models.Colaboracion
|
||||
@{
|
||||
ViewData["Title"] = "Detalle de Colaboración";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Detalle de Colaboración #@Model.Id</h4>
|
||||
<p class="text-muted mb-0">Información completa de la colaboración</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Información Principal -->
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card-custom">
|
||||
<h6 class="text-primary border-bottom pb-2 mb-3">Información de Registro</h6>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">Miembro</label>
|
||||
<div><strong>@Model.Miembro.Persona.Nombres @Model.Miembro.Persona.Apellidos</strong></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">Fecha de Registro</label>
|
||||
<div>@Model.FechaRegistro.ToString("dd/MM/yyyy HH:mm")</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">Registrado por</label>
|
||||
<div>@(Model.RegistradoPor ?? "Sistema")</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">Monto Total</label>
|
||||
<div><h4 class="text-success mb-0">$@Model.MontoTotal.ToString("N2")</h4></div>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.Observaciones))
|
||||
{
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">Observaciones</label>
|
||||
<div class="alert alert-info mb-0">@Model.Observaciones</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desglose Detallado -->
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card-custom">
|
||||
<h6 class="text-primary border-bottom pb-2 mb-3">Desglose por Tipo</h6>
|
||||
|
||||
@{
|
||||
var porTipo = Model.Detalles.GroupBy(d => d.TipoColaboracion.Nombre);
|
||||
}
|
||||
|
||||
@foreach (var grupo in porTipo)
|
||||
{
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h6 class="mb-0">@grupo.Key</h6>
|
||||
<span class="badge bg-primary">@grupo.Count() meses</span>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
Total: <strong class="text-success">$@grupo.Sum(d => d.Monto).ToString("N2")</strong>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabla de Detalles Mensuales -->
|
||||
<div class="card-custom">
|
||||
<h6 class="text-primary border-bottom pb-2 mb-3">Meses Cubiertos</h6>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mes</th>
|
||||
<th>Año</th>
|
||||
<th>Tipo</th>
|
||||
<th class="text-end">Monto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var detalle in Model.Detalles.OrderBy(d => d.Anio).ThenBy(d => d.Mes))
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
@{
|
||||
var fecha = new DateTime(detalle.Anio, detalle.Mes, 1);
|
||||
}
|
||||
@fecha.ToString("MMMM", new System.Globalization.CultureInfo("es-ES"))
|
||||
</td>
|
||||
<td>@detalle.Anio</td>
|
||||
<td>
|
||||
<span class="badge bg-primary">@detalle.TipoColaboracion.Nombre</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<strong>$@detalle.Monto.ToString("N2")</strong>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-active">
|
||||
<td colspan="3" class="text-end"><strong>TOTAL:</strong></td>
|
||||
<td class="text-end">
|
||||
<h5 class="mb-0 text-success">$@Model.Detalles.Sum(d => d.Monto).ToString("N2")</h5>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a asp-action="Index" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver al Listado
|
||||
</a>
|
||||
<a asp-action="EstadoCuenta" asp-route-id="@Model.MiembroId" class="btn btn-primary-custom">
|
||||
<i class="bi bi-file-text me-1"></i> Ver Estado de Cuenta del Miembro
|
||||
</a>
|
||||
</div>
|
||||
133
RS_system/Views/Colaboracion/EstadoCuenta.cshtml
Normal file
133
RS_system/Views/Colaboracion/EstadoCuenta.cshtml
Normal file
@@ -0,0 +1,133 @@
|
||||
@model Rs_system.Models.ViewModels.EstadoCuentaViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Estado de Cuenta";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Estado de Cuenta</h4>
|
||||
<p class="text-muted mb-0">@Model.NombreMiembro</p>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="window.print()" class="btn btn-outline-secondary me-2">
|
||||
<i class="bi bi-printer me-1"></i> Imprimir
|
||||
</button>
|
||||
<a asp-action="Index" class="btn btn-primary-custom">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Encabezado del Estado de Cuenta -->
|
||||
<div class="card-custom mb-4" style="border: 2px solid #198754;">
|
||||
<div class="text-center py-4">
|
||||
<h3 class="mb-3">ESTADO DE CUENTA DE COLABORACIONES</h3>
|
||||
<h5 class="text-primary mb-2">@Model.NombreMiembro</h5>
|
||||
<p class="text-muted mb-0">Fecha de consulta: @Model.FechaConsulta.ToString("dd/MM/yyyy HH:mm")</p>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="text-center py-3">
|
||||
<h6 class="text-muted mb-2">Total Aportado</h6>
|
||||
<h2 class="text-success mb-0">$@Model.TotalAportado.ToString("N2")</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Historial por Tipo de Colaboración -->
|
||||
@if (Model.HistorialPorTipos.Any())
|
||||
{
|
||||
@foreach (var historial in Model.HistorialPorTipos)
|
||||
{
|
||||
<div class="card-custom mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h5 class="mb-0">
|
||||
<span class="badge bg-primary">@historial.TipoNombre</span>
|
||||
</h5>
|
||||
<h6 class="mb-0 text-success">
|
||||
Total: $@historial.TotalTipo.ToString("N2")
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mes / Año</th>
|
||||
<th>Fecha de Pago</th>
|
||||
<th class="text-end">Monto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var registro in historial.Registros.OrderByDescending(r => r.Anio).ThenByDescending(r => r.Mes))
|
||||
{
|
||||
<tr>
|
||||
<td><strong>@registro.MesTexto</strong></td>
|
||||
<td>@registro.FechaRegistro.ToString("dd/MM/yyyy")</td>
|
||||
<td class="text-end">
|
||||
<strong>$@registro.Monto.ToString("N2")</strong>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-active">
|
||||
<td colspan="2" class="text-end"><strong>Subtotal @historial.TipoNombre:</strong></td>
|
||||
<td class="text-end">
|
||||
<strong class="text-success">$@historial.TotalTipo.ToString("N2")</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Total General -->
|
||||
<div class="card-custom" style="border: 2px solid #198754;">
|
||||
<div class="d-flex justify-content-between align-items-center py-3">
|
||||
<h4 class="mb-0">TOTAL GENERAL</h4>
|
||||
<h3 class="mb-0 text-success">$@Model.TotalAportado.ToString("N2")</h3>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card-custom">
|
||||
<div class="alert alert-info mb-0">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
Este miembro aún no tiene colaboraciones registradas.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Pie de página para impresión -->
|
||||
<div class="mt-5 text-center text-muted" style="page-break-before: auto;">
|
||||
<hr>
|
||||
<p class="mb-1"><small>Este documento es un comprobante de colaboraciones realizadas</small></p>
|
||||
<p class="mb-0"><small>Generado el @DateTime.Now.ToString("dd/MM/yyyy HH:mm")</small></p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@media print {
|
||||
.btn, .no-print {
|
||||
display: none !important;
|
||||
}
|
||||
.card-custom {
|
||||
border: 1px solid #dee2e6;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
@if (TempData["Error"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.error('@TempData["Error"]');
|
||||
</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
147
RS_system/Views/Colaboracion/Index.cshtml
Normal file
147
RS_system/Views/Colaboracion/Index.cshtml
Normal file
@@ -0,0 +1,147 @@
|
||||
@model IEnumerable<Rs_system.Models.Colaboracion>
|
||||
@{
|
||||
ViewData["Title"] = "Colaboraciones";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Colaboraciones Económicas</h4>
|
||||
<p class="text-muted mb-0">Registro de colaboraciones mensuales de los miembros</p>
|
||||
</div>
|
||||
<div>
|
||||
<a asp-action="Reportes" class="btn btn-outline-primary me-2">
|
||||
<i class="bi bi-file-earmark-bar-graph me-1"></i> Reportes
|
||||
</a>
|
||||
<a asp-action="Create" class="btn btn-primary-custom">
|
||||
<i class="bi bi-plus-lg me-1"></i> Nueva Colaboración
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card-custom text-center">
|
||||
<h6 class="text-muted mb-2">Total Recaudado Hoy</h6>
|
||||
<h3 class="text-primary mb-0">
|
||||
$@Model.Where(c => c.FechaRegistro.Date == DateTime.Today).Sum(c => c.MontoTotal).ToString("N2")
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card-custom text-center">
|
||||
<h6 class="text-muted mb-2">Colaboraciones Hoy</h6>
|
||||
<h3 class="text-success mb-0">
|
||||
@Model.Count(c => c.FechaRegistro.Date == DateTime.Today)
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card-custom text-center">
|
||||
<h6 class="text-muted mb-2">Total Registros</h6>
|
||||
<h3 class="text-info mb-0">@Model.Count()</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Collaborations Table -->
|
||||
<div class="card-custom">
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fecha</th>
|
||||
<th>Miembro</th>
|
||||
<th>Tipos</th>
|
||||
<th>Período</th>
|
||||
<th>Monto</th>
|
||||
<th>Registrado por</th>
|
||||
<th class="text-center">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted py-4">
|
||||
<i class="bi bi-cash-coin fs-1 d-block mb-2"></i>
|
||||
No hay colaboraciones registradas
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var colaboracion in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<strong>@colaboracion.FechaRegistro.ToString("dd/MM/yyyy")</strong><br>
|
||||
<small class="text-muted">@colaboracion.FechaRegistro.ToString("HH:mm")</small>
|
||||
</td>
|
||||
<td>
|
||||
<strong>@colaboracion.Miembro.Persona.Nombres @colaboracion.Miembro.Persona.Apellidos</strong>
|
||||
</td>
|
||||
<td>
|
||||
@{
|
||||
var tipos = colaboracion.Detalles.Select(d => d.TipoColaboracion.Nombre).Distinct();
|
||||
}
|
||||
@foreach (var tipo in tipos)
|
||||
{
|
||||
<span class="badge bg-primary me-1">@tipo</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@{
|
||||
var ordenados = colaboracion.Detalles.OrderBy(d => d.Anio).ThenBy(d => d.Mes).ToList();
|
||||
var primero = ordenados.First();
|
||||
var ultimo = ordenados.Last();
|
||||
|
||||
if (primero.Anio == ultimo.Anio && primero.Mes == ultimo.Mes)
|
||||
{
|
||||
var fecha = new DateTime(primero.Anio, primero.Mes, 1);
|
||||
<text>@fecha.ToString("MMMM yyyy", new System.Globalization.CultureInfo("es-ES"))</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
var fechaInicio = new DateTime(primero.Anio, primero.Mes, 1);
|
||||
var fechaFin = new DateTime(ultimo.Anio, ultimo.Mes, 1);
|
||||
<text>@fechaInicio.ToString("MMM yyyy", new System.Globalization.CultureInfo("es-ES")) - @fechaFin.ToString("MMM yyyy", new System.Globalization.CultureInfo("es-ES"))</text>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<strong class="text-success">$@colaboracion.MontoTotal.ToString("N2")</strong>
|
||||
</td>
|
||||
<td>
|
||||
<small>@(colaboracion.RegistradoPor ?? "Sistema")</small>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a asp-action="Details" asp-route-id="@colaboracion.Id" class="btn btn-sm btn-outline-primary" title="Ver detalles">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
<a asp-action="EstadoCuenta" asp-route-id="@colaboracion.MiembroId" class="btn btn-sm btn-outline-info" title="Estado de cuenta">
|
||||
<i class="bi bi-file-text"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
// Show success/error messages
|
||||
@if (TempData["Success"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.success('@TempData["Success"]');
|
||||
</text>
|
||||
}
|
||||
@if (TempData["Error"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.error('@TempData["Error"]');
|
||||
</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
150
RS_system/Views/Colaboracion/Reporte.cshtml
Normal file
150
RS_system/Views/Colaboracion/Reporte.cshtml
Normal file
@@ -0,0 +1,150 @@
|
||||
@model Rs_system.Models.ViewModels.ReporteColaboracionesViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Reporte de Colaboraciones";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Reporte de Colaboraciones</h4>
|
||||
<p class="text-muted mb-0">
|
||||
Del @Model.FechaInicio.ToString("dd/MM/yyyy") al @Model.FechaFin.ToString("dd/MM/yyyy")
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="window.print()" class="btn btn-outline-secondary me-2">
|
||||
<i class="bi bi-printer me-1"></i> Imprimir
|
||||
</button>
|
||||
<a asp-action="Reportes" class="btn btn-primary-custom">
|
||||
<i class="bi bi-arrow-left me-1"></i> Nuevo Reporte
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resumen General -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card-custom text-center">
|
||||
<h6 class="text-muted mb-2">Total Recaudado</h6>
|
||||
<h2 class="text-success mb-0">$@Model.TotalRecaudado.ToString("N2")</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card-custom text-center">
|
||||
<h6 class="text-muted mb-2">Total Movimientos</h6>
|
||||
<h2 class="text-primary mb-0">@Model.Movimientos.Count</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card-custom text-center">
|
||||
<h6 class="text-muted mb-2">Tipos de Colaboración</h6>
|
||||
<h2 class="text-info mb-0">@Model.DesglosePorTipos.Count</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desglose por Tipo -->
|
||||
<div class="card-custom mb-4">
|
||||
<h6 class="text-primary border-bottom pb-2 mb-3">Desglose por Tipo de Colaboración</h6>
|
||||
|
||||
@if (Model.DesglosePorTipos.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tipo de Colaboración</th>
|
||||
<th class="text-center">Cantidad de Meses</th>
|
||||
<th class="text-end">Total Recaudado</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var tipo in Model.DesglosePorTipos.OrderByDescending(t => t.TotalRecaudado))
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<span class="badge bg-primary">@tipo.TipoNombre</span>
|
||||
</td>
|
||||
<td class="text-center">@tipo.CantidadMeses</td>
|
||||
<td class="text-end">
|
||||
<strong class="text-success">$@tipo.TotalRecaudado.ToString("N2")</strong>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-active">
|
||||
<td colspan="2" class="text-end"><strong>TOTAL:</strong></td>
|
||||
<td class="text-end">
|
||||
<h5 class="mb-0 text-success">$@Model.TotalRecaudado.ToString("N2")</h5>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
No hay datos para el período seleccionado.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Detalle de Movimientos -->
|
||||
<div class="card-custom">
|
||||
<h6 class="text-primary border-bottom pb-2 mb-3">Detalle de Movimientos</h6>
|
||||
|
||||
@if (Model.Movimientos.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fecha</th>
|
||||
<th>Miembro</th>
|
||||
<th>Tipos</th>
|
||||
<th>Período Cubierto</th>
|
||||
<th class="text-end">Monto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var movimiento in Model.Movimientos.OrderByDescending(m => m.Fecha))
|
||||
{
|
||||
<tr>
|
||||
<td>@movimiento.Fecha.ToString("dd/MM/yyyy HH:mm")</td>
|
||||
<td><strong>@movimiento.NombreMiembro</strong></td>
|
||||
<td>
|
||||
@foreach (var tipo in movimiento.TiposColaboracion.Split(", "))
|
||||
{
|
||||
<span class="badge bg-primary me-1">@tipo</span>
|
||||
}
|
||||
</td>
|
||||
<td>@movimiento.PeriodoCubierto</td>
|
||||
<td class="text-end">
|
||||
<strong>$@movimiento.Monto.ToString("N2")</strong>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
No hay movimientos registrados en este período.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@media print {
|
||||
.btn, .no-print {
|
||||
display: none !important;
|
||||
}
|
||||
.card-custom {
|
||||
border: 1px solid #dee2e6;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
118
RS_system/Views/Colaboracion/Reportes.cshtml
Normal file
118
RS_system/Views/Colaboracion/Reportes.cshtml
Normal file
@@ -0,0 +1,118 @@
|
||||
@{
|
||||
ViewData["Title"] = "Reportes de Colaboraciones";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Reportes de Colaboraciones</h4>
|
||||
<p class="text-muted mb-0">Generar reportes por rango de fechas</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<form asp-action="GenerarReporte" method="post">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<label for="fechaInicio" class="form-label">Fecha Inicio</label>
|
||||
<input type="date"
|
||||
class="form-control"
|
||||
id="fechaInicio"
|
||||
name="fechaInicio"
|
||||
value="@ViewBag.FechaInicio?.ToString("yyyy-MM-dd")"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="fechaFin" class="form-label">Fecha Fin</label>
|
||||
<input type="date"
|
||||
class="form-control"
|
||||
id="fechaFin"
|
||||
name="fechaFin"
|
||||
value="@ViewBag.FechaFin?.ToString("yyyy-MM-dd")"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label"> </label>
|
||||
<button type="submit" class="btn btn-primary-custom w-100">
|
||||
<i class="bi bi-search me-1"></i> Generar Reporte
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Acceso rápido -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h6 class="text-muted mb-3">Accesos Rápidos</h6>
|
||||
</div>
|
||||
<div class="col-md-3 mb-2">
|
||||
<button class="btn btn-outline-primary w-100" onclick="reporteHoy()">
|
||||
<i class="bi bi-calendar-day me-1"></i> Hoy
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3 mb-2">
|
||||
<button class="btn btn-outline-primary w-100" onclick="reporteSemana()">
|
||||
<i class="bi bi-calendar-week me-1"></i> Esta Semana
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3 mb-2">
|
||||
<button class="btn btn-outline-primary w-100" onclick="reporteMes()">
|
||||
<i class="bi bi-calendar-month me-1"></i> Este Mes
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3 mb-2">
|
||||
<button class="btn btn-outline-primary w-100" onclick="reporteAnio()">
|
||||
<i class="bi bi-calendar3 me-1"></i> Este Año
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function setFechas(inicio, fin) {
|
||||
document.getElementById('fechaInicio').value = inicio;
|
||||
document.getElementById('fechaFin').value = fin;
|
||||
}
|
||||
|
||||
function reporteHoy() {
|
||||
const hoy = new Date().toISOString().split('T')[0];
|
||||
setFechas(hoy, hoy);
|
||||
}
|
||||
|
||||
function reporteSemana() {
|
||||
const hoy = new Date();
|
||||
const inicioSemana = new Date(hoy);
|
||||
inicioSemana.setDate(hoy.getDate() - hoy.getDay());
|
||||
const finSemana = new Date(inicioSemana);
|
||||
finSemana.setDate(inicioSemana.getDate() + 6);
|
||||
|
||||
setFechas(inicioSemana.toISOString().split('T')[0], finSemana.toISOString().split('T')[0]);
|
||||
}
|
||||
|
||||
function reporteMes() {
|
||||
const hoy = new Date();
|
||||
const inicio = new Date(hoy.getFullYear(), hoy.getMonth(), 1);
|
||||
const fin = new Date(hoy.getFullYear(), hoy.getMonth() + 1, 0);
|
||||
|
||||
setFechas(inicio.toISOString().split('T')[0], fin.toISOString().split('T')[0]);
|
||||
}
|
||||
|
||||
function reporteAnio() {
|
||||
const hoy = new Date();
|
||||
const inicio = new Date(hoy.getFullYear(), 0, 1);
|
||||
const fin = new Date(hoy.getFullYear(), 11, 31);
|
||||
|
||||
setFechas(inicio.toISOString().split('T')[0], fin.toISOString().split('T')[0]);
|
||||
}
|
||||
|
||||
@if (TempData["Error"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.error('@TempData["Error"]');
|
||||
</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
64
RS_system/Views/Contabilidad/Create.cshtml
Normal file
64
RS_system/Views/Contabilidad/Create.cshtml
Normal file
@@ -0,0 +1,64 @@
|
||||
@model Rs_system.Models.ContabilidadRegistro
|
||||
@{
|
||||
ViewData["Title"] = "Nuevo Registro Contable";
|
||||
}
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card card-custom">
|
||||
<div class="card-header bg-white border-bottom-0 pt-4 pb-0">
|
||||
<h5 class="card-title mb-0">Nuevo Movimiento</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form asp-action="Create">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="GrupoTrabajoId" class="form-label">Grupo de Trabajo</label>
|
||||
<select asp-for="GrupoTrabajoId" class="form-select" asp-items="ViewBag.Grupos">
|
||||
<option value="">Seleccione...</option>
|
||||
</select>
|
||||
<span asp-validation-for="GrupoTrabajoId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label asp-for="Tipo" class="form-label">Tipo de Movimiento</label>
|
||||
<select asp-for="Tipo" class="form-select" asp-items="Html.GetEnumSelectList<Rs_system.Models.TipoMovimientoContable>()"></select>
|
||||
<span asp-validation-for="Tipo" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label asp-for="Fecha" class="form-label">Fecha</label>
|
||||
<input asp-for="Fecha" type="date" class="form-control" value="@DateTime.Now.ToString("yyyy-MM-dd")" />
|
||||
<span asp-validation-for="Fecha" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Monto" class="form-label">Monto</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input asp-for="Monto" class="form-control" type="number" step="0.01" />
|
||||
</div>
|
||||
<span asp-validation-for="Monto" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="3"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
}
|
||||
140
RS_system/Views/Contabilidad/Index.cshtml
Normal file
140
RS_system/Views/Contabilidad/Index.cshtml
Normal file
@@ -0,0 +1,140 @@
|
||||
@using Rs_system.Models
|
||||
@model List<ReporteMensualContable>
|
||||
@{
|
||||
ViewData["Title"] = "Contabilidad Mensual";
|
||||
var grupoId = ViewBag.GrupoId as long?;
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Contabilidad Mensual</h4>
|
||||
<p class="text-muted mb-0">Gestión de reportes financieros por grupo</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-custom mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" class="row g-3 align-items-end">
|
||||
<div class="col-md-8">
|
||||
<label class="form-label">Grupo de Trabajo</label>
|
||||
<select name="grupoId" class="form-select" asp-items="ViewBag.Grupos" onchange="this.form.submit()">
|
||||
<option value="">Seleccione un grupo...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button type="button" class="btn btn-primary-custom w-100" data-bs-toggle="modal" data-bs-target="#abrirMesModal" @(grupoId.HasValue ? "" : "disabled")>
|
||||
<i class="bi bi-plus-lg me-1"></i> Abrir Nuevo Mes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (grupoId.HasValue)
|
||||
{
|
||||
@if (Model != null && Model.Any())
|
||||
{
|
||||
<div class="card card-custom">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mes / Año</th>
|
||||
<th>Saldo Inicial</th>
|
||||
<th>Estado</th>
|
||||
<th>Fecha Creación</th>
|
||||
<th class="text-end">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td class="fw-bold">@item.NombreMes @item.Anio</td>
|
||||
<td>@item.SaldoInicial.ToString("C")</td>
|
||||
<td>
|
||||
@if (item.Cerrado)
|
||||
{
|
||||
<span class="badge bg-secondary">Cerrado</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-success">Abierto</span>
|
||||
}
|
||||
</td>
|
||||
<td>@item.FechaCreacion.ToLocalTime().ToString("dd/MM/yyyy HH:mm")</td>
|
||||
<td class="text-end">
|
||||
<a asp-action="RegistroMensual" asp-route-id="@item.Id" class="btn btn-sm btn-outline-success">
|
||||
<i class="bi bi-table me-1"></i> @(item.Cerrado ? "Ver Detalles" : "Gestionar Registros")
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info py-4 text-center">
|
||||
<i class="bi bi-info-circle fs-3 d-block mb-3"></i>
|
||||
<h5>No hay reportes mensuales para este grupo</h5>
|
||||
<p class="mb-0">Haga clic en "Abrir Nuevo Mes" para comenzar el registro contable de este mes.</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-light border text-center py-5">
|
||||
<i class="bi bi-people fs-2 d-block mb-3 text-muted"></i>
|
||||
<h5 class="text-muted">Seleccione un grupo para ver sus reportes mensuales</h5>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Modal Abrir Mes -->
|
||||
<div class="modal fade" id="abrirMesModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form asp-action="AbrirMes" method="post">
|
||||
<input type="hidden" name="grupoId" value="@grupoId" />
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Abrir Nuevo Mes Contable</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Mes</label>
|
||||
<select name="mes" class="form-select">
|
||||
@for (int i = 1; i <= 12; i++)
|
||||
{
|
||||
bool isCurrent = i == DateTime.Now.Month;
|
||||
<!option value="@i" @(isCurrent ? "selected" : "")>@(new DateTime(2000, i, 1).ToString("MMMM", new System.Globalization.CultureInfo("es-ES")))</!option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Año</label>
|
||||
<select name="anio" class="form-select">
|
||||
@for (int i = DateTime.Now.Year - 1; i <= DateTime.Now.Year + 1; i++)
|
||||
{
|
||||
bool isCurrent = i == DateTime.Now.Year;
|
||||
<!option value="@i" @(isCurrent ? "selected" : "")>@i</!option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info mt-3 mb-0 py-2 small">
|
||||
<i class="bi bi-lightbulb me-2"></i>El saldo inicial se calculará automáticamente a partir del cierre del mes anterior si existe.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-primary-custom">Abrir Mes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
261
RS_system/Views/Contabilidad/RegistroMensual.cshtml
Normal file
261
RS_system/Views/Contabilidad/RegistroMensual.cshtml
Normal file
@@ -0,0 +1,261 @@
|
||||
@model Rs_system.Models.ReporteMensualContable
|
||||
@{
|
||||
ViewData["Title"] = $"Reporte {Model.NombreMes} {Model.Anio}";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">@Model.GrupoTrabajo.Nombre - @Model.NombreMes @Model.Anio</h4>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@if (Model.Cerrado)
|
||||
{
|
||||
<span class="badge bg-secondary"><i class="bi bi-lock-fill me-1"></i> REPORTE CERRADO</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-success"><i class="bi bi-unlock-fill me-1"></i> REPORTE ABIERTO</span>
|
||||
}
|
||||
<span class="text-muted small">Creado el @Model.FechaCreacion.ToLocalTime().ToString("dd/MM/yyyy")</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a asp-action="Index" asp-route-grupoId="@Model.GrupoTrabajoId" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver a Planilla
|
||||
</a>
|
||||
@if (!Model.Cerrado)
|
||||
{
|
||||
<form asp-action="CerrarMes" asp-route-id="@Model.Id" method="post" onsubmit="return confirm('¿Está seguro de cerrar este mes? Ya no se podrán realizar más cambios.')">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-lock me-1"></i> Cerrar Mes
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card card-custom p-3 text-center bg-light">
|
||||
<span class="text-muted small">Saldo Inicial</span>
|
||||
<h4 class="mb-0">@Model.SaldoInicial.ToString("N2")</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card card-custom p-3 text-center bg-light">
|
||||
<span class="text-muted small">Ingresos (+)</span>
|
||||
<h4 class="mb-0 text-success" id="totalIngresos">0.00</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card card-custom p-3 text-center bg-light">
|
||||
<span class="text-muted small">Egresos (-)</span>
|
||||
<h4 class="mb-0 text-danger" id="totalEgresos">0.00</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card card-custom p-3 text-center border-primary shadow-sm">
|
||||
<span class="text-muted small">Saldo Final (=)</span>
|
||||
<h4 class="mb-0 text-primary" id="saldoFinal">0.00</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-custom">
|
||||
<div class="card-header d-flex justify-content-between align-items-center bg-white border-bottom py-3">
|
||||
<h6 class="mb-0"><i class="bi bi-grid-3x3 me-2"></i>Registros de Movimientos</h6>
|
||||
@if (!Model.Cerrado)
|
||||
{
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="addRow()">
|
||||
<i class="bi bi-plus-lg me-1"></i> Agregar Fila
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary-custom" onclick="saveAll()">
|
||||
<i class="bi bi-cloud-arrow-up me-1"></i> Guardar Cambios
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0" id="excelTable">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th style="width: 50px;" class="text-center">#</th>
|
||||
<th style="width: 180px;">Fecha</th>
|
||||
<th style="width: 200px;">Tipo</th>
|
||||
<th>Descripción</th>
|
||||
<th style="width: 200px;" class="text-end">Monto</th>
|
||||
@if(!Model.Cerrado) { <th style="width: 60px;"></th> }
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableBody">
|
||||
@if (Model.Registros != null && Model.Registros.Any())
|
||||
{
|
||||
int count = 1;
|
||||
foreach (var item in Model.Registros.OrderBy(r => r.Fecha))
|
||||
{
|
||||
<tr data-id="@item.Id">
|
||||
<td class="text-center text-muted">@count</td>
|
||||
<td><input type="date" class="form-control form-control-sm row-fecha" value="@item.Fecha.ToString("yyyy-MM-dd")" @(Model.Cerrado ? "disabled" : "") /></td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm row-tipo" @(Model.Cerrado ? "disabled" : "") onchange="updateTotals()">
|
||||
<!option value="1" @(item.Tipo == TipoMovimientoContable.Ingreso ? "selected" : "")>Ingreso (+)</!option>
|
||||
<!option value="0" @(item.Tipo == TipoMovimientoContable.Egreso ? "selected" : "")>Egreso (-)</!option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="form-control form-control-sm row-descripcion" value="@item.Descripcion" placeholder="Motivo del movimiento..." @(Model.Cerrado ? "disabled" : "") /></td>
|
||||
<td><input type="number" step="0.01" class="form-control form-control-sm text-end row-monto" value="@item.Monto.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)" @(Model.Cerrado ? "disabled" : "") onchange="updateTotals()" /></td>
|
||||
@if(!Model.Cerrado) {
|
||||
<td class="text-center">
|
||||
<button class="btn btn-sm text-danger" onclick="deleteRow(this)"><i class="bi bi-trash"></i></button>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
count++;
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
@if(!Model.Cerrado) {
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="6" class="p-0">
|
||||
<button type="button" class="btn btn-link w-100 text-decoration-none py-3 text-muted" onclick="addRow()">
|
||||
<i class="bi bi-plus-circle me-1"></i> Haga clic aquí para agregar una nueva fila
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
const reporteId = @Model.Id;
|
||||
const saldoInicial = @Model.SaldoInicial.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
|
||||
const isCerrado = @Model.Cerrado.ToString().ToLower();
|
||||
|
||||
function addRow() {
|
||||
if (isCerrado) return;
|
||||
const tbody = document.getElementById('tableBody');
|
||||
const rowCount = tbody.children.length + 1;
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
tr.setAttribute('data-id', '0');
|
||||
tr.innerHTML = `
|
||||
<td class="text-center text-muted">${rowCount}</td>
|
||||
<td><input type="date" class="form-control form-control-sm row-fecha" value="${today}" /></td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm row-tipo" onchange="updateTotals()">
|
||||
<option value="1">Ingreso (+)</option>
|
||||
<option value="0">Egreso (-)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="form-control form-control-sm row-descripcion" value="" placeholder="Motivo del movimiento..." /></td>
|
||||
<td><input type="number" step="0.01" class="form-control form-control-sm text-end row-monto" value="0.00" onchange="updateTotals()" /></td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-sm text-danger" onclick="deleteRow(this)"><i class="bi bi-trash"></i></button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
updateTotals();
|
||||
}
|
||||
|
||||
function deleteRow(btn) {
|
||||
if (isCerrado) return;
|
||||
const row = btn.closest('tr');
|
||||
row.remove();
|
||||
|
||||
// Renumber rows
|
||||
const rows = document.querySelectorAll('#tableBody tr');
|
||||
rows.forEach((r, idx) => {
|
||||
r.cells[0].innerText = idx + 1;
|
||||
});
|
||||
updateTotals();
|
||||
}
|
||||
|
||||
function updateTotals() {
|
||||
let ingresos = 0;
|
||||
let egresos = 0;
|
||||
|
||||
const rows = document.querySelectorAll('#tableBody tr');
|
||||
rows.forEach(row => {
|
||||
const tipo = parseInt(row.querySelector('.row-tipo').value);
|
||||
const monto = parseFloat(row.querySelector('.row-monto').value) || 0;
|
||||
|
||||
if (tipo === 1) ingresos += monto;
|
||||
else egresos += monto;
|
||||
});
|
||||
|
||||
document.getElementById('totalIngresos').innerText = ingresos.toFixed(2);
|
||||
document.getElementById('totalEgresos').innerText = egresos.toFixed(2);
|
||||
document.getElementById('saldoFinal').innerText = (saldoInicial + ingresos - egresos).toFixed(2);
|
||||
}
|
||||
|
||||
async function saveAll() {
|
||||
if (isCerrado) return;
|
||||
|
||||
const registros = [];
|
||||
const rows = document.querySelectorAll('#tableBody tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
registros.push({
|
||||
id: parseInt(row.getAttribute('data-id')),
|
||||
fecha: row.querySelector('.row-fecha').value,
|
||||
tipo: parseInt(row.querySelector('.row-tipo').value),
|
||||
monto: parseFloat(row.querySelector('.row-monto').value) || 0,
|
||||
descripcion: row.querySelector('.row-descripcion').value
|
||||
});
|
||||
});
|
||||
|
||||
const btn = event.target.closest('button');
|
||||
const originalContent = btn.innerHTML;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Guardando...';
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('/Contabilidad/GuardarBulk', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ reporteId: reporteId, registros: registros })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: '¡Guardado!',
|
||||
text: 'Los registros se han guardado exitosamente.',
|
||||
timer: 1500,
|
||||
showConfirmButton: false
|
||||
});
|
||||
// Refresh view results would be better, but for now we trust the saldo returned
|
||||
document.getElementById('saldoFinal').innerText = result.saldo.toFixed(2);
|
||||
window.location.reload(); // Reload to refresh IDs and state
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: result.message || 'Error al guardar los registros.'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error de Red',
|
||||
text: 'No se pudo conectar con el servidor.'
|
||||
});
|
||||
} finally {
|
||||
btn.innerHTML = originalContent;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize totals on load
|
||||
document.addEventListener('DOMContentLoaded', updateTotals);
|
||||
</script>
|
||||
}
|
||||
164
RS_system/Views/ContabilidadGeneral/Consolidado.cshtml
Normal file
164
RS_system/Views/ContabilidadGeneral/Consolidado.cshtml
Normal file
@@ -0,0 +1,164 @@
|
||||
@model Rs_system.Models.ReporteMensualGeneral
|
||||
@{
|
||||
ViewData["Title"] = $"Consolidado - {Model.NombreMes} {Model.Anio}";
|
||||
var ingresosPorCat = ViewBag.ConsolidadoIngresos as Dictionary<string, decimal> ?? new Dictionary<string, decimal>();
|
||||
var egresosPorCat = ViewBag.ConsolidadoEgresos as Dictionary<string, decimal> ?? new Dictionary<string, decimal>();
|
||||
|
||||
var totalIngresosMes = ingresosPorCat.Values.Sum();
|
||||
var totalEgresosMes = egresosPorCat.Values.Sum();
|
||||
var saldoFinal = Model.SaldoInicial + totalIngresosMes - totalEgresosMes;
|
||||
}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 text-gray-800">Consolidado: @Model.NombreMes @Model.Anio</h1>
|
||||
<div>
|
||||
<a asp-action="RegistroMensual" asp-route-id="@Model.Id" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-edit"></i> Ver Detalle Registros
|
||||
</a>
|
||||
<a asp-action="Index" class="btn btn-secondary btn-sm ml-2">
|
||||
<i class="fas fa-arrow-left"></i> Volver
|
||||
</a>
|
||||
<button onclick="window.print()" class="btn btn-info btn-sm ml-2">
|
||||
<i class="fas fa-print"></i> Imprimir
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resumen Cards -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-primary shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Saldo Inicial</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">@Model.SaldoInicial.ToString("C")</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-success shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Total Ingresos</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">@totalIngresosMes.ToString("C")</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-danger shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-danger text-uppercase mb-1">Total Egresos</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">@totalEgresosMes.ToString("C")</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-info shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Saldo Final</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">@saldoFinal.ToString("C")</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Ingresos Chart/Table -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-success">Desglose de Ingresos</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Categoría</th>
|
||||
<th class="text-right">Monto</th>
|
||||
<th class="text-right">%</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in ingresosPorCat.OrderByDescending(x => x.Value))
|
||||
{
|
||||
var porcentaje = totalIngresosMes > 0 ? (item.Value / totalIngresosMes) * 100 : 0;
|
||||
<tr>
|
||||
<td>@item.Key</td>
|
||||
<td class="text-right">@item.Value.ToString("C")</td>
|
||||
<td class="text-right">@porcentaje.ToString("F1")%</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="font-weight-bold">
|
||||
<td>Total</td>
|
||||
<td class="text-right">@totalIngresosMes.ToString("C")</td>
|
||||
<td class="text-right">100%</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Egresos Chart/Table -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-danger">Desglose de Egresos</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Categoría</th>
|
||||
<th class="text-right">Monto</th>
|
||||
<th class="text-right">%</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in egresosPorCat.OrderByDescending(x => x.Value))
|
||||
{
|
||||
var porcentaje = totalEgresosMes > 0 ? (item.Value / totalEgresosMes) * 100 : 0;
|
||||
<tr>
|
||||
<td>@item.Key</td>
|
||||
<td class="text-right">@item.Value.ToString("C")</td>
|
||||
<td class="text-right">@porcentaje.ToString("F1")%</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="font-weight-bold">
|
||||
<td>Total</td>
|
||||
<td class="text-right">@totalEgresosMes.ToString("C")</td>
|
||||
<td class="text-right">100%</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
222
RS_system/Views/ContabilidadGeneral/GestionCategorias.cshtml
Normal file
222
RS_system/Views/ContabilidadGeneral/GestionCategorias.cshtml
Normal file
@@ -0,0 +1,222 @@
|
||||
@{
|
||||
ViewData["Title"] = "Gestión de Categorías";
|
||||
var categoriasIngreso = ViewBag.CategoriasIngreso as List<Rs_system.Models.CategoriaIngreso>;
|
||||
var categoriasEgreso = ViewBag.CategoriasEgreso as List<Rs_system.Models.CategoriaEgreso>;
|
||||
}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 text-gray-800">Gestión de Categorías</h1>
|
||||
<a asp-action="Index" class="btn btn-secondary btn-sm">
|
||||
<i class="fas fa-arrow-left"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (TempData["Success"] != null)
|
||||
{
|
||||
<div class="alert alert-success">@TempData["Success"]</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<!-- Categorías de Ingreso -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-success">Categorías de Ingreso</h6>
|
||||
<button class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#modalCrearIngreso">
|
||||
<i class="fas fa-plus"></i> Nueva
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th style="width: 100px;">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in categoriasIngreso)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.Nombre</td>
|
||||
<td>@item.Descripcion</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-primary btn-sm"
|
||||
onclick="editarIngreso(@item.Id, '@item.Nombre', '@item.Descripcion')">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<form asp-action="EliminarCategoriaIngreso" asp-route-id="@item.Id" method="post" class="d-inline" onsubmit="return confirm('¿Eliminar esta categoría?');">
|
||||
<button type="submit" class="btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Categorías de Egreso -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-danger">Categorías de Egreso</h6>
|
||||
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#modalCrearEgreso">
|
||||
<i class="fas fa-plus"></i> Nueva
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th style="width: 100px;">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in categoriasEgreso)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.Nombre</td>
|
||||
<td>@item.Descripcion</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-primary btn-sm"
|
||||
onclick="editarEgreso(@item.Id, '@item.Nombre', '@item.Descripcion')">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<form asp-action="EliminarCategoriaEgreso" asp-route-id="@item.Id" method="post" class="d-inline" onsubmit="return confirm('¿Eliminar esta categoría?');">
|
||||
<button type="submit" class="btn btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Crear Ingreso -->
|
||||
<div class="modal fade" id="modalCrearIngreso" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Nueva Categoría de Ingreso</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form asp-action="CrearCategoriaIngreso" method="post">
|
||||
<div class="modal-body">
|
||||
<div class="form-group mb-3">
|
||||
<label>Nombre</label>
|
||||
<input name="Nombre" class="form-control" required />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label>Descripción</label>
|
||||
<input name="Descripcion" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Crear Egreso -->
|
||||
<div class="modal fade" id="modalCrearEgreso" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Nueva Categoría de Egreso</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form asp-action="CrearCategoriaEgreso" method="post">
|
||||
<div class="modal-body">
|
||||
<div class="form-group mb-3">
|
||||
<label>Nombre</label>
|
||||
<input name="Nombre" class="form-control" required />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label>Descripción</label>
|
||||
<input name="Descripcion" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Editar (Compartido y poblado por JS) -->
|
||||
<div class="modal fade" id="modalEditar" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="tituloEditar">Editar Categoría</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="formEditar" method="post">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="Id" id="editId" />
|
||||
<div class="form-group mb-3">
|
||||
<label>Nombre</label>
|
||||
<input name="Nombre" id="editNombre" class="form-control" required />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label>Descripción</label>
|
||||
<input name="Descripcion" id="editDescripcion" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-primary">Actualizar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function editarIngreso(id, nombre, descripcion) {
|
||||
$('#tituloEditar').text('Editar Categoría de Ingreso');
|
||||
$('#formEditar').attr('action', '@Url.Action("EditarCategoriaIngreso")');
|
||||
llenarModal(id, nombre, descripcion);
|
||||
}
|
||||
|
||||
function editarEgreso(id, nombre, descripcion) {
|
||||
$('#tituloEditar').text('Editar Categoría de Egreso');
|
||||
$('#formEditar').attr('action', '@Url.Action("EditarCategoriaEgreso")');
|
||||
llenarModal(id, nombre, descripcion);
|
||||
}
|
||||
|
||||
function llenarModal(id, nombre, descripcion) {
|
||||
$('#editId').val(id);
|
||||
$('#editNombre').val(nombre);
|
||||
$('#editDescripcion').val(descripcion);
|
||||
|
||||
var modalEl = document.getElementById('modalEditar');
|
||||
var modal = new bootstrap.Modal(modalEl);
|
||||
modal.show();
|
||||
}
|
||||
</script>
|
||||
}
|
||||
124
RS_system/Views/ContabilidadGeneral/Index.cshtml
Normal file
124
RS_system/Views/ContabilidadGeneral/Index.cshtml
Normal file
@@ -0,0 +1,124 @@
|
||||
@model List<Rs_system.Models.ReporteMensualGeneral>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Contabilidad General";
|
||||
var anioActual = ViewBag.Anio;
|
||||
}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 text-gray-800">Contabilidad General</h1>
|
||||
|
||||
<form asp-action="Index" method="get" class="form-inline">
|
||||
<label class="me-2">Año:</label>
|
||||
<select name="anio" class="form-control me-2" asp-items="ViewBag.Anios" onchange="this.form.submit()">
|
||||
<option value="@anioActual" selected>@anioActual</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@if (TempData["Error"] != null)
|
||||
{
|
||||
<div class="alert alert-danger">@TempData["Error"]</div>
|
||||
}
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Reportes Mensuales @anioActual</h6>
|
||||
<div>
|
||||
<a asp-action="GestionCategorias" class="btn btn-info btn-sm">
|
||||
<i class="fas fa-tags"></i> Gestionar Categorías
|
||||
</a>
|
||||
<!-- Button trigger modal -->
|
||||
<button type="button" class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#abrirMesModal">
|
||||
<i class="fas fa-plus"></i> Abrir Nuevo Mes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mes</th>
|
||||
<th>Saldo Inicial</th>
|
||||
<th>Estado</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.NombreMes</td>
|
||||
<td>@item.SaldoInicial.ToString("C")</td>
|
||||
<td>
|
||||
@if (item.Cerrado)
|
||||
{
|
||||
<span class="badge bg-secondary">Cerrado</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-success">Abierto</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<a asp-action="RegistroMensual" asp-route-id="@item.Id" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-edit"></i> Gestionar
|
||||
</a>
|
||||
<a asp-action="Consolidado" asp-route-id="@item.Id" class="btn btn-info btn-sm">
|
||||
<i class="fas fa-chart-pie"></i> Ver Consolidado
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">No hay reportes para este año.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Abrir Mes -->
|
||||
<div class="modal fade" id="abrirMesModal" tabindex="-1" role="dialog" aria-labelledby="abrirMesModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="abrirMesModalLabel">Abrir Nuevo Mes Contable</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form asp-action="AbrirMes" method="post">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="anio" value="@anioActual" />
|
||||
<div class="form-group mb-3">
|
||||
<label>Mes</label>
|
||||
<select name="mes" class="form-select" required>
|
||||
@for (int i = 1; i <= 12; i++)
|
||||
{
|
||||
var nMes = new DateTime(2000, i, 1).ToString("MMMM", new System.Globalization.CultureInfo("es-ES"));
|
||||
<option value="@i">@nMes</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<p class="text-muted small">
|
||||
Nota: Al abrir el mes, se calculará automáticamente el saldo inicial basado en el cierre del mes anterior.
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-primary">Crear Reporte</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
479
RS_system/Views/ContabilidadGeneral/RegistroMensual.cshtml
Normal file
479
RS_system/Views/ContabilidadGeneral/RegistroMensual.cshtml
Normal file
@@ -0,0 +1,479 @@
|
||||
@model Rs_system.Models.ReporteMensualGeneral
|
||||
@{
|
||||
ViewData["Title"] = $"Registro - {Model.NombreMes} {Model.Anio}";
|
||||
var categoriasIngreso = ViewBag.CategoriasIngreso as List<Rs_system.Models.CategoriaIngreso>;
|
||||
var categoriasEgreso = ViewBag.CategoriasEgreso as List<Rs_system.Models.CategoriaEgreso>;
|
||||
}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h1 class="h3 text-gray-800">Registro Mensual: @Model.NombreMes @Model.Anio</h1>
|
||||
<h5 class="text-secondary">
|
||||
Saldo Actual: <span class="font-weight-bold @(ViewBag.SaldoActual >= 0 ? "text-success" : "text-danger")">@ViewBag.SaldoActual?.ToString("C")</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div>
|
||||
<a asp-action="Index" class="btn btn-secondary btn-sm">
|
||||
<i class="fas fa-arrow-left"></i> Volver
|
||||
</a>
|
||||
@if (!Model.Cerrado)
|
||||
{
|
||||
<button id="btnGuardar" class="btn btn-primary btn-sm ml-2">
|
||||
<i class="fas fa-save"></i> Guardar Cambios
|
||||
</button>
|
||||
<form asp-action="CerrarMes" asp-route-id="@Model.Id" method="post" class="d-inline ml-2" onsubmit="return confirm('¿Está seguro de cerrar este mes? No podrá realizar más cambios.');">
|
||||
<button type="submit" class="btn btn-warning btn-sm">
|
||||
<i class="fas fa-lock"></i> Cerrar Mes
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge badge-secondary ml-2 p-2">Mes Cerrado</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (TempData["Success"] != null)
|
||||
{
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
@TempData["Success"]
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (TempData["Error"] != null)
|
||||
{
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
@TempData["Error"]
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm mb-0" id="tablaRegistros">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th style="width: 50px;">#</th>
|
||||
<th style="width: 120px;">Fecha</th>
|
||||
<th style="width: 120px;">Tipo</th>
|
||||
<th style="width: 200px;">Categoría</th>
|
||||
<th>Descripción</th>
|
||||
<th style="width: 120px;">Comprobante</th>
|
||||
<th style="width: 50px;"></th>
|
||||
<th style="width: 150px;">Monto</th>
|
||||
@if (!Model.Cerrado)
|
||||
{
|
||||
<th style="width: 50px;"></th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tbodyRegistros">
|
||||
<!-- Rows rendered by JS -->
|
||||
</tbody>
|
||||
@if (!Model.Cerrado)
|
||||
{
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="9" class="text-center p-2">
|
||||
<button class="btn btn-outline-primary btn-sm" onclick="agregarFila()">
|
||||
<i class="fas fa-plus"></i> Agregar Fila
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Adjuntos -->
|
||||
<div class="modal fade" id="modalAdjuntos" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Adjuntos del Movimiento</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="adjuntoMovimientoId" />
|
||||
|
||||
@if (!Model.Cerrado)
|
||||
{
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Subir Archivos (Imágenes o PDF)</label>
|
||||
<!-- Usar 'form' envolvente para resetear fácil -->
|
||||
<form id="formSubirAdjuntos">
|
||||
<div class="input-group">
|
||||
<input type="file" class="form-control" id="inputArchivos" multiple accept="image/*,.pdf">
|
||||
<button class="btn btn-primary" type="button" onclick="subirArchivos()">
|
||||
<i class="fas fa-upload"></i> Subir
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Fecha</th>
|
||||
<th style="width: 150px;">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tbodyAdjuntos">
|
||||
<!-- Populated via JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
const esCerrado = @Model.Cerrado.ToString().ToLower();
|
||||
const reporteId = @Model.Id;
|
||||
|
||||
// Data from server
|
||||
let rawRegistros = @Html.Raw(Json.Serialize(Model.Movimientos.Select(m => new {
|
||||
m.Id,
|
||||
m.Tipo, // 1 = Ingreso, 2 = Egreso
|
||||
m.CategoriaIngresoId,
|
||||
m.CategoriaEgresoId,
|
||||
m.Monto,
|
||||
Fecha = m.Fecha.ToString("yyyy-MM-dd"),
|
||||
m.Descripcion,
|
||||
m.NumeroComprobante
|
||||
})));
|
||||
|
||||
// Normalizar datos a mi estructura interna para evitar problemas de mayúsculas/minúsculas
|
||||
let registros = rawRegistros.map(r => ({
|
||||
id: r.Id || r.id || 0,
|
||||
tipo: (r.Tipo !== undefined ? r.Tipo : r.tipo),
|
||||
categoriaIngresoId: r.CategoriaIngresoId || r.categoriaIngresoId,
|
||||
categoriaEgresoId: r.CategoriaEgresoId || r.categoriaEgresoId,
|
||||
monto: r.Monto !== undefined ? r.Monto : r.monto,
|
||||
fecha: r.Fecha || r.fecha,
|
||||
descripcion: r.Descripcion || r.descripcion,
|
||||
numeroComprobante: r.NumeroComprobante || r.numeroComprobante
|
||||
}));
|
||||
|
||||
// Categories helper
|
||||
const rawCatsIngreso = @Html.Raw(Json.Serialize(categoriasIngreso.Select(c => new { c.Id, c.Nombre })));
|
||||
const rawCatsEgreso = @Html.Raw(Json.Serialize(categoriasEgreso.Select(c => new { c.Id, c.Nombre })));
|
||||
|
||||
const catsIngreso = rawCatsIngreso.map(c => ({ id: c.Id || c.id, nombre: c.Nombre || c.nombre }));
|
||||
const catsEgreso = rawCatsEgreso.map(c => ({ id: c.Id || c.id, nombre: c.Nombre || c.nombre }));
|
||||
|
||||
function renderTable() {
|
||||
const tbody = document.getElementById('tbodyRegistros');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
registros.forEach((reg, index) => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.dataset.index = index;
|
||||
|
||||
// Logic for Attachment Button
|
||||
const hasId = reg.id && reg.id > 0;
|
||||
const btnAdjuntoClass = hasId ? "btn-info" : "btn-secondary";
|
||||
const btnAdjuntoTitle = hasId ? "Gestionar Adjuntos" : "Guarde primero para adjuntar";
|
||||
// IMPORTANTE: onclick debe ser cadena vacía si no hay ID, pero el disabled lo controla.
|
||||
const btnAdjuntoAction = hasId ? `abrirAdjuntos(${reg.id})` : "";
|
||||
const btnAdjuntoIcon = '<i class="fas fa-paperclip"></i>';
|
||||
const disabledAttr = hasId ? "" : "disabled";
|
||||
|
||||
if (esCerrado) {
|
||||
// Read-only view
|
||||
const tipoTexto = reg.tipo === 1 ? '<span class="text-success">Ingreso</span>' : '<span class="text-danger">Egreso</span>';
|
||||
const catNombre = reg.tipo === 0
|
||||
? (catsIngreso.find(c => c.id === reg.categoriaIngresoId)?.nombre || '-')
|
||||
: (catsEgreso.find(c => c.id === reg.categoriaEgresoId)?.nombre || '-');
|
||||
|
||||
tr.innerHTML = `
|
||||
<td class="text-center">${index + 1}</td>
|
||||
<td>${reg.fecha}</td>
|
||||
<td>${tipoTexto}</td>
|
||||
<td>${catNombre}</td>
|
||||
<td>${reg.descripcion || ''}</td>
|
||||
<td>${reg.numeroComprobante || ''}</td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="btn btn-sm ${btnAdjuntoClass}" onclick="${btnAdjuntoAction}" title="${btnAdjuntoTitle}" ${disabledAttr}>
|
||||
${btnAdjuntoIcon}
|
||||
</button>
|
||||
</td>
|
||||
<td class="text-end">${parseFloat(reg.monto).toFixed(2)}</td>
|
||||
<td></td>
|
||||
`;
|
||||
} else {
|
||||
// Editable view
|
||||
tr.innerHTML = `
|
||||
<td class="text-center align-middle">${index + 1}</td>
|
||||
<td><input type="date" class="form-control form-control-sm" value="${reg.fecha}" onchange="updateReg(${index}, 'fecha', this.value)"></td>
|
||||
<td>
|
||||
<select class="form-control form-control-sm" onchange="cambiarTipo(${index}, this.value)">
|
||||
<option value="1" ${reg.tipo === 1 ? 'selected' : ''}>Ingreso</option>
|
||||
<option value="2" ${reg.tipo === 2 ? 'selected' : ''}>Egreso</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-control form-control-sm" onchange="updateReg(${index}, 'categoria', this.value)">
|
||||
<option value="">Seleccione...</option>
|
||||
${renderCatsOptions(reg.tipo, reg.tipo === 1 ? reg.categoriaIngresoId : reg.categoriaEgresoId)}
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="form-control form-control-sm" value="${reg.descripcion || ''}" onchange="updateReg(${index}, 'descripcion', this.value)"></td>
|
||||
<td><input type="text" class="form-control form-control-sm" value="${reg.numeroComprobante || ''}" onchange="updateReg(${index}, 'numeroComprobante', this.value)"></td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="btn btn-sm ${btnAdjuntoClass}" onclick="${btnAdjuntoAction}" title="${btnAdjuntoTitle}" ${disabledAttr}>
|
||||
${btnAdjuntoIcon}
|
||||
</button>
|
||||
</td>
|
||||
<td><input type="number" step="0.01" class="form-control form-control-sm text-end" value="${reg.monto}" onchange="updateReg(${index}, 'monto', this.value)"></td>
|
||||
<td class="text-center align-middle">
|
||||
<button class="btn btn-danger btn-sm py-0" onclick="eliminarFila(${index})" title="Eliminar"><i class="fas fa-times"></i></button>
|
||||
</td>
|
||||
`;
|
||||
}
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function renderCatsOptions(tipo, selectedId) {
|
||||
const list = tipo == 1 ? catsIngreso : catsEgreso; // loose equality for string/number match
|
||||
return list.map(c => `<option value="${c.id}" ${c.id == selectedId ? 'selected' : ''}>${c.nombre}</option>`).join('');
|
||||
}
|
||||
|
||||
function agregarFila() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
registros.push({
|
||||
id: 0,
|
||||
tipo: 1,
|
||||
categoriaIngresoId: null,
|
||||
categoriaEgresoId: null,
|
||||
monto: 0,
|
||||
fecha: today,
|
||||
descripcion: '',
|
||||
numeroComprobante: ''
|
||||
});
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function eliminarFila(index) {
|
||||
registros.splice(index, 1);
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function cambiarTipo(index, nuevoTipo) {
|
||||
registros[index].tipo = parseInt(nuevoTipo);
|
||||
registros[index].categoriaIngresoId = null;
|
||||
registros[index].categoriaEgresoId = null;
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function updateReg(index, field, value) {
|
||||
const reg = registros[index];
|
||||
if (field === 'categoria') {
|
||||
const val = value ? parseInt(value) : null;
|
||||
if (reg.tipo === 1) reg.categoriaIngresoId = val;
|
||||
else reg.categoriaEgresoId = val;
|
||||
} else if (field === 'monto') {
|
||||
reg.monto = parseFloat(value) || 0;
|
||||
} else {
|
||||
reg[field] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: Payload mapping for Server (Server expects PascalCase usually, but standard API accepts camelCase too)
|
||||
document.getElementById('btnGuardar')?.addEventListener('click', async () => {
|
||||
// Validations
|
||||
for (let i = 0; i < registros.length; i++) {
|
||||
const r = registros[i];
|
||||
if (r.monto <= 0) {
|
||||
alert(`Fila ${i+1}: El monto debe ser mayor a 0.`);
|
||||
return;
|
||||
}
|
||||
if (r.tipo === 1 && !r.categoriaIngresoId) {
|
||||
alert(`Fila ${i+1}: Debe seleccionar una categoría de ingreso.`);
|
||||
return;
|
||||
}
|
||||
if (r.tipo === 2 && !r.categoriaEgresoId) {
|
||||
alert(`Fila ${i+1}: Debe seleccionar una categoría de egreso.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btnGuardar');
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Guardando...';
|
||||
btn.disabled = true;
|
||||
|
||||
// Map back to PascalCase structure just in case server strictly needs it matches DTO
|
||||
const payloadMovimientos = registros.map(r => ({
|
||||
Id: r.id,
|
||||
Tipo: r.tipo,
|
||||
CategoriaIngresoId: r.categoriaIngresoId,
|
||||
CategoriaEgresoId: r.categoriaEgresoId,
|
||||
Monto: r.monto,
|
||||
Fecha: r.fecha,
|
||||
Descripcion: r.descripcion,
|
||||
NumeroComprobante: r.numeroComprobante
|
||||
}));
|
||||
|
||||
try {
|
||||
const response = await fetch('@Url.Action("GuardarBulk")', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ReporteId: reporteId,
|
||||
Movimientos: payloadMovimientos
|
||||
})
|
||||
});
|
||||
// ... rest of logic
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// Show toast or alert
|
||||
alert('Guardado exitosamente');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert('Ocurrió un error al guardar.');
|
||||
} finally {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// ================= ADJUNTOS LOGIC =================
|
||||
|
||||
let currentMovId = 0;
|
||||
var myModalAdjuntos;
|
||||
|
||||
function abrirAdjuntos(id) {
|
||||
currentMovId = id;
|
||||
document.getElementById('adjuntoMovimientoId').value = id;
|
||||
|
||||
// Clear previous entries
|
||||
document.getElementById('tbodyAdjuntos').innerHTML = '<tr><td colspan="3" class="text-center">Cargando...</td></tr>';
|
||||
|
||||
if (!myModalAdjuntos) {
|
||||
var el = document.getElementById('modalAdjuntos');
|
||||
myModalAdjuntos = new bootstrap.Modal(el);
|
||||
}
|
||||
myModalAdjuntos.show();
|
||||
|
||||
cargarAdjuntos(id);
|
||||
}
|
||||
|
||||
async function cargarAdjuntos(id) {
|
||||
try {
|
||||
const url = '@Url.Action("ObtenerAdjuntos")?movimientoId=' + id;
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
|
||||
const tbody = document.getElementById('tbodyAdjuntos');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="text-center">Sin adjuntos</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
data.forEach(adj => {
|
||||
const tr = document.createElement('tr');
|
||||
|
||||
// Si es imagen, mostrar preview pequeño en popover o algo, pero por ahora link simple
|
||||
let link = `<a href="${adj.url}" target="_blank" class="text-decoration-none"><i class="fas fa-external-link-alt"></i> ${adj.nombre}</a>`;
|
||||
|
||||
let deleteBtn = '';
|
||||
if (!esCerrado) {
|
||||
deleteBtn = `<button class="btn btn-danger btn-sm" onclick="eliminarAdjunto(${adj.id})"><i class="fas fa-trash"></i></button>`;
|
||||
}
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>${link}</td>
|
||||
<td>${adj.fecha}</td>
|
||||
<td class="text-center">
|
||||
<a href="${adj.url}" download class="btn btn-secondary btn-sm"><i class="fas fa-download"></i></a>
|
||||
${deleteBtn}
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
document.getElementById('tbodyAdjuntos').innerHTML = '<tr><td colspan="3" class="text-danger">Error al cargar adjuntos</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
async function subirArchivos() {
|
||||
const input = document.getElementById('inputArchivos');
|
||||
if (input.files.length === 0) {
|
||||
alert("Seleccione al menos un archivo.");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('movimientoId', currentMovId);
|
||||
for (let i = 0; i < input.files.length; i++) {
|
||||
formData.append('archivos', input.files[i]);
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('@Url.Action("SubirAdjunto")', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const result = await res.json();
|
||||
if (result.success) {
|
||||
// Limpiar input y recargar
|
||||
input.value = '';
|
||||
cargarAdjuntos(currentMovId);
|
||||
} else {
|
||||
alert("Error: " + result.message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error al subir archivos.");
|
||||
}
|
||||
}
|
||||
|
||||
async function eliminarAdjunto(id) {
|
||||
if (!confirm('¿Eliminar este adjunto?')) return;
|
||||
try {
|
||||
const res = await fetch('@Url.Action("EliminarAdjunto")?id=' + id, { method: 'POST' });
|
||||
const result = await res.json();
|
||||
if (result.success) {
|
||||
cargarAdjuntos(currentMovId);
|
||||
} else {
|
||||
alert("No se pudo eliminar.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error al eliminar.");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
if (registros.length === 0 && !esCerrado) {
|
||||
agregarFila();
|
||||
} else {
|
||||
renderTable();
|
||||
}
|
||||
</script>
|
||||
}
|
||||
67
RS_system/Views/Estados/Create.cshtml
Normal file
67
RS_system/Views/Estados/Create.cshtml
Normal file
@@ -0,0 +1,67 @@
|
||||
@model Rs_system.Models.EstadoArticulo
|
||||
@{
|
||||
ViewData["Title"] = "Nuevo Estado";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Nuevo Estado</h4>
|
||||
<p class="text-muted mb-0">Registrar un nuevo estado para clasificación</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom" style="max-width: 800px; margin: 0 auto;">
|
||||
<div class="card-body">
|
||||
<form asp-action="Create" method="post">
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Nombre" class="form-label">Nombre <span class="text-danger">*</span></label>
|
||||
<input asp-for="Nombre" class="form-control" placeholder="Ej: Bueno, Malo, Regular" autofocus />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="2" placeholder="Breve descripción del estado (opcional)"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Color" class="form-label">Color de Etiqueta</label>
|
||||
<select asp-for="Color" class="form-select">
|
||||
<option value="secondary">Gris (Por defecto)</option>
|
||||
<option value="success">Verde (Bueno/Completo)</option>
|
||||
<option value="warning">Amarillo (Advertencia/Reparación)</option>
|
||||
<option value="danger">Rojo (Malo/Crítico)</option>
|
||||
<option value="info">Azul (Informativo)</option>
|
||||
<option value="primary">Azul Oscuro (Primario)</option>
|
||||
</select>
|
||||
<div class="form-text">Color que se usará para mostrar el estado en las listas.</div>
|
||||
<span asp-validation-for="Color" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" role="switch" checked />
|
||||
<label asp-for="Activo" class="form-check-label">Estado Activo</label>
|
||||
</div>
|
||||
<div class="form-text">Si está inactivo, no aparecerá en las selecciones.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Guardar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
}
|
||||
68
RS_system/Views/Estados/Edit.cshtml
Normal file
68
RS_system/Views/Estados/Edit.cshtml
Normal file
@@ -0,0 +1,68 @@
|
||||
@model Rs_system.Models.EstadoArticulo
|
||||
@{
|
||||
ViewData["Title"] = "Editar Estado";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Editar Estado</h4>
|
||||
<p class="text-muted mb-0">Modificar información del estado</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom" style="max-width: 800px; margin: 0 auto;">
|
||||
<div class="card-body">
|
||||
<form asp-action="Edit" method="post">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Nombre" class="form-label">Nombre <span class="text-danger">*</span></label>
|
||||
<input asp-for="Nombre" class="form-control" placeholder="Ej: Bueno, Malo, Regular" />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="2" placeholder="Breve descripción del estado (opcional)"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Color" class="form-label">Color de Etiqueta</label>
|
||||
<select asp-for="Color" class="form-select">
|
||||
<option value="secondary">Gris (Por defecto)</option>
|
||||
<option value="success">Verde (Bueno/Completo)</option>
|
||||
<option value="warning">Amarillo (Advertencia/Reparación)</option>
|
||||
<option value="danger">Rojo (Malo/Crítico)</option>
|
||||
<option value="info">Azul (Informativo)</option>
|
||||
<option value="primary">Azul Oscuro (Primario)</option>
|
||||
</select>
|
||||
<div class="form-text">Color que se usará para mostrar el estado en las listas.</div>
|
||||
<span asp-validation-for="Color" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" role="switch" />
|
||||
<label asp-for="Activo" class="form-check-label">Estado Activo</label>
|
||||
</div>
|
||||
<div class="form-text">Si está inactivo, no aparecerá en las selecciones.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Guardar Cambios
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
}
|
||||
121
RS_system/Views/Estados/Index.cshtml
Normal file
121
RS_system/Views/Estados/Index.cshtml
Normal file
@@ -0,0 +1,121 @@
|
||||
@model IEnumerable<Rs_system.Models.EstadoArticulo>
|
||||
@{
|
||||
ViewData["Title"] = "Estados de Artículos";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Estados de Artículos</h4>
|
||||
<p class="text-muted mb-0">Gestión de estados para bienes e inventario</p>
|
||||
</div>
|
||||
<a asp-action="Create" class="btn btn-primary-custom">
|
||||
<i class="bi bi-plus-lg me-1"></i> Nuevo Estado
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th class="text-center">Etiqueta Visual</th>
|
||||
<th class="text-center">Estado</th>
|
||||
<th class="text-center">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted py-4">
|
||||
<i class="bi bi-palette fs-1 d-block mb-2"></i>
|
||||
No hay estados registrados
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<strong>@item.Nombre</strong>
|
||||
</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(item.Descripcion))
|
||||
{
|
||||
<span>@item.Descripcion</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">-</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-@item.Color">@item.Nombre</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@if (item.Activo)
|
||||
{
|
||||
<span class="badge bg-success">Activo</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Inactivo</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-outline-secondary" title="Editar">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="confirmDelete(@item.Id)" title="Eliminar">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Form -->
|
||||
<form id="deleteForm" asp-action="Delete" method="post" style="display: none;">
|
||||
<input type="hidden" name="id" id="deleteId" />
|
||||
</form>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function confirmDelete(id) {
|
||||
Swal.fire({
|
||||
title: '¿Eliminar estado?',
|
||||
text: 'Esta acción moverá el estado a la papelera.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#d33',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: 'Sí, eliminar',
|
||||
cancelButtonText: 'Cancelar'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('deleteId').value = id;
|
||||
document.getElementById('deleteForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show success/error messages
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.success('@TempData["SuccessMessage"]');
|
||||
</text>
|
||||
}
|
||||
@if (TempData["ErrorMessage"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.error('@TempData["ErrorMessage"]');
|
||||
</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
298
RS_system/Views/MovimientosInventario/Create.cshtml
Normal file
298
RS_system/Views/MovimientosInventario/Create.cshtml
Normal file
@@ -0,0 +1,298 @@
|
||||
@model dynamic
|
||||
@{
|
||||
ViewData["Title"] = "Nuevo Movimiento";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Registrar Movimiento</h4>
|
||||
<p class="text-muted mb-0">Traslados, bajas o cambios de estado</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Cancelar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Step 1: Select Article -->
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card-custom h-100">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<h5 class="card-title mb-0">1. Seleccionar Artículo</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="get" asp-action="Create">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Buscar Artículo</label>
|
||||
<select name="articuloId" class="form-select" asp-items="ViewBag.Articulos" onchange="this.form.submit()">
|
||||
<option value="">-- Seleccione un artículo --</option>
|
||||
</select>
|
||||
<div class="form-text">Seleccione para cargar datos actuales.</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if (ViewBag.ArticuloId != null)
|
||||
{
|
||||
<div class="alert alert-light border mt-3">
|
||||
<h6 class="fw-bold mb-2">Estado Actual</h6>
|
||||
<div class="mb-1"><span class="text-muted">Ubicación:</span> <strong>@ViewBag.UbicacionActual</strong></div>
|
||||
<div class="mb-1"><span class="text-muted">Estado:</span> <strong>@ViewBag.EstadoActual</strong></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Select Action -->
|
||||
<div class="col-md-8">
|
||||
@if (ViewBag.ArticuloId != null)
|
||||
{
|
||||
<div class="card-custom">
|
||||
<div class="card-header bg-transparent py-3">
|
||||
<ul class="nav nav-pills card-header-pills" id="pills-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="pills-entrada-tab" data-bs-toggle="pill" data-bs-target="#pills-entrada" type="button" role="tab">
|
||||
<i class="bi bi-plus-circle me-1"></i> Entrada
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="pills-traslado-tab" data-bs-toggle="pill" data-bs-target="#pills-traslado" type="button" role="tab">
|
||||
<i class="bi bi-arrow-left-right me-1"></i> Traslado
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="pills-estado-tab" data-bs-toggle="pill" data-bs-target="#pills-estado" type="button" role="tab">
|
||||
<i class="bi bi-cone-striped me-1"></i> Cambio de Estado
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="pills-prestamo-tab" data-bs-toggle="pill" data-bs-target="#pills-prestamo" type="button" role="tab">
|
||||
<i class="bi bi-box-arrow-right me-1"></i> Préstamo
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link text-danger" id="pills-baja-tab" data-bs-toggle="pill" data-bs-target="#pills-baja" type="button" role="tab">
|
||||
<i class="bi bi-trash me-1"></i> Dar de Baja
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body pt-4">
|
||||
<div class="tab-content" id="pills-tabContent">
|
||||
|
||||
<!-- ENTRADA FORM -->
|
||||
<div class="tab-pane fade show active" id="pills-entrada" role="tabpanel">
|
||||
<form asp-action="RegistrarEntrada" method="post">
|
||||
<input type="hidden" name="articuloId" value="@ViewBag.ArticuloId" />
|
||||
<h5 class="mb-3">Registrar Entrada de Inventario (Compra/Reingreso)</h5>
|
||||
|
||||
<div class="alert alert-success py-2">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
Esta acción aumentará el stock actual del artículo.
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">Cantidad a Ingresar</label>
|
||||
@if (ViewBag.TipoControl == "LOTE")
|
||||
{
|
||||
<input type="number" name="cantidad" class="form-control" min="1" value="1" required />
|
||||
}
|
||||
else
|
||||
{
|
||||
<input type="number" name="cantidad" class="form-control" value="1" readonly />
|
||||
<div class="form-text">Para artículos unitarios, la cantidad es siempre 1.</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Observación / Referencia</label>
|
||||
<textarea name="observacion" class="form-control" rows="2" placeholder="Ej: Compra según factura #123, Donación recibida..."></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="bi bi-plus-lg me-1"></i> Confirmar Entrada
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- TRASLADO FORM -->
|
||||
<div class="tab-pane fade" id="pills-traslado" role="tabpanel">
|
||||
<form asp-action="RegistrarTraslado" method="post">
|
||||
<input type="hidden" name="articuloId" value="@ViewBag.ArticuloId" />
|
||||
<h5 class="mb-3">Registrar Traslado de Ubicación</h5>
|
||||
|
||||
@if (ViewBag.TipoControl == "LOTE")
|
||||
{
|
||||
<div class="alert alert-info py-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><strong>Control por Lote:</strong> Especifique la cantidad a mover.</span>
|
||||
<span class="badge bg-light text-dark border">Global: @ViewBag.CantidadGlobal</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-primary fw-bold">Cantidad a Mover</label>
|
||||
<input type="number" name="cantidad" class="form-control" min="1" max="@ViewBag.CantidadGlobal" value="1" required />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nueva Ubicación</label>
|
||||
<select name="nuevaUbicacionId" class="form-select" asp-items="ViewBag.Ubicaciones" required>
|
||||
<option value="">-- Seleccionar Destino --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Observación</label>
|
||||
<textarea name="observacion" class="form-control" rows="2" placeholder="Motivo del traslado..."></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Confirmar Traslado
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- CAMBIO ESTADO FORM -->
|
||||
<div class="tab-pane fade" id="pills-estado" role="tabpanel">
|
||||
<form asp-action="RegistrarCambioEstado" method="post">
|
||||
<input type="hidden" name="articuloId" value="@ViewBag.ArticuloId" />
|
||||
<h5 class="mb-3">Registrar Cambio de Condición</h5>
|
||||
|
||||
<div class="alert alert-light border">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
@if(ViewBag.TipoControl == "LOTE") {
|
||||
<span>El cambio de estado aplicará a <strong>todo el lote</strong> (@ViewBag.CantidadGlobal unidades).</span>
|
||||
} else {
|
||||
<span>El cambio de estado aplica a la unidad única.</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nuevo Estado</label>
|
||||
<select name="nuevoEstadoId" class="form-select" asp-items="ViewBag.Estados" required>
|
||||
<option value="">-- Seleccionar Nuevo Estado --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Observación</label>
|
||||
<textarea name="observacion" class="form-control" rows="2" placeholder="Detalles del daño o reparación..."></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning text-white">
|
||||
<i class="bi bi-save me-1"></i> Confirmar Cambio
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- PRESTAMO FORM -->
|
||||
<div class="tab-pane fade" id="pills-prestamo" role="tabpanel">
|
||||
<form asp-action="RegistrarPrestamo" method="post">
|
||||
<input type="hidden" name="articuloId" value="@ViewBag.ArticuloId" />
|
||||
<h5 class="mb-3">Registrar Préstamo a Persona</h5>
|
||||
|
||||
@if (ViewBag.TipoControl == "LOTE")
|
||||
{
|
||||
<div class="alert alert-info py-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span><strong>Control por Lote:</strong> Especifique la cantidad a prestar.</span>
|
||||
<span class="badge bg-light text-dark border">Disponible: @ViewBag.CantidadGlobal</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-primary fw-bold">Cantidad a Prestar</label>
|
||||
<input type="number" name="cantidad" class="form-control" min="1" max="@ViewBag.CantidadGlobal" value="1" required />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre de la Persona <span class="text-danger">*</span></label>
|
||||
<input type="text" name="personaNombre" class="form-control" placeholder="Nombre completo" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Identificación</label>
|
||||
<input type="text" name="personaIdentificacion" class="form-control" placeholder="Cédula, DNI, etc." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Fecha Devolución Estimada</label>
|
||||
<input type="date" name="fechaDevolucionEstimada" class="form-control" />
|
||||
<div class="form-text">Fecha aproximada de devolución del artículo.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Observación</label>
|
||||
<textarea name="observacion" class="form-control" rows="2" placeholder="Detalles del préstamo..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-light border">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
<span>Se generarán códigos individuales para cada artículo prestado (ej: sp-b20-001, sp-b20-002, ...)</span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-info text-white">
|
||||
<i class="bi bi-box-arrow-right me-1"></i> Confirmar Préstamo
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- BAJA FORM -->
|
||||
<div class="tab-pane fade" id="pills-baja" role="tabpanel">
|
||||
<form asp-action="RegistrarBaja" method="post" onsubmit="return confirm('¿Está seguro de realizar esta baja?');">
|
||||
<input type="hidden" name="articuloId" value="@ViewBag.ArticuloId" />
|
||||
<h5 class="mb-3 text-danger">Registrar Baja de Activo</h5>
|
||||
|
||||
@if (ViewBag.TipoControl == "LOTE")
|
||||
{
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">Cantidad a dar de Baja</label>
|
||||
<input type="number" name="cantidad" class="form-control" min="1" max="@ViewBag.CantidadGlobal" value="1" required />
|
||||
<div class="form-text">Esto restará del stock global.</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong>Atención:</strong> Esta acción marcará el artículo como inactivo.
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Motivo de Baja <span class="text-danger">*</span></label>
|
||||
<textarea name="motivo" class="form-control" rows="3" placeholder="Ej: Robo, Pérdida total, Venta, Donación..." required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-trash me-1"></i> Confirmar Baja
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card-custom h-100 d-flex align-items-center justify-content-center bg-light border-0">
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="bi bi-arrow-left-circle fs-1 mb-3 d-block"></i>
|
||||
<h5>Seleccione un artículo para comenzar</h5>
|
||||
<p>Use el panel de la izquierda para buscar el activo.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
@if (TempData["ErrorMessage"] != null)
|
||||
{
|
||||
<text>toastr.error('@TempData["ErrorMessage"]');</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
128
RS_system/Views/MovimientosInventario/Index.cshtml
Normal file
128
RS_system/Views/MovimientosInventario/Index.cshtml
Normal file
@@ -0,0 +1,128 @@
|
||||
@model IEnumerable<Rs_system.Models.MovimientoInventario>
|
||||
@{
|
||||
ViewData["Title"] = "Historial de Movimientos";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Historial de Movimientos</h4>
|
||||
<p class="text-muted mb-0">Registro de traslados, bajas y cambios de estado</p>
|
||||
</div>
|
||||
<a asp-action="Create" class="btn btn-primary-custom">
|
||||
<i class="bi bi-arrow-left-right me-1"></i> Nuevo Movimiento
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fecha</th>
|
||||
<th>Artículo</th>
|
||||
<th>Tipo</th>
|
||||
<th>Detalles</th>
|
||||
<th>Usuario</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted py-5">
|
||||
<i class="bi bi-clock-history fs-1 d-block mb-2"></i>
|
||||
No hay movimientos registrados
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td style="width: 150px;">
|
||||
<div class="fw-bold">@item.Fecha.ToLocalTime().ToString("dd/MM/yyyy")</div>
|
||||
<small class="text-muted">@item.Fecha.ToLocalTime().ToString("HH:mm")</small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-bold">@item.Articulo?.Codigo</div>
|
||||
<small class="text-muted">@item.Articulo?.Nombre</small>
|
||||
</td>
|
||||
<td>
|
||||
@switch (item.TipoMovimiento)
|
||||
{
|
||||
case "ENTRADA":
|
||||
<span class="badge bg-success">ENTRADA</span>
|
||||
break;
|
||||
case "TRASLADO":
|
||||
<span class="badge bg-info text-dark">TRASLADO</span>
|
||||
break;
|
||||
case "BAJA":
|
||||
<span class="badge bg-danger">BAJA</span>
|
||||
break;
|
||||
case "PRESTAMO":
|
||||
<span class="badge bg-primary">PRESTAMO</span>
|
||||
break;
|
||||
case "DEVOLUCION":
|
||||
<span class="badge bg-teal" style="background-color: #20c997; color: white;">DEVOLUCIÓN</span>
|
||||
break;
|
||||
case "CAMBIO_ESTADO":
|
||||
<span class="badge bg-warning text-dark">ESTADO</span>
|
||||
break;
|
||||
default:
|
||||
<span class="badge bg-secondary">@item.TipoMovimiento</span>
|
||||
break;
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (item.TipoMovimiento == "TRASLADO")
|
||||
{
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="text-muted me-2">@item.UbicacionOrigen?.Nombre</span>
|
||||
<i class="bi bi-arrow-right text-primary me-2"></i>
|
||||
<span class="fw-bold">@item.UbicacionDestino?.Nombre</span>
|
||||
</div>
|
||||
}
|
||||
else if (item.TipoMovimiento == "CAMBIO_ESTADO")
|
||||
{
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="text-muted me-2">@item.EstadoAnterior?.Nombre</span>
|
||||
<i class="bi bi-arrow-right text-primary me-2"></i>
|
||||
<span class="fw-bold">@item.EstadoNuevo?.Nombre</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (item.Cantidad > 1 || item.Articulo?.TipoControl == "LOTE")
|
||||
{
|
||||
<div class="mt-1">
|
||||
<span class="badge bg-light text-dark border">Cantidad: @item.Cantidad</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(item.Observacion))
|
||||
{
|
||||
<div class="text-muted fst-italic mt-1 small">
|
||||
<i class="bi bi-card-text me-1"></i> @item.Observacion
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<small class="text-muted">@item.UsuarioId</small>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<text>toastr.success('@TempData["SuccessMessage"]');</text>
|
||||
}
|
||||
@if (TempData["ErrorMessage"] != null)
|
||||
{
|
||||
<text>toastr.error('@TempData["ErrorMessage"]');</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
156
RS_system/Views/MovimientosInventario/PrestamosActivos.cshtml
Normal file
156
RS_system/Views/MovimientosInventario/PrestamosActivos.cshtml
Normal file
@@ -0,0 +1,156 @@
|
||||
@model IEnumerable<Rs_system.Models.Prestamo>
|
||||
@{
|
||||
ViewData["Title"] = "Préstamos Activos";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Préstamos Activos</h4>
|
||||
<p class="text-muted mb-0">Artículos actualmente prestados</p>
|
||||
</div>
|
||||
<div>
|
||||
<a asp-action="Create" class="btn btn-outline-primary me-2">
|
||||
<i class="bi bi-plus-circle me-1"></i> Nuevo Préstamo
|
||||
</a>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Historial
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="card-body">
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-box-arrow-in-right fs-1 text-muted mb-3 d-block"></i>
|
||||
<h5 class="text-muted">No hay préstamos activos</h5>
|
||||
<p class="text-muted">Todos los artículos han sido devueltos.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Artículo</th>
|
||||
<th>Persona</th>
|
||||
<th>Cantidad</th>
|
||||
<th>Fecha Préstamo</th>
|
||||
<th>Devolución Estimada</th>
|
||||
<th>Estado</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var prestamo in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold">@prestamo.Articulo?.Codigo</div>
|
||||
<div class="text-muted small">@prestamo.Articulo?.Nombre</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-bold">@prestamo.PersonaNombre</div>
|
||||
@if (!string.IsNullOrEmpty(prestamo.PersonaIdentificacion))
|
||||
{
|
||||
<div class="text-muted small">@prestamo.PersonaIdentificacion</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-light text-dark">@prestamo.Cantidad</span>
|
||||
</td>
|
||||
<td>@prestamo.FechaPrestamo.ToString("dd/MM/yyyy")</td>
|
||||
<td>
|
||||
@if (prestamo.FechaDevolucionEstimada.HasValue)
|
||||
{
|
||||
var diasRestantes = (prestamo.FechaDevolucionEstimada.Value - DateTime.Today).Days;
|
||||
var claseCss = diasRestantes < 0 ? "text-danger" : diasRestantes <= 3 ? "text-warning" : "text-success";
|
||||
<span class="@claseCss">@prestamo.FechaDevolucionEstimada.Value.ToString("dd/MM/yyyy")</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">No definida</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@switch (prestamo.Estado)
|
||||
{
|
||||
case "ACTIVO":
|
||||
<span class="badge bg-info">Activo</span>
|
||||
break;
|
||||
case "ATRASADO":
|
||||
<span class="badge bg-danger">Atrasado</span>
|
||||
break;
|
||||
default:
|
||||
<span class="badge bg-secondary">@prestamo.Estado</span>
|
||||
break;
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-info" onclick="verDetalles(@prestamo.Id)">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
<form asp-action="RegistrarDevolucion" method="post" style="display: inline;">
|
||||
<input type="hidden" name="prestamoId" value="@prestamo.Id" />
|
||||
<button type="submit" class="btn btn-sm btn-success" onclick="return confirm('¿Confirmar devolución de este préstamo?');">
|
||||
<i class="bi bi-check-circle"></i> Devolver
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Detalles -->
|
||||
<div class="modal fade" id="detallesModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Detalles del Préstamo</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="detallesContent">
|
||||
<!-- Content loaded via AJAX -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function verDetalles(prestamoId) {
|
||||
// Aquí podrías implementar una llamada AJAX para cargar los detalles
|
||||
// Por ahora mostramos un mensaje simple
|
||||
$('#detallesContent').html('<div class="text-center py-3"><i class="bi bi-hourglass-split"></i> Cargando detalles...</div>');
|
||||
$('#detallesModal').modal('show');
|
||||
|
||||
// Simulación de carga
|
||||
setTimeout(() => {
|
||||
$('#detallesContent').html(`
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
Los códigos individuales de los artículos prestados se mostrarán aquí.
|
||||
</div>
|
||||
`);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<text>toastr.success('@TempData["SuccessMessage"]');</text>
|
||||
}
|
||||
@if (TempData["ErrorMessage"] != null)
|
||||
{
|
||||
<text>toastr.error('@TempData["ErrorMessage"]');</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
63
RS_system/Views/TipoColaboracion/Create.cshtml
Normal file
63
RS_system/Views/TipoColaboracion/Create.cshtml
Normal file
@@ -0,0 +1,63 @@
|
||||
@model Rs_system.Models.TipoColaboracion
|
||||
@{
|
||||
ViewData["Title"] = "Nuevo Tipo de Colaboración";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Nuevo Tipo de Colaboración</h4>
|
||||
<p class="text-muted mb-0">Crear un nuevo tipo de colaboración</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<form asp-action="Create" method="post">
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Nombre" class="form-label"></label>
|
||||
<input asp-for="Nombre" class="form-control" placeholder="Ej: Mantenimiento" required />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label"></label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="2" placeholder="Descripción del tipo de colaboración"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="MontoSugerido" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input asp-for="MontoSugerido" type="number" step="0.01" class="form-control" placeholder="1.00" required />
|
||||
</div>
|
||||
<span asp-validation-for="MontoSugerido" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Orden" class="form-label"></label>
|
||||
<input asp-for="Orden" type="number" class="form-control" placeholder="1" required />
|
||||
<span asp-validation-for="Orden" class="text-danger"></span>
|
||||
<small class="form-text text-muted">Orden de aparición en las listas</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" checked />
|
||||
<label asp-for="Activo" class="form-check-label"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Guardar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
65
RS_system/Views/TipoColaboracion/Edit.cshtml
Normal file
65
RS_system/Views/TipoColaboracion/Edit.cshtml
Normal file
@@ -0,0 +1,65 @@
|
||||
@model Rs_system.Models.TipoColaboracion
|
||||
@{
|
||||
ViewData["Title"] = "Editar Tipo de Colaboración";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Editar Tipo de Colaboración</h4>
|
||||
<p class="text-muted mb-0">Modificar tipo de colaboración existente</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<form asp-action="Edit" method="post">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<input type="hidden" asp-for="CreadoEn" />
|
||||
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Nombre" class="form-label"></label>
|
||||
<input asp-for="Nombre" class="form-control" required />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label"></label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="2"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="MontoSugerido" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input asp-for="MontoSugerido" type="number" step="0.01" class="form-control" required />
|
||||
</div>
|
||||
<span asp-validation-for="MontoSugerido" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Orden" class="form-label"></label>
|
||||
<input asp-for="Orden" type="number" class="form-control" required />
|
||||
<span asp-validation-for="Orden" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" />
|
||||
<label asp-for="Activo" class="form-check-label"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Actualizar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
112
RS_system/Views/TipoColaboracion/Index.cshtml
Normal file
112
RS_system/Views/TipoColaboracion/Index.cshtml
Normal file
@@ -0,0 +1,112 @@
|
||||
@model IEnumerable<Rs_system.Models.TipoColaboracion>
|
||||
@{
|
||||
ViewData["Title"] = "Tipos de Colaboración";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Tipos de Colaboración</h4>
|
||||
<p class="text-muted mb-0">Gestión de tipos de colaboración económica</p>
|
||||
</div>
|
||||
<a asp-action="Create" class="btn btn-primary-custom">
|
||||
<i class="bi bi-plus-lg me-1"></i> Nuevo Tipo
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Orden</th>
|
||||
<th>Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th>Monto Sugerido</th>
|
||||
<th class="text-center">Estado</th>
|
||||
<th class="text-center">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted py-4">
|
||||
<i class="bi bi-list-ul fs-1 d-block mb-2"></i>
|
||||
No hay tipos de colaboración registrados
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var tipo in Model)
|
||||
{
|
||||
<tr>
|
||||
<td><strong>@tipo.Orden</strong></td>
|
||||
<td><strong>@tipo.Nombre</strong></td>
|
||||
<td>@(tipo.Descripcion ?? "-")</td>
|
||||
<td><span class="text-success">$@tipo.MontoSugerido.ToString("N2")</span></td>
|
||||
<td class="text-center">
|
||||
@if (tipo.Activo)
|
||||
{
|
||||
<span class="badge bg-success">Activo</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Inactivo</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a asp-action="Edit" asp-route-id="@tipo.Id" class="btn btn-sm btn-outline-secondary" title="Editar">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
@if (tipo.Activo)
|
||||
{
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="confirmDelete(@tipo.Id)" title="Desactivar">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Form -->
|
||||
<form id="deleteForm" asp-action="Delete" method="post" style="display: none;">
|
||||
<input type="hidden" name="id" id="deleteId" />
|
||||
</form>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function confirmDelete(id) {
|
||||
Swal.fire({
|
||||
title: '¿Desactivar tipo?',
|
||||
text: 'Este tipo de colaboración será desactivado.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#d33',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: 'Sí, desactivar',
|
||||
cancelButtonText: 'Cancelar'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('deleteId').value = id;
|
||||
document.getElementById('deleteForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@if (TempData["Success"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.success('@TempData["Success"]');
|
||||
</text>
|
||||
}
|
||||
@if (TempData["Error"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.error('@TempData["Error"]');
|
||||
</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
63
RS_system/Views/Ubicaciones/Create.cshtml
Normal file
63
RS_system/Views/Ubicaciones/Create.cshtml
Normal file
@@ -0,0 +1,63 @@
|
||||
@model Rs_system.Models.Ubicacion
|
||||
@{
|
||||
ViewData["Title"] = "Nueva Ubicación";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Nueva Ubicación</h4>
|
||||
<p class="text-muted mb-0">Registrar un nuevo lugar de almacenamiento</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom" style="max-width: 800px; margin: 0 auto;">
|
||||
<div class="card-body">
|
||||
<form asp-action="Create" method="post">
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Nombre" class="form-label">Nombre <span class="text-danger">*</span></label>
|
||||
<input asp-for="Nombre" class="form-control" placeholder="Ej: Bodega Central, Auditorio, Oficina Pastoral" autofocus />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="2" placeholder="Detalles sobre la ubicación (opcional)"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Responsable" class="form-label">Responsable</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-person"></i></span>
|
||||
<input asp-for="Responsable" class="form-control" placeholder="Nombre de la persona encargada" />
|
||||
</div>
|
||||
<div class="form-text">Persona a cargo de esta ubicación (opcional).</div>
|
||||
<span asp-validation-for="Responsable" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" role="switch" checked />
|
||||
<label asp-for="Activo" class="form-check-label">Ubicación Activa</label>
|
||||
</div>
|
||||
<div class="form-text">Si está inactiva, no se podrá asignar a nuevos artículos.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Guardar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
}
|
||||
64
RS_system/Views/Ubicaciones/Edit.cshtml
Normal file
64
RS_system/Views/Ubicaciones/Edit.cshtml
Normal file
@@ -0,0 +1,64 @@
|
||||
@model Rs_system.Models.Ubicacion
|
||||
@{
|
||||
ViewData["Title"] = "Editar Ubicación";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Editar Ubicación</h4>
|
||||
<p class="text-muted mb-0">Modificar información del lugar</p>
|
||||
</div>
|
||||
<a asp-action="Index" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom" style="max-width: 800px; margin: 0 auto;">
|
||||
<div class="card-body">
|
||||
<form asp-action="Edit" method="post">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Nombre" class="form-label">Nombre <span class="text-danger">*</span></label>
|
||||
<input asp-for="Nombre" class="form-control" placeholder="Ej: Bodega Central, Auditorio" />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Descripcion" class="form-label">Descripción</label>
|
||||
<textarea asp-for="Descripcion" class="form-control" rows="2" placeholder="Detalles sobre la ubicación (opcional)"></textarea>
|
||||
<span asp-validation-for="Descripcion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Responsable" class="form-label">Responsable</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-person"></i></span>
|
||||
<input asp-for="Responsable" class="form-control" placeholder="Nombre de la persona encargada" />
|
||||
</div>
|
||||
<div class="form-text">Persona a cargo de esta ubicación (opcional).</div>
|
||||
<span asp-validation-for="Responsable" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input asp-for="Activo" class="form-check-input" type="checkbox" role="switch" />
|
||||
<label asp-for="Activo" class="form-check-label">Ubicación Activa</label>
|
||||
</div>
|
||||
<div class="form-text">Si está inactiva, no se podrá asignar a nuevos artículos.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="bi bi-save me-1"></i> Guardar Cambios
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
}
|
||||
131
RS_system/Views/Ubicaciones/Index.cshtml
Normal file
131
RS_system/Views/Ubicaciones/Index.cshtml
Normal file
@@ -0,0 +1,131 @@
|
||||
@model IEnumerable<Rs_system.Models.Ubicacion>
|
||||
@{
|
||||
ViewData["Title"] = "Ubicaciones de Inventario";
|
||||
}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Ubicaciones de Inventario</h4>
|
||||
<p class="text-muted mb-0">Gestión de lugares físicos de almacenamiento</p>
|
||||
</div>
|
||||
<a asp-action="Create" class="btn btn-primary-custom">
|
||||
<i class="bi bi-plus-lg me-1"></i> Nueva Ubicación
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card-custom">
|
||||
<div class="table-responsive">
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th>Responsable</th>
|
||||
<th class="text-center">Estado</th>
|
||||
<th class="text-center">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted py-4">
|
||||
<i class="bi bi-geo-alt fs-1 d-block mb-2"></i>
|
||||
No hay ubicaciones registradas
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<strong>@item.Nombre</strong>
|
||||
</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(item.Descripcion))
|
||||
{
|
||||
<span>@item.Descripcion</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">-</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(item.Responsable))
|
||||
{
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-person-circle me-2 text-muted"></i>
|
||||
<span>@item.Responsable</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">-</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@if (item.Activo)
|
||||
{
|
||||
<span class="badge bg-success">Activo</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Inactivo</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-outline-secondary" title="Editar">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="confirmDelete(@item.Id)" title="Eliminar">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Form -->
|
||||
<form id="deleteForm" asp-action="Delete" method="post" style="display: none;">
|
||||
<input type="hidden" name="id" id="deleteId" />
|
||||
</form>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function confirmDelete(id) {
|
||||
Swal.fire({
|
||||
title: '¿Eliminar ubicación?',
|
||||
text: 'Esta acción archivará la ubicación.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#d33',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: 'Sí, eliminar',
|
||||
cancelButtonText: 'Cancelar'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
document.getElementById('deleteId').value = id;
|
||||
document.getElementById('deleteForm').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show success/error messages
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.success('@TempData["SuccessMessage"]');
|
||||
</text>
|
||||
}
|
||||
@if (TempData["ErrorMessage"] != null)
|
||||
{
|
||||
<text>
|
||||
toastr.error('@TempData["ErrorMessage"]');
|
||||
</text>
|
||||
}
|
||||
</script>
|
||||
}
|
||||
Reference in New Issue
Block a user