add new
This commit is contained in:
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>
|
||||
}
|
||||
Reference in New Issue
Block a user