fixed commit
This commit is contained in:
11
foundation_system/Migrations/Scripts/AddModuleHierarchy.sql
Normal file
11
foundation_system/Migrations/Scripts/AddModuleHierarchy.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- =============================================
|
||||
-- Author: System
|
||||
-- Create date: 2025-12-31
|
||||
-- Description: Adds parent_id to modulos table for hierarchical support.
|
||||
-- =============================================
|
||||
|
||||
ALTER TABLE modulos
|
||||
ADD COLUMN IF NOT EXISTS parent_id INT REFERENCES modulos(id);
|
||||
|
||||
-- Optional: Create an index for performance
|
||||
CREATE INDEX IF NOT EXISTS idx_modulos_parent_id ON modulos(parent_id);
|
||||
33
foundation_system/Models/ViewModels/DashboardViewModel.cs
Normal file
33
foundation_system/Models/ViewModels/DashboardViewModel.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace foundation_system.Models.ViewModels;
|
||||
|
||||
public class DashboardViewModel
|
||||
{
|
||||
// Permission flags
|
||||
public bool TienePermisoExpediente { get; set; }
|
||||
public bool TienePermisoAsistencia { get; set; }
|
||||
public bool TienePermisoCajaChica { get; set; }
|
||||
|
||||
// Expediente stats (visible if TienePermisoExpediente)
|
||||
public int TotalNinosActivos { get; set; }
|
||||
public int CumpleanerosMes { get; set; }
|
||||
public int NinosMayores14 { get; set; }
|
||||
public List<NinoCumpleanero> ListaCumpleaneros { get; set; } = new();
|
||||
|
||||
// Attendance stats (visible if TienePermisoAsistencia)
|
||||
public int AsistenciasHoy { get; set; }
|
||||
public int TotalNinosParaAsistencia { get; set; }
|
||||
public decimal PorcentajeAsistenciaHoy { get; set; }
|
||||
|
||||
// Petty Cash stats (visible if TienePermisoCajaChica)
|
||||
public decimal SaldoCajaChica { get; set; }
|
||||
public decimal GastosMes { get; set; }
|
||||
public int MovimientosMes { get; set; }
|
||||
public string? NombreCajaActiva { get; set; }
|
||||
}
|
||||
|
||||
public class NinoCumpleanero
|
||||
{
|
||||
public string NombreCompleto { get; set; } = string.Empty;
|
||||
public int Edad { get; set; }
|
||||
public int DiaCumple { get; set; }
|
||||
}
|
||||
20
foundation_system/Models/ViewModels/MenuViewModel.cs
Normal file
20
foundation_system/Models/ViewModels/MenuViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace foundation_system.Models.ViewModels
|
||||
{
|
||||
public class MenuViewModel
|
||||
{
|
||||
public List<MenuItem> Items { get; set; } = new List<MenuItem>();
|
||||
}
|
||||
|
||||
public class MenuItem
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string? Icon { get; set; }
|
||||
public string? Url { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsGroup { get; set; } // True if it's a module with children
|
||||
public List<MenuItem> Children { get; set; } = new List<MenuItem>();
|
||||
public int Order { get; set; }
|
||||
}
|
||||
}
|
||||
27
foundation_system/Services/ConfiguracionService.cs
Normal file
27
foundation_system/Services/ConfiguracionService.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using foundation_system.Data;
|
||||
|
||||
namespace foundation_system.Services;
|
||||
|
||||
public class ConfiguracionService : IConfiguracionService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public ConfiguracionService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<string?> GetValorAsync(string clave)
|
||||
{
|
||||
var config = await _context.Configuraciones
|
||||
.FirstOrDefaultAsync(c => c.Clave == clave);
|
||||
return config?.Valor;
|
||||
}
|
||||
|
||||
public async Task<string> GetValorOrDefaultAsync(string clave, string defaultValue)
|
||||
{
|
||||
var valor = await GetValorAsync(clave);
|
||||
return valor ?? defaultValue;
|
||||
}
|
||||
}
|
||||
134
foundation_system/Services/DashboardService.cs
Normal file
134
foundation_system/Services/DashboardService.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using foundation_system.Data;
|
||||
using foundation_system.Models.ViewModels;
|
||||
|
||||
namespace foundation_system.Services;
|
||||
|
||||
public class DashboardService : IDashboardService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public DashboardService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetUserPermissionCodesAsync(long userId)
|
||||
{
|
||||
// Get all permission codes for the user through their roles
|
||||
var permissionCodes = await _context.RolesUsuario
|
||||
.Where(ru => ru.UsuarioId == userId)
|
||||
.Join(_context.RolesPermisos, ru => ru.RolId, rp => rp.RolId, (ru, rp) => rp.PermisoId)
|
||||
.Join(_context.Permisos, permisoId => permisoId, p => p.Id, (permisoId, p) => p.Codigo)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
return permissionCodes;
|
||||
}
|
||||
|
||||
public async Task<bool> UserHasPermissionAsync(long userId, string permisoCodigo)
|
||||
{
|
||||
var codes = await GetUserPermissionCodesAsync(userId);
|
||||
// Check if user has a permission that starts with the given code (e.g., "Expediente" matches "Expediente.Index")
|
||||
return codes.Any(c => c.StartsWith(permisoCodigo, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public async Task<DashboardViewModel> GetDashboardDataAsync(long userId)
|
||||
{
|
||||
var vm = new DashboardViewModel();
|
||||
|
||||
// Check if user is ROOT (has all permissions)
|
||||
var isRoot = await _context.RolesUsuario
|
||||
.Where(ru => ru.UsuarioId == userId)
|
||||
.Join(_context.RolesSistema, ru => ru.RolId, r => r.Id, (ru, r) => r.Codigo)
|
||||
.AnyAsync(codigo => codigo == "ROOT");
|
||||
|
||||
var permissionCodes = await GetUserPermissionCodesAsync(userId);
|
||||
|
||||
// Set permission flags
|
||||
vm.TienePermisoExpediente = isRoot || permissionCodes.Any(c => c.StartsWith("Expediente", StringComparison.OrdinalIgnoreCase));
|
||||
vm.TienePermisoAsistencia = isRoot || permissionCodes.Any(c => c.StartsWith("Asistencia", StringComparison.OrdinalIgnoreCase));
|
||||
vm.TienePermisoCajaChica = isRoot || permissionCodes.Any(c =>
|
||||
c.StartsWith("CajaChica", StringComparison.OrdinalIgnoreCase) ||
|
||||
c.StartsWith("MovimientoCaja", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Expediente Stats
|
||||
if (vm.TienePermisoExpediente)
|
||||
{
|
||||
vm.TotalNinosActivos = await _context.Ninos.CountAsync(n => n.Activo);
|
||||
|
||||
var today = DateTime.Today;
|
||||
var currentMonth = today.Month;
|
||||
|
||||
// Birthday list for current month
|
||||
var cumpleaneros = await _context.Ninos
|
||||
.Where(n => n.Activo)
|
||||
.Include(n => n.Persona)
|
||||
.Where(n => n.Persona.FechaNacimiento != null && n.Persona.FechaNacimiento.Value.Month == currentMonth)
|
||||
.Select(n => new {
|
||||
n.Persona.Nombres,
|
||||
n.Persona.Apellidos,
|
||||
n.Persona.FechaNacimiento
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
vm.CumpleanerosMes = cumpleaneros.Count;
|
||||
vm.ListaCumpleaneros = cumpleaneros.Select(c => new NinoCumpleanero
|
||||
{
|
||||
NombreCompleto = $"{c.Nombres} {c.Apellidos}",
|
||||
Edad = today.Year - c.FechaNacimiento!.Value.Year,
|
||||
DiaCumple = c.FechaNacimiento.Value.Day
|
||||
}).OrderBy(c => c.DiaCumple).ToList();
|
||||
|
||||
// Children older than 14
|
||||
var cutoffDate = DateOnly.FromDateTime(today.AddYears(-14));
|
||||
vm.NinosMayores14 = await _context.Ninos
|
||||
.Where(n => n.Activo)
|
||||
.Include(n => n.Persona)
|
||||
.CountAsync(n => n.Persona.FechaNacimiento != null && n.Persona.FechaNacimiento <= cutoffDate);
|
||||
}
|
||||
|
||||
// Attendance Stats
|
||||
if (vm.TienePermisoAsistencia)
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
vm.TotalNinosParaAsistencia = await _context.Ninos.CountAsync(n => n.Activo);
|
||||
vm.AsistenciasHoy = await _context.Asistencias
|
||||
.CountAsync(a => a.Fecha == today && a.Estado == "PRESENTE");
|
||||
|
||||
vm.PorcentajeAsistenciaHoy = vm.TotalNinosParaAsistencia > 0
|
||||
? Math.Round((decimal)vm.AsistenciasHoy / vm.TotalNinosParaAsistencia * 100, 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
// Petty Cash Stats
|
||||
if (vm.TienePermisoCajaChica)
|
||||
{
|
||||
var cajaActiva = await _context.CajasChicas
|
||||
.Where(c => c.Estado == "ABIERTA")
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (cajaActiva != null)
|
||||
{
|
||||
vm.NombreCajaActiva = cajaActiva.Nombre;
|
||||
vm.SaldoCajaChica = cajaActiva.SaldoActual;
|
||||
|
||||
var inicioMes = new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1);
|
||||
var finMes = inicioMes.AddMonths(1).AddDays(-1);
|
||||
|
||||
var movimientosMes = await _context.CajaChicaMovimientos
|
||||
.Where(m => m.CajaChicaId == cajaActiva.Id &&
|
||||
m.FechaMovimiento >= inicioMes &&
|
||||
m.FechaMovimiento <= finMes)
|
||||
.ToListAsync();
|
||||
|
||||
vm.MovimientosMes = movimientosMes.Count;
|
||||
vm.GastosMes = movimientosMes
|
||||
.Where(m => m.TipoMovimiento == "GASTO")
|
||||
.Sum(m => m.Monto);
|
||||
}
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
}
|
||||
7
foundation_system/Services/IConfiguracionService.cs
Normal file
7
foundation_system/Services/IConfiguracionService.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace foundation_system.Services;
|
||||
|
||||
public interface IConfiguracionService
|
||||
{
|
||||
Task<string?> GetValorAsync(string clave);
|
||||
Task<string> GetValorOrDefaultAsync(string clave, string defaultValue);
|
||||
}
|
||||
10
foundation_system/Services/IDashboardService.cs
Normal file
10
foundation_system/Services/IDashboardService.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using foundation_system.Models.ViewModels;
|
||||
|
||||
namespace foundation_system.Services;
|
||||
|
||||
public interface IDashboardService
|
||||
{
|
||||
Task<DashboardViewModel> GetDashboardDataAsync(long userId);
|
||||
Task<bool> UserHasPermissionAsync(long userId, string permisoCodigo);
|
||||
Task<List<string>> GetUserPermissionCodesAsync(long userId);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
@using foundation_system.Models.ViewModels
|
||||
@model MenuItem
|
||||
|
||||
@{
|
||||
var currentUrl = ViewData["CurrentUrl"] as string ?? "";
|
||||
var collapseId = "menu-" + Guid.NewGuid().ToString("N");
|
||||
|
||||
// Helper function to check active state recursively
|
||||
bool IsItemOrChildActive(MenuItem item, string url)
|
||||
{
|
||||
if (!item.IsGroup && !string.IsNullOrEmpty(item.Url))
|
||||
{
|
||||
return url.StartsWith(item.Url, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
return item.Children.Any(c => IsItemOrChildActive(c, url));
|
||||
}
|
||||
|
||||
bool isExpanded = Model.IsGroup && IsItemOrChildActive(Model, currentUrl);
|
||||
}
|
||||
|
||||
@if (Model.IsGroup)
|
||||
{
|
||||
<div class="nav-section-title mt-3" data-bs-toggle="collapse" data-bs-target="#@collapseId" aria-expanded="@isExpanded.ToString().ToLower()" style="cursor:pointer">
|
||||
@if (!string.IsNullOrEmpty(Model.Icon))
|
||||
{
|
||||
<i class="bi @Model.Icon me-1"></i>
|
||||
}
|
||||
@Model.Title
|
||||
<i class="bi bi-chevron-down float-end small"></i>
|
||||
</div>
|
||||
<div class="collapse @(isExpanded ? "show" : "")" id="@collapseId">
|
||||
<div class="ms-3 border-start ps-2">
|
||||
@foreach (var child in Model.Children)
|
||||
{
|
||||
<partial name="Components/Menu/_MenuItem" model="child" view-data="ViewData" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
var isActive = !string.IsNullOrEmpty(Model.Url) && currentUrl.StartsWith(Model.Url, StringComparison.OrdinalIgnoreCase);
|
||||
<a class="nav-link-custom @(isActive ? "active" : "")" href="@Model.Url">
|
||||
<i class="bi @Model.Icon"></i> @Model.Title
|
||||
</a>
|
||||
}
|
||||
Reference in New Issue
Block a user