add new
This commit is contained in:
276
RS_system/Services/ArticuloService.cs
Normal file
276
RS_system/Services/ArticuloService.cs
Normal file
@@ -0,0 +1,276 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rs_system.Data;
|
||||
using Rs_system.Models;
|
||||
using Rs_system.Models.ViewModels;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public class ArticuloService : IArticuloService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IFileStorageService _fileStorageService;
|
||||
|
||||
public ArticuloService(ApplicationDbContext context, IFileStorageService fileStorageService)
|
||||
{
|
||||
_context = context;
|
||||
_fileStorageService = fileStorageService;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ArticuloViewModel>> GetAllAsync(string? search = null, int? categoriaId = null, int? ubicacionId = null, int? estadoId = null)
|
||||
{
|
||||
var query = _context.Articulos
|
||||
.Include(a => a.Categoria)
|
||||
.Include(a => a.Estado)
|
||||
.Include(a => a.Ubicacion)
|
||||
.Where(a => !a.Eliminado)
|
||||
.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var term = search.ToLower();
|
||||
query = query.Where(a =>
|
||||
a.Nombre.ToLower().Contains(term) ||
|
||||
a.Codigo.ToLower().Contains(term) ||
|
||||
a.Modelo.ToLower().Contains(term) ||
|
||||
a.Marca.ToLower().Contains(term) ||
|
||||
a.NumeroSerie.ToLower().Contains(term));
|
||||
}
|
||||
|
||||
if (categoriaId.HasValue)
|
||||
query = query.Where(a => a.CategoriaId == categoriaId);
|
||||
|
||||
if (ubicacionId.HasValue)
|
||||
query = query.Where(a => a.UbicacionId == ubicacionId);
|
||||
|
||||
if (estadoId.HasValue)
|
||||
query = query.Where(a => a.EstadoId == estadoId);
|
||||
|
||||
return await query
|
||||
.OrderByDescending(a => a.CreadoEn)
|
||||
.Select(a => new ArticuloViewModel
|
||||
{
|
||||
Id = a.Id,
|
||||
Codigo = a.Codigo,
|
||||
Nombre = a.Nombre,
|
||||
Descripcion = a.Descripcion,
|
||||
Marca = a.Marca,
|
||||
Modelo = a.Modelo,
|
||||
NumeroSerie = a.NumeroSerie,
|
||||
Precio = a.Precio,
|
||||
FechaAdquisicion = a.FechaAdquisicion,
|
||||
ImagenUrl = a.ImagenUrl,
|
||||
CategoriaId = a.CategoriaId,
|
||||
CategoriaNombre = a.Categoria.Nombre,
|
||||
EstadoId = a.EstadoId,
|
||||
EstadoNombre = a.Estado.Nombre,
|
||||
EstadoColor = a.Estado.Color,
|
||||
UbicacionId = a.UbicacionId,
|
||||
UbicacionNombre = a.Ubicacion.Nombre,
|
||||
Activo = a.Activo
|
||||
})
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<ArticuloViewModel?> GetByIdAsync(int id)
|
||||
{
|
||||
var a = await _context.Articulos
|
||||
.Include(a => a.Categoria)
|
||||
.Include(a => a.Estado)
|
||||
.Include(a => a.Ubicacion)
|
||||
.FirstOrDefaultAsync(x => x.Id == id && !x.Eliminado);
|
||||
|
||||
if (a == null) return null;
|
||||
|
||||
return new ArticuloViewModel
|
||||
{
|
||||
Id = a.Id,
|
||||
Codigo = a.Codigo,
|
||||
Nombre = a.Nombre,
|
||||
Descripcion = a.Descripcion,
|
||||
Marca = a.Marca,
|
||||
Modelo = a.Modelo,
|
||||
NumeroSerie = a.NumeroSerie,
|
||||
Precio = a.Precio,
|
||||
FechaAdquisicion = a.FechaAdquisicion,
|
||||
ImagenUrl = a.ImagenUrl,
|
||||
CategoriaId = a.CategoriaId,
|
||||
CategoriaNombre = a.Categoria.Nombre,
|
||||
EstadoId = a.EstadoId,
|
||||
EstadoNombre = a.Estado.Nombre,
|
||||
EstadoColor = a.Estado.Color,
|
||||
UbicacionId = a.UbicacionId,
|
||||
UbicacionNombre = a.Ubicacion.Nombre,
|
||||
Activo = a.Activo,
|
||||
CantidadGlobal = a.CantidadGlobal,
|
||||
// New Fields
|
||||
TipoControl = a.TipoControl,
|
||||
CantidadInicial = a.CantidadGlobal // Map Global Qty to CantidadInicial for Display
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> CreateAsync(ArticuloViewModel viewModel, string createdBy)
|
||||
{
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
|
||||
try
|
||||
{
|
||||
await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
|
||||
string? imagenUrl = null;
|
||||
if (viewModel.ImagenFile != null)
|
||||
{
|
||||
imagenUrl = await _fileStorageService.SaveFileAsync(viewModel.ImagenFile, "articulos");
|
||||
}
|
||||
|
||||
var articulo = new Articulo
|
||||
{
|
||||
Codigo = viewModel.Codigo,
|
||||
Nombre = viewModel.Nombre,
|
||||
Descripcion = viewModel.Descripcion,
|
||||
Marca = viewModel.Marca,
|
||||
Modelo = viewModel.Modelo,
|
||||
NumeroSerie = viewModel.NumeroSerie,
|
||||
Precio = viewModel.Precio,
|
||||
FechaAdquisicion = viewModel.FechaAdquisicion,
|
||||
ImagenUrl = imagenUrl,
|
||||
CategoriaId = viewModel.CategoriaId,
|
||||
EstadoId = viewModel.EstadoId,
|
||||
UbicacionId = viewModel.UbicacionId,
|
||||
Activo = viewModel.Activo,
|
||||
Eliminado = false,
|
||||
CreadoPor = createdBy,
|
||||
CreadoEn = DateTime.UtcNow,
|
||||
ActualizadoEn = DateTime.UtcNow,
|
||||
// New Fields
|
||||
TipoControl = viewModel.TipoControl ?? nameof(Articulo.TipoControlInventario.UNITARIO),
|
||||
CantidadGlobal = (viewModel.TipoControl == nameof(Articulo.TipoControlInventario.LOTE)) ? viewModel.CantidadInicial : 1
|
||||
};
|
||||
|
||||
_context.Articulos.Add(articulo);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// If LOTE, initialize Existencia
|
||||
if (articulo.TipoControl == nameof(Articulo.TipoControlInventario.LOTE))
|
||||
{
|
||||
var existencia = new Existencia
|
||||
{
|
||||
ArticuloId = articulo.Id,
|
||||
UbicacionId = articulo.UbicacionId,
|
||||
Cantidad = articulo.CantidadGlobal,
|
||||
ActualizadoEn = DateTime.UtcNow
|
||||
};
|
||||
_context.Existencias.Add(existencia);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(ArticuloViewModel viewModel)
|
||||
{
|
||||
try
|
||||
{
|
||||
var articulo = await _context.Articulos.FindAsync(viewModel.Id);
|
||||
if (articulo == null || articulo.Eliminado) return false;
|
||||
|
||||
if (viewModel.ImagenFile != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(articulo.ImagenUrl))
|
||||
{
|
||||
await _fileStorageService.DeleteFileAsync(articulo.ImagenUrl);
|
||||
}
|
||||
articulo.ImagenUrl = await _fileStorageService.SaveFileAsync(viewModel.ImagenFile, "articulos");
|
||||
}
|
||||
|
||||
articulo.Codigo = viewModel.Codigo;
|
||||
articulo.Nombre = viewModel.Nombre;
|
||||
articulo.Descripcion = viewModel.Descripcion;
|
||||
articulo.Marca = viewModel.Marca;
|
||||
articulo.Modelo = viewModel.Modelo;
|
||||
articulo.NumeroSerie = viewModel.NumeroSerie;
|
||||
articulo.Precio = viewModel.Precio;
|
||||
articulo.FechaAdquisicion = viewModel.FechaAdquisicion;
|
||||
articulo.CategoriaId = viewModel.CategoriaId;
|
||||
articulo.EstadoId = viewModel.EstadoId;
|
||||
articulo.UbicacionId = viewModel.UbicacionId;
|
||||
articulo.Activo = viewModel.Activo;
|
||||
articulo.ActualizadoEn = DateTime.UtcNow;
|
||||
|
||||
_context.Articulos.Update(articulo);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var articulo = await _context.Articulos.FindAsync(id);
|
||||
if (articulo == null || articulo.Eliminado) return false;
|
||||
|
||||
articulo.Eliminado = true;
|
||||
articulo.ActualizadoEn = DateTime.UtcNow;
|
||||
|
||||
_context.Articulos.Update(articulo);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsCodigoAsync(string codigo, int? excludeId = null)
|
||||
{
|
||||
var query = _context.Articulos.AsQueryable();
|
||||
if (excludeId.HasValue)
|
||||
{
|
||||
query = query.Where(a => a.Id != excludeId.Value);
|
||||
}
|
||||
return await query.AnyAsync(a => a.Codigo.ToLower() == codigo.ToLower() && !a.Eliminado);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<(int Id, string Nombre)>> GetCategoriasAsync()
|
||||
{
|
||||
return await _context.Categorias
|
||||
.Where(x => x.Activo && !x.Eliminado)
|
||||
.OrderBy(x => x.Nombre)
|
||||
.Select(x => new ValueTuple<int, string>(x.Id, x.Nombre))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<(int Id, string Nombre, string Color)>> GetEstadosAsync()
|
||||
{
|
||||
return await _context.EstadosArticulos
|
||||
.Where(x => x.Activo && !x.Eliminado)
|
||||
.OrderBy(x => x.Nombre)
|
||||
.Select(x => new ValueTuple<int, string, string>(x.Id, x.Nombre, x.Color ?? "secondary"))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<(int Id, string Nombre)>> GetUbicacionesAsync()
|
||||
{
|
||||
return await _context.Ubicaciones
|
||||
.Where(x => x.Activo && !x.Eliminado)
|
||||
.OrderBy(x => x.Nombre)
|
||||
.Select(x => new ValueTuple<int, string>(x.Id, x.Nombre))
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
103
RS_system/Services/CategoriaService.cs
Normal file
103
RS_system/Services/CategoriaService.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rs_system.Data;
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public class CategoriaService : ICategoriaService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public CategoriaService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Categoria>> GetAllAsync()
|
||||
{
|
||||
return await _context.Categorias
|
||||
.Where(c => !c.Eliminado)
|
||||
.OrderBy(c => c.Nombre)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Categoria?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Categorias
|
||||
.FirstOrDefaultAsync(c => c.Id == id && !c.Eliminado);
|
||||
}
|
||||
|
||||
public async Task<bool> CreateAsync(Categoria categoria)
|
||||
{
|
||||
try
|
||||
{
|
||||
categoria.CreadoEn = DateTime.UtcNow;
|
||||
categoria.ActualizadoEn = DateTime.UtcNow;
|
||||
// Eliminado and Activo defaults are set in the model/DB, ensuring here just in case
|
||||
categoria.Eliminado = false;
|
||||
|
||||
_context.Categorias.Add(categoria);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(Categoria categoria)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = await _context.Categorias.FindAsync(categoria.Id);
|
||||
if (existing == null || existing.Eliminado) return false;
|
||||
|
||||
existing.Nombre = categoria.Nombre;
|
||||
existing.Descripcion = categoria.Descripcion;
|
||||
existing.Activo = categoria.Activo;
|
||||
existing.ActualizadoEn = DateTime.UtcNow;
|
||||
// CreadoPor and CreadoEn should not change
|
||||
|
||||
_context.Categorias.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var categoria = await _context.Categorias.FindAsync(id);
|
||||
if (categoria == null || categoria.Eliminado) return false;
|
||||
|
||||
categoria.Eliminado = true;
|
||||
categoria.ActualizadoEn = DateTime.UtcNow;
|
||||
|
||||
_context.Categorias.Update(categoria);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(string nombre, int? excludeId = null)
|
||||
{
|
||||
var query = _context.Categorias.AsQueryable();
|
||||
|
||||
if (excludeId.HasValue)
|
||||
{
|
||||
query = query.Where(c => c.Id != excludeId.Value);
|
||||
}
|
||||
|
||||
return await query.AnyAsync(c => c.Nombre.ToLower() == nombre.ToLower() && !c.Eliminado);
|
||||
}
|
||||
}
|
||||
373
RS_system/Services/ColaboracionService.cs
Normal file
373
RS_system/Services/ColaboracionService.cs
Normal file
@@ -0,0 +1,373 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rs_system.Data;
|
||||
using Rs_system.Models;
|
||||
using Rs_system.Models.ViewModels;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public class ColaboracionService : IColaboracionService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public ColaboracionService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<List<TipoColaboracion>> GetTiposActivosAsync()
|
||||
{
|
||||
return await _context.TiposColaboracion
|
||||
.Where(t => t.Activo)
|
||||
.OrderBy(t => t.Orden)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<TipoColaboracion?> GetTipoByIdAsync(long id)
|
||||
{
|
||||
return await _context.TiposColaboracion
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
}
|
||||
|
||||
public async Task<Colaboracion> RegistrarColaboracionAsync(
|
||||
RegistrarColaboracionViewModel model,
|
||||
string registradoPor)
|
||||
{
|
||||
// Validar que el rango de fechas sea válido
|
||||
var fechaInicial = new DateTime(model.AnioInicial, model.MesInicial, 1);
|
||||
var fechaFinal = new DateTime(model.AnioFinal, model.MesFinal, 1);
|
||||
|
||||
if (fechaFinal < fechaInicial)
|
||||
{
|
||||
throw new ArgumentException("La fecha final no puede ser anterior a la fecha inicial");
|
||||
}
|
||||
|
||||
// Obtener información de los tipos seleccionados
|
||||
var tiposColaboracion = await _context.TiposColaboracion
|
||||
.Where(t => model.TiposSeleccionados.Contains(t.Id))
|
||||
.ToListAsync();
|
||||
|
||||
// Generar todos los meses en el rango
|
||||
var mesesAPagar = GenerarRangoMeses(
|
||||
model.AnioInicial, model.MesInicial,
|
||||
model.AnioFinal, model.MesFinal);
|
||||
|
||||
// Crear colaboración principal
|
||||
var colaboracion = new Colaboracion
|
||||
{
|
||||
MiembroId = model.MiembroId,
|
||||
FechaRegistro = DateTime.UtcNow,
|
||||
MontoTotal = model.MontoTotal,
|
||||
Observaciones = model.Observaciones,
|
||||
RegistradoPor = registradoPor,
|
||||
CreadoEn = DateTime.UtcNow,
|
||||
ActualizadoEn = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// Distribuir el monto total entre los meses y tipos
|
||||
var detalles = DistribuirMonto(
|
||||
model.MontoTotal,
|
||||
tiposColaboracion,
|
||||
mesesAPagar,
|
||||
model.TipoPrioritario);
|
||||
|
||||
foreach (var detalle in detalles)
|
||||
{
|
||||
colaboracion.Detalles.Add(detalle);
|
||||
}
|
||||
|
||||
_context.Colaboraciones.Add(colaboracion);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return colaboracion;
|
||||
}
|
||||
|
||||
private List<DetalleColaboracion> DistribuirMonto(
|
||||
decimal montoTotal,
|
||||
List<TipoColaboracion> tipos,
|
||||
List<(int anio, int mes)> meses,
|
||||
long? tipoPrioritario)
|
||||
{
|
||||
var detalles = new List<DetalleColaboracion>();
|
||||
var montoRestante = montoTotal;
|
||||
|
||||
// Estrategia: Mes a Mes
|
||||
// Para cada mes, intentamos cubrir los tipos (Prioritario primero)
|
||||
|
||||
foreach (var (anio, mes) in meses)
|
||||
{
|
||||
if (montoRestante <= 0) break;
|
||||
|
||||
// Ordenar tipos para este mes: Prioritario al inicio
|
||||
var tiposOrdenados = new List<TipoColaboracion>();
|
||||
|
||||
if (tipoPrioritario.HasValue)
|
||||
{
|
||||
var prio = tipos.FirstOrDefault(t => t.Id == tipoPrioritario.Value);
|
||||
if (prio != null)
|
||||
{
|
||||
tiposOrdenados.Add(prio);
|
||||
tiposOrdenados.AddRange(tipos.Where(t => t.Id != tipoPrioritario.Value));
|
||||
}
|
||||
else
|
||||
{
|
||||
tiposOrdenados.AddRange(tipos);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tiposOrdenados.AddRange(tipos);
|
||||
}
|
||||
|
||||
foreach (var tipo in tiposOrdenados)
|
||||
{
|
||||
if (montoRestante <= 0) break;
|
||||
|
||||
// Determinar cuánto asignar
|
||||
// Intentamos cubrir el monto sugerido completo
|
||||
var montoAAsignar = Math.Min(tipo.MontoSugerido, montoRestante);
|
||||
|
||||
// Si es un monto muy pequeño (ej: residuo), igual lo asignamos para no perderlo,
|
||||
// salvo que queramos reglas estrictas de "solo completos".
|
||||
// Por ahora asignamos lo que haya.
|
||||
|
||||
if (montoAAsignar > 0)
|
||||
{
|
||||
detalles.Add(new DetalleColaboracion
|
||||
{
|
||||
TipoColaboracionId = tipo.Id,
|
||||
Mes = mes,
|
||||
Anio = anio,
|
||||
Monto = montoAAsignar,
|
||||
CreadoEn = DateTime.UtcNow
|
||||
});
|
||||
|
||||
montoRestante -= montoAAsignar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return detalles;
|
||||
}
|
||||
|
||||
public async Task<List<UltimoPagoViewModel>> GetUltimosPagosPorMiembroAsync(long miembroId)
|
||||
{
|
||||
// Obtener todos los detalles agrupados por tipo para encontrar la fecha máxima
|
||||
var detalles = await _context.DetalleColaboraciones
|
||||
.Include(d => d.Colaboracion)
|
||||
.Include(d => d.TipoColaboracion)
|
||||
.Where(d => d.Colaboracion.MiembroId == miembroId)
|
||||
.ToListAsync();
|
||||
|
||||
var resultado = detalles
|
||||
.GroupBy(d => d.TipoColaboracion)
|
||||
.Select(g =>
|
||||
{
|
||||
// Encontrar el registro con el mes/año más reciente
|
||||
var ultimo = g.OrderByDescending(d => d.Anio).ThenByDescending(d => d.Mes).FirstOrDefault();
|
||||
if (ultimo == null) return null;
|
||||
|
||||
return new UltimoPagoViewModel
|
||||
{
|
||||
TipoId = g.Key.Id,
|
||||
NombreTipo = g.Key.Nombre,
|
||||
UltimoMes = ultimo.Mes,
|
||||
UltimoAnio = ultimo.Anio,
|
||||
FechaUltimoPago = ultimo.Colaboracion.FechaRegistro
|
||||
};
|
||||
})
|
||||
.Where(x => x != null)
|
||||
.ToList();
|
||||
|
||||
// Asegurar que retornamos todos los tipos activos, incluso si no tienen pagos
|
||||
var tiposActivos = await GetTiposActivosAsync();
|
||||
var listaFinal = new List<UltimoPagoViewModel>();
|
||||
|
||||
foreach (var tipo in tiposActivos)
|
||||
{
|
||||
var pago = resultado.FirstOrDefault(r => r.TipoId == tipo.Id);
|
||||
if (pago != null)
|
||||
{
|
||||
listaFinal.Add(pago);
|
||||
}
|
||||
else
|
||||
{
|
||||
listaFinal.Add(new UltimoPagoViewModel
|
||||
{
|
||||
TipoId = tipo.Id,
|
||||
NombreTipo = tipo.Nombre,
|
||||
UltimoMes = 0, // No hay pagos
|
||||
UltimoAnio = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return listaFinal;
|
||||
}
|
||||
|
||||
private List<(int anio, int mes)> GenerarRangoMeses(
|
||||
int anioInicial, int mesInicial,
|
||||
int anioFinal, int mesFinal)
|
||||
{
|
||||
var meses = new List<(int, int)>();
|
||||
var fecha = new DateTime(anioInicial, mesInicial, 1);
|
||||
var fechaFin = new DateTime(anioFinal, mesFinal, 1);
|
||||
|
||||
while (fecha <= fechaFin)
|
||||
{
|
||||
meses.Add((fecha.Year, fecha.Month));
|
||||
fecha = fecha.AddMonths(1);
|
||||
}
|
||||
|
||||
return meses;
|
||||
}
|
||||
|
||||
public async Task<List<Colaboracion>> GetColaboracionesRecientesAsync(int cantidad = 50)
|
||||
{
|
||||
return await _context.Colaboraciones
|
||||
.Include(c => c.Miembro)
|
||||
.ThenInclude(m => m.Persona)
|
||||
.Include(c => c.Detalles)
|
||||
.ThenInclude(d => d.TipoColaboracion)
|
||||
.OrderByDescending(c => c.FechaRegistro)
|
||||
.Take(cantidad)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Colaboracion?> GetColaboracionByIdAsync(long id)
|
||||
{
|
||||
return await _context.Colaboraciones
|
||||
.Include(c => c.Miembro)
|
||||
.ThenInclude(m => m.Persona)
|
||||
.Include(c => c.Detalles)
|
||||
.ThenInclude(d => d.TipoColaboracion)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(c => c.Id == id);
|
||||
}
|
||||
|
||||
public async Task<ReporteColaboracionesViewModel> GenerarReportePorFechasAsync(
|
||||
DateTime fechaInicio,
|
||||
DateTime fechaFin)
|
||||
{
|
||||
var colaboraciones = await _context.Colaboraciones
|
||||
.Include(c => c.Miembro)
|
||||
.ThenInclude(m => m.Persona)
|
||||
.Include(c => c.Detalles)
|
||||
.ThenInclude(d => d.TipoColaboracion)
|
||||
.Where(c => c.FechaRegistro >= fechaInicio && c.FechaRegistro <= fechaFin)
|
||||
.OrderByDescending(c => c.FechaRegistro)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
var reporte = new ReporteColaboracionesViewModel
|
||||
{
|
||||
FechaInicio = fechaInicio,
|
||||
FechaFin = fechaFin,
|
||||
TotalRecaudado = colaboraciones.Sum(c => c.MontoTotal)
|
||||
};
|
||||
|
||||
// Desglose por tipo
|
||||
var desglosePorTipo = colaboraciones
|
||||
.SelectMany(c => c.Detalles)
|
||||
.GroupBy(d => d.TipoColaboracion.Nombre)
|
||||
.Select(g => new DesglosePorTipo
|
||||
{
|
||||
TipoNombre = g.Key,
|
||||
CantidadMeses = g.Count(),
|
||||
TotalRecaudado = g.Sum(d => d.Monto)
|
||||
})
|
||||
.OrderBy(d => d.TipoNombre)
|
||||
.ToList();
|
||||
|
||||
reporte.DesglosePorTipos = desglosePorTipo;
|
||||
|
||||
// Detalle de movimientos
|
||||
var movimientos = colaboraciones.Select(c => new DetalleMovimiento
|
||||
{
|
||||
ColaboracionId = c.Id,
|
||||
Fecha = c.FechaRegistro,
|
||||
NombreMiembro = $"{c.Miembro.Persona.Nombres} {c.Miembro.Persona.Apellidos}",
|
||||
TiposColaboracion = string.Join(", ", c.Detalles.Select(d => d.TipoColaboracion.Nombre).Distinct()),
|
||||
PeriodoCubierto = ObtenerPeriodoCubierto(c.Detalles.ToList()),
|
||||
Monto = c.MontoTotal
|
||||
}).ToList();
|
||||
|
||||
reporte.Movimientos = movimientos;
|
||||
|
||||
return reporte;
|
||||
}
|
||||
|
||||
private string ObtenerPeriodoCubierto(List<DetalleColaboracion> detalles)
|
||||
{
|
||||
if (!detalles.Any()) return "";
|
||||
|
||||
var ordenados = detalles.OrderBy(d => d.Anio).ThenBy(d => d.Mes).ToList();
|
||||
var primero = ordenados.First();
|
||||
var ultimo = ordenados.Last();
|
||||
|
||||
var cultura = new CultureInfo("es-ES");
|
||||
|
||||
if (primero.Anio == ultimo.Anio && primero.Mes == ultimo.Mes)
|
||||
{
|
||||
return new DateTime(primero.Anio, primero.Mes, 1).ToString("MMMM yyyy", cultura);
|
||||
}
|
||||
|
||||
return $"{new DateTime(primero.Anio, primero.Mes, 1).ToString("MMM yyyy", cultura)} - " +
|
||||
$"{new DateTime(ultimo.Anio, ultimo.Mes, 1).ToString("MMM yyyy", cultura)}";
|
||||
}
|
||||
|
||||
public async Task<EstadoCuentaViewModel> GenerarEstadoCuentaAsync(long miembroId)
|
||||
{
|
||||
var miembro = await _context.Miembros
|
||||
.Include(m => m.Persona)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(m => m.Id == miembroId);
|
||||
|
||||
if (miembro == null)
|
||||
throw new Exception("Miembro no encontrado");
|
||||
|
||||
var colaboraciones = await _context.Colaboraciones
|
||||
.Include(c => c.Detalles)
|
||||
.ThenInclude(d => d.TipoColaboracion)
|
||||
.Where(c => c.MiembroId == miembroId)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
var estado = new EstadoCuentaViewModel
|
||||
{
|
||||
MiembroId = miembroId,
|
||||
NombreMiembro = $"{miembro.Persona.Nombres} {miembro.Persona.Apellidos}",
|
||||
FechaConsulta = DateTime.Now,
|
||||
TotalAportado = colaboraciones.Sum(c => c.MontoTotal)
|
||||
};
|
||||
|
||||
// Agrupar por tipo
|
||||
var historialPorTipo = colaboraciones
|
||||
.SelectMany(c => c.Detalles.Select(d => new { Detalle = d, FechaRegistro = c.FechaRegistro }))
|
||||
.GroupBy(x => x.Detalle.TipoColaboracion.Nombre)
|
||||
.Select(g => new HistorialPorTipo
|
||||
{
|
||||
TipoNombre = g.Key,
|
||||
TotalTipo = g.Sum(x => x.Detalle.Monto),
|
||||
Registros = g.Select(x => new RegistroMensual
|
||||
{
|
||||
Mes = x.Detalle.Mes,
|
||||
Anio = x.Detalle.Anio,
|
||||
Monto = x.Detalle.Monto,
|
||||
FechaRegistro = x.FechaRegistro
|
||||
})
|
||||
.OrderBy(r => r.Anio)
|
||||
.ThenBy(r => r.Mes)
|
||||
.ToList()
|
||||
})
|
||||
.OrderBy(h => h.TipoNombre)
|
||||
.ToList();
|
||||
|
||||
estado.HistorialPorTipos = historialPorTipo;
|
||||
|
||||
return estado;
|
||||
}
|
||||
}
|
||||
337
RS_system/Services/ContabilidadGeneralService.cs
Normal file
337
RS_system/Services/ContabilidadGeneralService.cs
Normal file
@@ -0,0 +1,337 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rs_system.Data;
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public class ContabilidadGeneralService : IContabilidadGeneralService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public ContabilidadGeneralService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// ==================== Categorías de Ingreso ====================
|
||||
|
||||
public async Task<List<CategoriaIngreso>> ObtenerCategoriasIngresoAsync()
|
||||
{
|
||||
return await _context.CategoriasIngreso
|
||||
.Where(c => c.Activa)
|
||||
.OrderBy(c => c.Nombre)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<CategoriaIngreso?> ObtenerCategoriaIngresoPorIdAsync(long id)
|
||||
{
|
||||
return await _context.CategoriasIngreso.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<CategoriaIngreso> CrearCategoriaIngresoAsync(CategoriaIngreso categoria)
|
||||
{
|
||||
categoria.FechaCreacion = DateTime.UtcNow;
|
||||
_context.CategoriasIngreso.Add(categoria);
|
||||
await _context.SaveChangesAsync();
|
||||
return categoria;
|
||||
}
|
||||
|
||||
public async Task<bool> ActualizarCategoriaIngresoAsync(CategoriaIngreso categoria)
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.CategoriasIngreso.Update(categoria);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> EliminarCategoriaIngresoAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var categoria = await _context.CategoriasIngreso.FindAsync(id);
|
||||
if (categoria == null) return false;
|
||||
|
||||
// Soft delete - marcar como inactiva en lugar de eliminar
|
||||
categoria.Activa = false;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Categorías de Egreso ====================
|
||||
|
||||
public async Task<List<CategoriaEgreso>> ObtenerCategoriasEgresoAsync()
|
||||
{
|
||||
return await _context.CategoriasEgreso
|
||||
.Where(c => c.Activa)
|
||||
.OrderBy(c => c.Nombre)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<CategoriaEgreso?> ObtenerCategoriaEgresoPorIdAsync(long id)
|
||||
{
|
||||
return await _context.CategoriasEgreso.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<CategoriaEgreso> CrearCategoriaEgresoAsync(CategoriaEgreso categoria)
|
||||
{
|
||||
categoria.FechaCreacion = DateTime.UtcNow;
|
||||
_context.CategoriasEgreso.Add(categoria);
|
||||
await _context.SaveChangesAsync();
|
||||
return categoria;
|
||||
}
|
||||
|
||||
public async Task<bool> ActualizarCategoriaEgresoAsync(CategoriaEgreso categoria)
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.CategoriasEgreso.Update(categoria);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> EliminarCategoriaEgresoAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var categoria = await _context.CategoriasEgreso.FindAsync(id);
|
||||
if (categoria == null) return false;
|
||||
|
||||
// Soft delete - marcar como inactiva en lugar de eliminar
|
||||
categoria.Activa = false;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Reportes Mensuales ====================
|
||||
|
||||
public async Task<ReporteMensualGeneral?> ObtenerReporteMensualAsync(int mes, int anio)
|
||||
{
|
||||
return await _context.ReportesMensualesGenerales
|
||||
.Include(r => r.Movimientos)
|
||||
.ThenInclude(m => m.CategoriaIngreso)
|
||||
.Include(r => r.Movimientos)
|
||||
.ThenInclude(m => m.CategoriaEgreso)
|
||||
.FirstOrDefaultAsync(r => r.Mes == mes && r.Anio == anio);
|
||||
}
|
||||
|
||||
public async Task<ReporteMensualGeneral> ObtenerOCrearReporteMensualAsync(int mes, int anio)
|
||||
{
|
||||
var reporteExistente = await ObtenerReporteMensualAsync(mes, anio);
|
||||
if (reporteExistente != null)
|
||||
return reporteExistente;
|
||||
|
||||
// Obtener el saldo final del mes anterior
|
||||
var mesAnterior = mes == 1 ? 12 : mes - 1;
|
||||
var anioAnterior = mes == 1 ? anio - 1 : anio;
|
||||
|
||||
var reporteAnterior = await ObtenerReporteMensualAsync(mesAnterior, anioAnterior);
|
||||
var saldoInicial = reporteAnterior != null
|
||||
? await CalcularSaldoActualAsync(reporteAnterior.Id)
|
||||
: 0;
|
||||
|
||||
var nuevoReporte = new ReporteMensualGeneral
|
||||
{
|
||||
Mes = mes,
|
||||
Anio = anio,
|
||||
SaldoInicial = saldoInicial,
|
||||
FechaCreacion = DateTime.UtcNow,
|
||||
Cerrado = false
|
||||
};
|
||||
|
||||
_context.ReportesMensualesGenerales.Add(nuevoReporte);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return nuevoReporte;
|
||||
}
|
||||
|
||||
public async Task<List<ReporteMensualGeneral>> ListarReportesAsync(int? anio = null)
|
||||
{
|
||||
var query = _context.ReportesMensualesGenerales.AsQueryable();
|
||||
|
||||
if (anio.HasValue)
|
||||
query = query.Where(r => r.Anio == anio.Value);
|
||||
|
||||
return await query
|
||||
.OrderByDescending(r => r.Anio)
|
||||
.ThenByDescending(r => r.Mes)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> CerrarReporteAsync(long reporteId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reporte = await _context.ReportesMensualesGenerales.FindAsync(reporteId);
|
||||
if (reporte == null) return false;
|
||||
|
||||
_context.Entry(reporte).Property(x => x.Cerrado).CurrentValue = true;
|
||||
_context.Entry(reporte).Property(x => x.Cerrado).IsModified = true;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Movimientos ====================
|
||||
|
||||
public async Task<bool> GuardarMovimientosBulkAsync(long reporteId, List<MovimientoGeneral> movimientos)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reporte = await _context.ReportesMensualesGenerales.FindAsync(reporteId);
|
||||
if (reporte == null || reporte.Cerrado)
|
||||
return false;
|
||||
|
||||
foreach (var movimiento in movimientos)
|
||||
{
|
||||
movimiento.ReporteMensualGeneralId = reporteId;
|
||||
movimiento.Fecha = DateTime.SpecifyKind(movimiento.Fecha, DateTimeKind.Utc);
|
||||
|
||||
if (movimiento.Id > 0)
|
||||
{
|
||||
// Update existing
|
||||
var existente = await _context.MovimientosGenerales.FindAsync(movimiento.Id);
|
||||
if (existente != null)
|
||||
{
|
||||
existente.Tipo = movimiento.Tipo;
|
||||
existente.CategoriaIngresoId = movimiento.CategoriaIngresoId;
|
||||
existente.CategoriaEgresoId = movimiento.CategoriaEgresoId;
|
||||
existente.Monto = movimiento.Monto;
|
||||
existente.Fecha = movimiento.Fecha;
|
||||
existente.Descripcion = movimiento.Descripcion;
|
||||
existente.NumeroComprobante = movimiento.NumeroComprobante;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert new
|
||||
_context.MovimientosGenerales.Add(movimiento);
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<decimal> CalcularSaldoActualAsync(long reporteId)
|
||||
{
|
||||
var reporte = await _context.ReportesMensualesGenerales
|
||||
.Include(r => r.Movimientos)
|
||||
.FirstOrDefaultAsync(r => r.Id == reporteId);
|
||||
|
||||
if (reporte == null) return 0;
|
||||
|
||||
var totalIngresos = reporte.Movimientos
|
||||
.Where(m => m.Tipo == (int) TipoMovimientoGeneral.Ingreso)
|
||||
.Sum(m => m.Monto);
|
||||
|
||||
var totalEgresos = reporte.Movimientos
|
||||
.Where(m => m.Tipo == (int)TipoMovimientoGeneral.Egreso)
|
||||
.Sum(m => m.Monto);
|
||||
|
||||
return reporte.SaldoInicial + totalIngresos - totalEgresos;
|
||||
}
|
||||
|
||||
// ==================== Consolidados ====================
|
||||
|
||||
public async Task<Dictionary<string, decimal>> ObtenerConsolidadoIngresosAsync(long reporteId)
|
||||
{
|
||||
var movimientos = await _context.MovimientosGenerales
|
||||
.Include(m => m.CategoriaIngreso)
|
||||
.Where(m => m.ReporteMensualGeneralId == reporteId
|
||||
&& m.Tipo == (int)TipoMovimientoGeneral.Ingreso)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
return movimientos
|
||||
.GroupBy(m => m.CategoriaIngreso?.Nombre ?? "Sin Categoría")
|
||||
.ToDictionary(g => g.Key, g => g.Sum(m => m.Monto));
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, decimal>> ObtenerConsolidadoEgresosAsync(long reporteId)
|
||||
{
|
||||
var movimientos = await _context.MovimientosGenerales
|
||||
.Include(m => m.CategoriaEgreso)
|
||||
.Where(m => m.ReporteMensualGeneralId == reporteId
|
||||
&& m.Tipo == (int)TipoMovimientoGeneral.Egreso)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
return movimientos
|
||||
.GroupBy(m => m.CategoriaEgreso?.Nombre ?? "Sin Categoría")
|
||||
.ToDictionary(g => g.Key, g => g.Sum(m => m.Monto));
|
||||
}
|
||||
|
||||
// ==================== Adjuntos ====================
|
||||
|
||||
public async Task<List<MovimientoGeneralAdjunto>> ObtenerAdjuntosMovimientoAsync(long movimientoId)
|
||||
{
|
||||
return await _context.MovimientosGeneralesAdjuntos
|
||||
.Where(a => a.MovimientoGeneralId == movimientoId)
|
||||
.OrderByDescending(a => a.FechaSubida)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<MovimientoGeneralAdjunto?> CrearAdjuntoAsync(long movimientoId, string nombreArchivo, string rutaArchivo, string tipoContenido)
|
||||
{
|
||||
var movimiento = await _context.MovimientosGenerales.FindAsync(movimientoId);
|
||||
if (movimiento == null) return null;
|
||||
|
||||
var adjunto = new MovimientoGeneralAdjunto
|
||||
{
|
||||
MovimientoGeneralId = movimientoId,
|
||||
NombreArchivo = nombreArchivo,
|
||||
RutaArchivo = rutaArchivo,
|
||||
TipoContenido = tipoContenido,
|
||||
FechaSubida = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.MovimientosGeneralesAdjuntos.Add(adjunto);
|
||||
await _context.SaveChangesAsync();
|
||||
return adjunto;
|
||||
}
|
||||
|
||||
public async Task<bool> EliminarAdjuntoAsync(long adjuntoId)
|
||||
{
|
||||
var adjunto = await _context.MovimientosGeneralesAdjuntos.FindAsync(adjuntoId);
|
||||
if (adjunto == null) return false;
|
||||
|
||||
_context.MovimientosGeneralesAdjuntos.Remove(adjunto);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
169
RS_system/Services/ContabilidadService.cs
Normal file
169
RS_system/Services/ContabilidadService.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rs_system.Data;
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public class ContabilidadService : IContabilidadService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public ContabilidadService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ContabilidadRegistro> CrearRegistroAsync(ContabilidadRegistro registro)
|
||||
{
|
||||
// Ensure Group exists
|
||||
var groupExists = await _context.GruposTrabajo.AnyAsync(g => g.Id == registro.GrupoTrabajoId);
|
||||
if (!groupExists)
|
||||
{
|
||||
throw new ArgumentException($"Grupo de trabajo con ID {registro.GrupoTrabajoId} no existe.");
|
||||
}
|
||||
|
||||
_context.ContabilidadRegistros.Add(registro);
|
||||
await _context.SaveChangesAsync();
|
||||
return registro;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ContabilidadRegistro>> ObtenerRegistrosAsync(long grupoId, DateTime desde, DateTime hasta)
|
||||
{
|
||||
return await _context.ContabilidadRegistros
|
||||
.Include(c => c.GrupoTrabajo)
|
||||
.Where(c => c.GrupoTrabajoId == grupoId && c.Fecha.Date >= desde.Date && c.Fecha.Date <= hasta.Date)
|
||||
.OrderByDescending(c => c.Fecha)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<ReporteMensualContable?> ObtenerReporteMensualAsync(long grupoId, int mes, int anio)
|
||||
{
|
||||
return await _context.ReportesMensualesContables
|
||||
.Include(r => r.Registros)
|
||||
.FirstOrDefaultAsync(r => r.GrupoTrabajoId == grupoId && r.Mes == mes && r.Anio == anio);
|
||||
}
|
||||
|
||||
public async Task<ReporteMensualContable> ObtenerOCrearReporteMensualAsync(long grupoId, int mes, int anio)
|
||||
{
|
||||
var reporte = await ObtenerReporteMensualAsync(grupoId, mes, anio);
|
||||
if (reporte != null) return reporte;
|
||||
|
||||
// Calculate Saldo Inicial based on previous month
|
||||
decimal saldoInicial = 0;
|
||||
var prevMes = mes == 1 ? 12 : mes - 1;
|
||||
var prevAnio = mes == 1 ? anio - 1 : anio;
|
||||
|
||||
var reportePrevio = await ObtenerReporteMensualAsync(grupoId, prevMes, prevAnio);
|
||||
if (reportePrevio != null)
|
||||
{
|
||||
saldoInicial = await CalcularSaldoActualAsync(reportePrevio.Id);
|
||||
}
|
||||
|
||||
reporte = new ReporteMensualContable
|
||||
{
|
||||
GrupoTrabajoId = grupoId,
|
||||
Mes = mes,
|
||||
Anio = anio,
|
||||
SaldoInicial = saldoInicial,
|
||||
FechaCreacion = DateTime.UtcNow,
|
||||
Cerrado = false
|
||||
};
|
||||
|
||||
_context.ReportesMensualesContables.Add(reporte);
|
||||
await _context.SaveChangesAsync();
|
||||
return reporte;
|
||||
}
|
||||
|
||||
public async Task<List<ReporteMensualContable>> ListarReportesPorGrupoAsync(long grupoId)
|
||||
{
|
||||
return await _context.ReportesMensualesContables
|
||||
.Where(r => r.GrupoTrabajoId == grupoId)
|
||||
.OrderByDescending(r => r.Anio)
|
||||
.ThenByDescending(r => r.Mes)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> GuardarRegistrosBulkAsync(long reporteId, List<ContabilidadRegistro> registros)
|
||||
{
|
||||
var reporte = await _context.ReportesMensualesContables
|
||||
.Include(r => r.Registros)
|
||||
.FirstOrDefaultAsync(r => r.Id == reporteId);
|
||||
|
||||
if (reporte == null || reporte.Cerrado) return false;
|
||||
try
|
||||
{
|
||||
// Remove existing records for this report (or handle updates carefully)
|
||||
// For a simple bulk entry system, we might replace all or upsert by ID.
|
||||
// Let's go with UPSERT based on ID.
|
||||
|
||||
var existingIds = reporte.Registros.Select(r => r.Id).ToList();
|
||||
var incomingIds = registros.Where(r => r.Id > 0).Select(r => r.Id).ToList();
|
||||
|
||||
// Delete records that are no longer in the list
|
||||
var toDelete = reporte.Registros.Where(r => !incomingIds.Contains(r.Id)).ToList();
|
||||
_context.ContabilidadRegistros.RemoveRange(toDelete);
|
||||
|
||||
foreach (var registro in registros)
|
||||
{
|
||||
if (registro.Id > 0)
|
||||
{
|
||||
// Update
|
||||
var existing = reporte.Registros.FirstOrDefault(r => r.Id == registro.Id);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Tipo = registro.Tipo;
|
||||
existing.Monto = registro.Monto;
|
||||
existing.Fecha = registro.Fecha;
|
||||
existing.Descripcion = registro.Descripcion;
|
||||
_context.Entry(existing).State = EntityState.Modified;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add
|
||||
registro.ReporteMensualId = reporteId;
|
||||
registro.GrupoTrabajoId = reporte.GrupoTrabajoId;
|
||||
_context.ContabilidadRegistros.Add(registro);
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<decimal> CalcularSaldoActualAsync(long reporteId)
|
||||
{
|
||||
var reporte = await _context.ReportesMensualesContables
|
||||
.Include(r => r.Registros)
|
||||
.FirstOrDefaultAsync(r => r.Id == reporteId);
|
||||
|
||||
if (reporte == null) return 0;
|
||||
|
||||
decimal ingresos = reporte.Registros
|
||||
.Where(r => r.Tipo == TipoMovimientoContable.Ingreso)
|
||||
.Sum(r => r.Monto);
|
||||
|
||||
decimal egresos = reporte.Registros
|
||||
.Where(r => r.Tipo == TipoMovimientoContable.Egreso)
|
||||
.Sum(r => r.Monto);
|
||||
|
||||
return reporte.SaldoInicial + ingresos - egresos;
|
||||
}
|
||||
|
||||
public async Task<bool> CerrarReporteAsync(long reporteId)
|
||||
{
|
||||
var reporte = await _context.ReportesMensualesContables.FindAsync(reporteId);
|
||||
if (reporte == null || reporte.Cerrado) return false;
|
||||
|
||||
reporte.Cerrado = true;
|
||||
_context.ReportesMensualesContables.Update(reporte);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
102
RS_system/Services/EstadoArticuloService.cs
Normal file
102
RS_system/Services/EstadoArticuloService.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rs_system.Data;
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public class EstadoArticuloService : IEstadoArticuloService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public EstadoArticuloService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EstadoArticulo>> GetAllAsync()
|
||||
{
|
||||
return await _context.EstadosArticulos
|
||||
.Where(e => !e.Eliminado)
|
||||
.OrderBy(e => e.Nombre)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<EstadoArticulo?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.EstadosArticulos
|
||||
.FirstOrDefaultAsync(e => e.Id == id && !e.Eliminado);
|
||||
}
|
||||
|
||||
public async Task<bool> CreateAsync(EstadoArticulo estado)
|
||||
{
|
||||
try
|
||||
{
|
||||
estado.CreadoEn = DateTime.UtcNow;
|
||||
estado.ActualizadoEn = DateTime.UtcNow;
|
||||
estado.Eliminado = false;
|
||||
|
||||
_context.EstadosArticulos.Add(estado);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(EstadoArticulo estado)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = await _context.EstadosArticulos.FindAsync(estado.Id);
|
||||
if (existing == null || existing.Eliminado) return false;
|
||||
|
||||
existing.Nombre = estado.Nombre;
|
||||
existing.Descripcion = estado.Descripcion;
|
||||
existing.Color = estado.Color;
|
||||
existing.Activo = estado.Activo;
|
||||
existing.ActualizadoEn = DateTime.UtcNow;
|
||||
|
||||
_context.EstadosArticulos.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var estado = await _context.EstadosArticulos.FindAsync(id);
|
||||
if (estado == null || estado.Eliminado) return false;
|
||||
|
||||
estado.Eliminado = true;
|
||||
estado.ActualizadoEn = DateTime.UtcNow;
|
||||
|
||||
_context.EstadosArticulos.Update(estado);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(string nombre, int? excludeId = null)
|
||||
{
|
||||
var query = _context.EstadosArticulos.AsQueryable();
|
||||
|
||||
if (excludeId.HasValue)
|
||||
{
|
||||
query = query.Where(e => e.Id != excludeId.Value);
|
||||
}
|
||||
|
||||
return await query.AnyAsync(e => e.Nombre.ToLower() == nombre.ToLower() && !e.Eliminado);
|
||||
}
|
||||
}
|
||||
18
RS_system/Services/IArticuloService.cs
Normal file
18
RS_system/Services/IArticuloService.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Rs_system.Models.ViewModels;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public interface IArticuloService
|
||||
{
|
||||
Task<IEnumerable<ArticuloViewModel>> GetAllAsync(string? search = null, int? categoriaId = null, int? ubicacionId = null, int? estadoId = null);
|
||||
Task<ArticuloViewModel?> GetByIdAsync(int id);
|
||||
Task<bool> CreateAsync(ArticuloViewModel viewModel, string createdBy);
|
||||
Task<bool> UpdateAsync(ArticuloViewModel viewModel);
|
||||
Task<bool> DeleteAsync(int id);
|
||||
Task<bool> ExistsCodigoAsync(string codigo, int? excludeId = null);
|
||||
|
||||
// Dropdown helpers
|
||||
Task<IEnumerable<(int Id, string Nombre)>> GetCategoriasAsync();
|
||||
Task<IEnumerable<(int Id, string Nombre, string Color)>> GetEstadosAsync();
|
||||
Task<IEnumerable<(int Id, string Nombre)>> GetUbicacionesAsync();
|
||||
}
|
||||
13
RS_system/Services/ICategoriaService.cs
Normal file
13
RS_system/Services/ICategoriaService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public interface ICategoriaService
|
||||
{
|
||||
Task<IEnumerable<Categoria>> GetAllAsync();
|
||||
Task<Categoria?> GetByIdAsync(int id);
|
||||
Task<bool> CreateAsync(Categoria categoria);
|
||||
Task<bool> UpdateAsync(Categoria categoria);
|
||||
Task<bool> DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(string nombre, int? excludeId = null);
|
||||
}
|
||||
21
RS_system/Services/IColaboracionService.cs
Normal file
21
RS_system/Services/IColaboracionService.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Rs_system.Models;
|
||||
using Rs_system.Models.ViewModels;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public interface IColaboracionService
|
||||
{
|
||||
// Tipos de colaboración
|
||||
Task<List<TipoColaboracion>> GetTiposActivosAsync();
|
||||
Task<TipoColaboracion?> GetTipoByIdAsync(long id);
|
||||
|
||||
// Colaboraciones
|
||||
Task<Colaboracion> RegistrarColaboracionAsync(RegistrarColaboracionViewModel model, string registradoPor);
|
||||
Task<List<Colaboracion>> GetColaboracionesRecientesAsync(int cantidad = 50);
|
||||
Task<Colaboracion?> GetColaboracionByIdAsync(long id);
|
||||
|
||||
// Reportes
|
||||
Task<ReporteColaboracionesViewModel> GenerarReportePorFechasAsync(DateTime fechaInicio, DateTime fechaFin);
|
||||
Task<EstadoCuentaViewModel> GenerarEstadoCuentaAsync(long miembroId);
|
||||
Task<List<UltimoPagoViewModel>> GetUltimosPagosPorMiembroAsync(long miembroId);
|
||||
}
|
||||
39
RS_system/Services/IContabilidadGeneralService.cs
Normal file
39
RS_system/Services/IContabilidadGeneralService.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public interface IContabilidadGeneralService
|
||||
{
|
||||
// Categorías de Ingreso
|
||||
Task<List<CategoriaIngreso>> ObtenerCategoriasIngresoAsync();
|
||||
Task<CategoriaIngreso?> ObtenerCategoriaIngresoPorIdAsync(long id);
|
||||
Task<CategoriaIngreso> CrearCategoriaIngresoAsync(CategoriaIngreso categoria);
|
||||
Task<bool> ActualizarCategoriaIngresoAsync(CategoriaIngreso categoria);
|
||||
Task<bool> EliminarCategoriaIngresoAsync(long id);
|
||||
|
||||
// Categorías de Egreso
|
||||
Task<List<CategoriaEgreso>> ObtenerCategoriasEgresoAsync();
|
||||
Task<CategoriaEgreso?> ObtenerCategoriaEgresoPorIdAsync(long id);
|
||||
Task<CategoriaEgreso> CrearCategoriaEgresoAsync(CategoriaEgreso categoria);
|
||||
Task<bool> ActualizarCategoriaEgresoAsync(CategoriaEgreso categoria);
|
||||
Task<bool> EliminarCategoriaEgresoAsync(long id);
|
||||
|
||||
// Reportes Mensuales
|
||||
Task<ReporteMensualGeneral?> ObtenerReporteMensualAsync(int mes, int anio);
|
||||
Task<ReporteMensualGeneral> ObtenerOCrearReporteMensualAsync(int mes, int anio);
|
||||
Task<List<ReporteMensualGeneral>> ListarReportesAsync(int? anio = null);
|
||||
Task<bool> CerrarReporteAsync(long reporteId);
|
||||
|
||||
// Movimientos
|
||||
Task<bool> GuardarMovimientosBulkAsync(long reporteId, List<MovimientoGeneral> movimientos);
|
||||
Task<decimal> CalcularSaldoActualAsync(long reporteId);
|
||||
|
||||
// Consolidados
|
||||
Task<Dictionary<string, decimal>> ObtenerConsolidadoIngresosAsync(long reporteId);
|
||||
Task<Dictionary<string, decimal>> ObtenerConsolidadoEgresosAsync(long reporteId);
|
||||
|
||||
// Adjuntos
|
||||
Task<List<MovimientoGeneralAdjunto>> ObtenerAdjuntosMovimientoAsync(long movimientoId);
|
||||
Task<MovimientoGeneralAdjunto?> CrearAdjuntoAsync(long movimientoId, string nombreArchivo, string rutaArchivo, string tipoContenido);
|
||||
Task<bool> EliminarAdjuntoAsync(long adjuntoId);
|
||||
}
|
||||
18
RS_system/Services/IContabilidadService.cs
Normal file
18
RS_system/Services/IContabilidadService.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public interface IContabilidadService
|
||||
{
|
||||
Task<ContabilidadRegistro> CrearRegistroAsync(ContabilidadRegistro registro);
|
||||
Task<IReadOnlyList<ContabilidadRegistro>> ObtenerRegistrosAsync(long grupoId, DateTime desde, DateTime hasta);
|
||||
|
||||
// Monthly Report Methods
|
||||
Task<ReporteMensualContable?> ObtenerReporteMensualAsync(long grupoId, int mes, int anio);
|
||||
Task<ReporteMensualContable> ObtenerOCrearReporteMensualAsync(long grupoId, int mes, int anio);
|
||||
Task<List<ReporteMensualContable>> ListarReportesPorGrupoAsync(long grupoId);
|
||||
Task<bool> GuardarRegistrosBulkAsync(long reporteId, List<ContabilidadRegistro> registros);
|
||||
Task<decimal> CalcularSaldoActualAsync(long reporteId);
|
||||
Task<bool> CerrarReporteAsync(long reporteId);
|
||||
|
||||
}
|
||||
13
RS_system/Services/IEstadoArticuloService.cs
Normal file
13
RS_system/Services/IEstadoArticuloService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public interface IEstadoArticuloService
|
||||
{
|
||||
Task<IEnumerable<EstadoArticulo>> GetAllAsync();
|
||||
Task<EstadoArticulo?> GetByIdAsync(int id);
|
||||
Task<bool> CreateAsync(EstadoArticulo estado);
|
||||
Task<bool> UpdateAsync(EstadoArticulo estado);
|
||||
Task<bool> DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(string nombre, int? excludeId = null);
|
||||
}
|
||||
21
RS_system/Services/IMovimientoService.cs
Normal file
21
RS_system/Services/IMovimientoService.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public interface IMovimientoService
|
||||
{
|
||||
Task<IEnumerable<MovimientoInventario>> GetHistorialGeneralAsync(int limit = 100);
|
||||
Task<IEnumerable<MovimientoInventario>> GetHistorialPorArticuloAsync(int articuloId);
|
||||
|
||||
// Legacy wrappers (Quantity = 1)
|
||||
Task<bool> RegistrarTrasladoAsync(int articuloId, int nuevaUbicacionId, string observacion, string usuario);
|
||||
Task<bool> RegistrarBajaAsync(int articuloId, string motivo, string usuario);
|
||||
|
||||
// New Quantity-Aware Methods
|
||||
Task<bool> RegistrarTrasladoCantidadAsync(int articuloId, int nuevaUbicacionId, int cantidad, string observacion, string usuario);
|
||||
Task<bool> RegistrarBajaCantidadAsync(int articuloId, int cantidad, string motivo, string usuario);
|
||||
|
||||
Task<bool> RegistrarCambioEstadoAsync(int articuloId, int nuevoEstadoId, string observacion, string usuario);
|
||||
Task<bool> RegistrarPrestamoAsync(int articuloId, int cantidad, string personaNombre, string? personaIdentificacion, DateTime? fechaDevolucionEstimada, string observacion, string usuario);
|
||||
Task<bool> RegistrarEntradaCantidadAsync(int articuloId, int cantidad, string observacion, string usuario);
|
||||
}
|
||||
13
RS_system/Services/IPrestamoService.cs
Normal file
13
RS_system/Services/IPrestamoService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public interface IPrestamoService
|
||||
{
|
||||
Task<IEnumerable<Prestamo>> GetHistorialPrestamosAsync(int limit = 100);
|
||||
Task<IEnumerable<Prestamo>> GetPrestamosActivosAsync();
|
||||
Task<Prestamo?> GetPrestamoByIdAsync(long id);
|
||||
Task<bool> RegistrarPrestamoAsync(int articuloId, int cantidad, string personaNombre, string? personaIdentificacion, DateTime? fechaDevolucionEstimada, string observacion, string usuario);
|
||||
Task<bool> RegistrarDevolucionAsync(long prestamoId, string observacion, string usuario);
|
||||
Task<bool> RegistrarDevolucionParcialAsync(long prestamoId, List<string> codigosDevolucion, string observacion, string usuario);
|
||||
}
|
||||
13
RS_system/Services/IUbicacionService.cs
Normal file
13
RS_system/Services/IUbicacionService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public interface IUbicacionService
|
||||
{
|
||||
Task<IEnumerable<Ubicacion>> GetAllAsync();
|
||||
Task<Ubicacion?> GetByIdAsync(int id);
|
||||
Task<bool> CreateAsync(Ubicacion ubicacion);
|
||||
Task<bool> UpdateAsync(Ubicacion ubicacion);
|
||||
Task<bool> DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(string nombre, int? excludeId = null);
|
||||
}
|
||||
450
RS_system/Services/MovimientoService.cs
Normal file
450
RS_system/Services/MovimientoService.cs
Normal file
@@ -0,0 +1,450 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rs_system.Data;
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public class MovimientoService : IMovimientoService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public MovimientoService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MovimientoInventario>> GetHistorialGeneralAsync(int limit = 100)
|
||||
{
|
||||
return await _context.MovimientosInventario
|
||||
.Include(m => m.Articulo)
|
||||
.Include(m => m.UbicacionOrigen)
|
||||
.Include(m => m.UbicacionDestino)
|
||||
.Include(m => m.EstadoAnterior)
|
||||
.Include(m => m.EstadoNuevo)
|
||||
.OrderByDescending(m => m.Fecha)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MovimientoInventario>> GetHistorialPorArticuloAsync(int articuloId)
|
||||
{
|
||||
return await _context.MovimientosInventario
|
||||
.Include(m => m.UbicacionOrigen)
|
||||
.Include(m => m.UbicacionDestino)
|
||||
.Include(m => m.EstadoAnterior)
|
||||
.Include(m => m.EstadoNuevo)
|
||||
.Where(m => m.ArticuloId == articuloId)
|
||||
.OrderByDescending(m => m.Fecha)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> RegistrarTrasladoAsync(int articuloId, int nuevaUbicacionId, string observacion, string usuario)
|
||||
{
|
||||
return await RegistrarTrasladoCantidadAsync(articuloId, nuevaUbicacionId, 1, observacion, usuario);
|
||||
}
|
||||
|
||||
public async Task<bool> RegistrarTrasladoCantidadAsync(
|
||||
int articuloId,
|
||||
int nuevaUbicacionId,
|
||||
int cantidad,
|
||||
string observacion,
|
||||
string usuario)
|
||||
{
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
|
||||
return await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var articulo = await _context.Articulos.FindAsync(articuloId);
|
||||
if (articulo == null) return false;
|
||||
|
||||
var fecha = DateTime.UtcNow;
|
||||
|
||||
if (articulo.TipoControl == nameof(Articulo.TipoControlInventario.LOTE))
|
||||
{
|
||||
// ===== LOTE =====
|
||||
var origenExistencia = await _context.Existencias
|
||||
.FirstOrDefaultAsync(e =>
|
||||
e.ArticuloId == articuloId &&
|
||||
e.UbicacionId == articulo.UbicacionId);
|
||||
|
||||
if (origenExistencia == null || origenExistencia.Cantidad < cantidad)
|
||||
return false;
|
||||
|
||||
origenExistencia.Cantidad -= cantidad;
|
||||
origenExistencia.ActualizadoEn = fecha;
|
||||
_context.Existencias.Update(origenExistencia);
|
||||
|
||||
|
||||
var destinoExistencia = await _context.Existencias
|
||||
.FirstOrDefaultAsync(e =>
|
||||
e.ArticuloId == articuloId &&
|
||||
e.UbicacionId == nuevaUbicacionId);
|
||||
|
||||
if (destinoExistencia == null)
|
||||
{
|
||||
destinoExistencia = new Existencia
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
UbicacionId = nuevaUbicacionId,
|
||||
Cantidad = 0,
|
||||
ActualizadoEn = fecha
|
||||
};
|
||||
_context.Existencias.Add(destinoExistencia);
|
||||
}
|
||||
|
||||
destinoExistencia.Cantidad += cantidad;
|
||||
destinoExistencia.ActualizadoEn = fecha;
|
||||
|
||||
// 📉 MOVIMIENTO SALIDA
|
||||
var movSalida = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.TRASLADO),
|
||||
Fecha = fecha,
|
||||
UbicacionOrigenId = articulo.UbicacionId,
|
||||
Cantidad = cantidad,
|
||||
TipMov = 2,
|
||||
Observacion = observacion,
|
||||
UsuarioId = usuario,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
EstadoNuevoId = articulo.EstadoId
|
||||
};
|
||||
|
||||
// 📈 MOVIMIENTO ENTRADA
|
||||
var movEntrada = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.TRASLADO),
|
||||
Fecha = fecha,
|
||||
UbicacionDestinoId = nuevaUbicacionId,
|
||||
Cantidad = cantidad,
|
||||
TipMov = 1,
|
||||
Observacion = observacion,
|
||||
UsuarioId = usuario,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
EstadoNuevoId = articulo.EstadoId
|
||||
};
|
||||
|
||||
_context.MovimientosInventario.AddRange(movSalida, movEntrada);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ===== UNITARIO =====
|
||||
if (articulo.UbicacionId == nuevaUbicacionId)
|
||||
return false;
|
||||
|
||||
// 📉 SALIDA
|
||||
var movSalida = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.TRASLADO),
|
||||
Fecha = fecha,
|
||||
UbicacionOrigenId = articulo.UbicacionId,
|
||||
Cantidad = 1,
|
||||
TipMov = 2,
|
||||
Observacion = observacion,
|
||||
UsuarioId = usuario,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
EstadoNuevoId = articulo.EstadoId
|
||||
};
|
||||
|
||||
// 📈 ENTRADA
|
||||
var movEntrada = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.TRASLADO),
|
||||
Fecha = fecha,
|
||||
UbicacionDestinoId = nuevaUbicacionId,
|
||||
Cantidad = 1,
|
||||
TipMov = 1,
|
||||
Observacion = observacion,
|
||||
UsuarioId = usuario,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
EstadoNuevoId = articulo.EstadoId
|
||||
};
|
||||
|
||||
articulo.UbicacionId = nuevaUbicacionId;
|
||||
articulo.ActualizadoEn = fecha;
|
||||
|
||||
_context.Articulos.Update(articulo);
|
||||
_context.MovimientosInventario.AddRange(movSalida, movEntrada);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> RegistrarCambioEstadoAsync(int articuloId, int nuevoEstadoId, string observacion, string usuario)
|
||||
{
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
|
||||
return await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var articulo = await _context.Articulos.FindAsync(articuloId);
|
||||
if (articulo == null) return false;
|
||||
|
||||
if (articulo.EstadoId == nuevoEstadoId) return false;
|
||||
|
||||
var movimiento = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.CAMBIO_ESTADO),
|
||||
Fecha = DateTime.UtcNow,
|
||||
UbicacionOrigenId = articulo.UbicacionId,
|
||||
UbicacionDestinoId = articulo.UbicacionId,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
EstadoNuevoId = nuevoEstadoId,
|
||||
Cantidad = (articulo.TipoControl == nameof(Articulo.TipoControlInventario.LOTE)) ? articulo.CantidadGlobal : 1,
|
||||
Observacion = observacion,
|
||||
UsuarioId = usuario
|
||||
};
|
||||
|
||||
articulo.EstadoId = nuevoEstadoId;
|
||||
articulo.ActualizadoEn = DateTime.UtcNow;
|
||||
|
||||
_context.MovimientosInventario.Add(movimiento);
|
||||
_context.Articulos.Update(articulo);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> RegistrarPrestamoAsync(int articuloId, int cantidad, string personaNombre, string? personaIdentificacion,
|
||||
DateTime? fechaDevolucionEstimada, string observacion, string usuario)
|
||||
{
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
|
||||
return await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var articulo = await _context.Articulos.FindAsync(articuloId);
|
||||
if (articulo == null) return false;
|
||||
|
||||
// Validar stock disponible
|
||||
if (articulo.TipoControl == nameof(Articulo.TipoControlInventario.LOTE))
|
||||
{
|
||||
var existencia = await _context.Existencias
|
||||
.FirstOrDefaultAsync(e => e.ArticuloId == articuloId && e.UbicacionId == articulo.UbicacionId);
|
||||
|
||||
if (existencia == null || existencia.Cantidad < cantidad) return false;
|
||||
|
||||
// Reducir stock existente
|
||||
existencia.Cantidad -= cantidad;
|
||||
articulo.CantidadGlobal -= cantidad;
|
||||
|
||||
if (articulo.CantidadGlobal < 0) articulo.CantidadGlobal = 0;
|
||||
if (existencia.Cantidad < 0) existencia.Cantidad = 0;
|
||||
|
||||
_context.Existencias.Update(existencia);
|
||||
_context.Articulos.Update(articulo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// UNITARIO - Solo se puede prestar 1
|
||||
if (cantidad != 1) return false;
|
||||
if (!articulo.Activo) return false;
|
||||
}
|
||||
|
||||
// Crear movimiento de inventario
|
||||
var movimiento = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.PRESTAMO),
|
||||
TipMov = 2,
|
||||
Fecha = DateTime.UtcNow,
|
||||
UbicacionOrigenId = articulo.UbicacionId,
|
||||
UbicacionDestinoId = articulo.UbicacionId, // Mismo lugar, solo está prestado
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
EstadoNuevoId = articulo.EstadoId,
|
||||
Cantidad = cantidad,
|
||||
Observacion = $"Préstamo a {personaNombre}. {observacion}",
|
||||
UsuarioId = usuario
|
||||
};
|
||||
|
||||
_context.MovimientosInventario.Add(movimiento);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> RegistrarBajaAsync(int articuloId, string motivo, string usuario)
|
||||
{
|
||||
return await RegistrarBajaCantidadAsync(articuloId, 1, motivo, usuario);
|
||||
}
|
||||
|
||||
public async Task<bool> RegistrarBajaCantidadAsync(int articuloId, int cantidad, string motivo, string usuario)
|
||||
{
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
|
||||
return await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var articulo = await _context.Articulos.FindAsync(articuloId);
|
||||
if (articulo == null) return false;
|
||||
|
||||
if (articulo.TipoControl == nameof(Articulo.TipoControlInventario.LOTE))
|
||||
{
|
||||
var existencia = await _context.Existencias
|
||||
.FirstOrDefaultAsync(e => e.ArticuloId == articuloId && e.UbicacionId == articulo.UbicacionId);
|
||||
|
||||
if (existencia == null || existencia.Cantidad < cantidad) return false;
|
||||
|
||||
existencia.Cantidad -= cantidad;
|
||||
articulo.CantidadGlobal -= cantidad;
|
||||
|
||||
if (articulo.CantidadGlobal <= 0) articulo.Activo = false;
|
||||
|
||||
var movimiento = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.BAJA),
|
||||
TipMov = 2,
|
||||
Fecha = DateTime.UtcNow,
|
||||
UbicacionOrigenId = articulo.UbicacionId,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
Cantidad = cantidad,
|
||||
Observacion = motivo,
|
||||
UsuarioId = usuario
|
||||
};
|
||||
|
||||
_context.Existencias.Update(existencia);
|
||||
_context.MovimientosInventario.Add(movimiento);
|
||||
_context.Articulos.Update(articulo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var movimiento = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.BAJA),
|
||||
TipMov = 2,
|
||||
Fecha = DateTime.UtcNow,
|
||||
UbicacionOrigenId = articulo.UbicacionId,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
Cantidad = 1,
|
||||
Observacion = motivo,
|
||||
UsuarioId = usuario
|
||||
};
|
||||
|
||||
articulo.Activo = false;
|
||||
_context.MovimientosInventario.Add(movimiento);
|
||||
_context.Articulos.Update(articulo);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> RegistrarEntradaCantidadAsync(int articuloId, int cantidad, string observacion, string usuario)
|
||||
{
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
|
||||
return await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var articulo = await _context.Articulos.FindAsync(articuloId);
|
||||
if (articulo == null) return false;
|
||||
|
||||
if (articulo.TipoControl == nameof(Articulo.TipoControlInventario.LOTE))
|
||||
{
|
||||
var existencia = await _context.Existencias
|
||||
.FirstOrDefaultAsync(e => e.ArticuloId == articuloId && e.UbicacionId == articulo.UbicacionId);
|
||||
|
||||
if (existencia == null)
|
||||
{
|
||||
existencia = new Existencia
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
UbicacionId = articulo.UbicacionId,
|
||||
Cantidad = 0,
|
||||
ActualizadoEn = DateTime.UtcNow
|
||||
};
|
||||
_context.Existencias.Add(existencia);
|
||||
}
|
||||
|
||||
existencia.Cantidad += cantidad;
|
||||
articulo.CantidadGlobal += cantidad;
|
||||
articulo.ActualizadoEn = DateTime.UtcNow;
|
||||
|
||||
_context.Existencias.Update(existencia);
|
||||
_context.Articulos.Update(articulo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// UNITARIO - Si está inactivo por baja, lo reactivamos?
|
||||
// En unitario, una entrada suele ser que el objeto "vuelve" a estar disponible.
|
||||
articulo.Activo = true;
|
||||
articulo.ActualizadoEn = DateTime.UtcNow;
|
||||
_context.Articulos.Update(articulo);
|
||||
}
|
||||
|
||||
var movimiento = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.ENTRADA),
|
||||
TipMov = 1,
|
||||
Fecha = DateTime.UtcNow,
|
||||
UbicacionDestinoId = articulo.UbicacionId,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
EstadoNuevoId = articulo.EstadoId,
|
||||
Cantidad = cantidad,
|
||||
Observacion = observacion,
|
||||
UsuarioId = usuario
|
||||
};
|
||||
|
||||
_context.MovimientosInventario.Add(movimiento);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
226
RS_system/Services/PrestamoService.cs
Normal file
226
RS_system/Services/PrestamoService.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rs_system.Data;
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public class PrestamoService : IPrestamoService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public PrestamoService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Prestamo>> GetHistorialPrestamosAsync(int limit = 100)
|
||||
{
|
||||
return await _context.Prestamos
|
||||
.Include(p => p.Articulo)
|
||||
.OrderByDescending(p => p.FechaPrestamo)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Prestamo>> GetPrestamosActivosAsync()
|
||||
{
|
||||
return await _context.Prestamos
|
||||
.Include(p => p.Articulo)
|
||||
.Where(p => p.Estado == "ACTIVO" || p.Estado == "ATRASADO")
|
||||
.OrderByDescending(p => p.FechaPrestamo)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Prestamo?> GetPrestamoByIdAsync(long id)
|
||||
{
|
||||
return await _context.Prestamos
|
||||
.Include(p => p.Articulo)
|
||||
.Include(p => p.Detalles)
|
||||
.FirstOrDefaultAsync(p => p.Id == id);
|
||||
}
|
||||
|
||||
public async Task<bool> RegistrarPrestamoAsync(int articuloId, int cantidad, string personaNombre, string? personaIdentificacion, DateTime? fechaDevolucionEstimada, string observacion, string usuario)
|
||||
{
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
|
||||
return await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var articulo = await _context.Articulos.FindAsync(articuloId);
|
||||
if (articulo == null) return false;
|
||||
|
||||
// 1. Validar y actualizar stock
|
||||
if (articulo.TipoControl == nameof(Articulo.TipoControlInventario.LOTE))
|
||||
{
|
||||
var existencia = await _context.Existencias
|
||||
.FirstOrDefaultAsync(e => e.ArticuloId == articuloId && e.UbicacionId == articulo.UbicacionId);
|
||||
|
||||
if (existencia == null || existencia.Cantidad < cantidad) return false;
|
||||
|
||||
existencia.Cantidad -= cantidad;
|
||||
articulo.CantidadGlobal -= cantidad;
|
||||
_context.Existencias.Update(existencia);
|
||||
_context.Articulos.Update(articulo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unitario
|
||||
if (cantidad != 1 || !articulo.Activo) return false;
|
||||
// En unitario, podrías marcar como inactivo o simplemente registrar el préstamo
|
||||
// Para este sistema, asumiremos que prestado sigue siendo "Activo" pero en una ubicación de préstamo (vía movimiento)
|
||||
}
|
||||
|
||||
// 2. Crear el registro de préstamo
|
||||
var prestamo = new Prestamo
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
Cantidad = cantidad,
|
||||
PersonaNombre = personaNombre,
|
||||
PersonaIdentificacion = personaIdentificacion,
|
||||
FechaPrestamo = DateTime.UtcNow,
|
||||
FechaDevolucionEstimada = fechaDevolucionEstimada,
|
||||
Estado = "ACTIVO",
|
||||
Observacion = observacion,
|
||||
UsuarioId = usuario
|
||||
};
|
||||
|
||||
_context.Prestamos.Add(prestamo);
|
||||
await _context.SaveChangesAsync(); // Guardamos para tener el ID del préstamo
|
||||
|
||||
// 3. Crear movimiento de inventario (auditoría)
|
||||
var movimiento = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articuloId,
|
||||
TipoMovimiento = nameof(TipoMovimiento.PRESTAMO),
|
||||
TipMov = 2,
|
||||
Fecha = DateTime.UtcNow,
|
||||
UbicacionOrigenId = articulo.UbicacionId,
|
||||
UbicacionDestinoId = articulo.UbicacionId,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
EstadoNuevoId = articulo.EstadoId,
|
||||
Cantidad = cantidad,
|
||||
Observacion = $"Préstamo #{prestamo.Id} a {personaNombre}. {observacion}",
|
||||
UsuarioId = usuario
|
||||
};
|
||||
|
||||
_context.MovimientosInventario.Add(movimiento);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> RegistrarDevolucionAsync(
|
||||
long prestamoId,
|
||||
string observacion,
|
||||
string usuario)
|
||||
{
|
||||
var strategy = _context.Database.CreateExecutionStrategy();
|
||||
|
||||
return await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var prestamo = await _context.Prestamos.FindAsync(prestamoId);
|
||||
if (prestamo == null)
|
||||
return false;
|
||||
|
||||
if (prestamo.Estado == "DEVUELTO")
|
||||
return false;
|
||||
|
||||
var articulo = await _context.Articulos.FindAsync(prestamo.ArticuloId);
|
||||
if (articulo == null)
|
||||
return false;
|
||||
|
||||
var fechaActual = DateTime.UtcNow;
|
||||
|
||||
if (articulo.TipoControl == nameof(Articulo.TipoControlInventario.LOTE))
|
||||
{
|
||||
// --- Buscar existencia ---
|
||||
var existencia = await _context.Existencias
|
||||
.FirstOrDefaultAsync(e =>
|
||||
e.ArticuloId == articulo.Id &&
|
||||
e.UbicacionId == articulo.UbicacionId);
|
||||
|
||||
// --- Crear existencia si no existe ---
|
||||
if (existencia == null)
|
||||
{
|
||||
existencia = new Existencia
|
||||
{
|
||||
ArticuloId = articulo.Id,
|
||||
UbicacionId = articulo.UbicacionId,
|
||||
Cantidad = 0,
|
||||
ActualizadoEn = fechaActual
|
||||
};
|
||||
_context.Existencias.Add(existencia);
|
||||
}
|
||||
|
||||
// --- Actualizar cantidades ---
|
||||
existencia.Cantidad += prestamo.Cantidad;
|
||||
existencia.ActualizadoEn = fechaActual;
|
||||
|
||||
articulo.CantidadGlobal += prestamo.Cantidad;
|
||||
articulo.ActualizadoEn = fechaActual;
|
||||
|
||||
_context.Existencias.Update(existencia);
|
||||
_context.Articulos.Update(articulo);
|
||||
}
|
||||
else
|
||||
{
|
||||
articulo.Activo = true;
|
||||
articulo.ActualizadoEn = fechaActual;
|
||||
_context.Articulos.Update(articulo);
|
||||
}
|
||||
|
||||
prestamo.Estado = "DEVUELTO";
|
||||
prestamo.FechaDevolucionReal = fechaActual;
|
||||
prestamo.Observacion =
|
||||
$"{prestamo.Observacion}\nDevolución: {observacion}";
|
||||
|
||||
_context.Prestamos.Update(prestamo);
|
||||
|
||||
var movimiento = new MovimientoInventario
|
||||
{
|
||||
ArticuloId = articulo.Id,
|
||||
TipoMovimiento = nameof(TipoMovimiento.DEVOLUCION),
|
||||
TipMov = 1, // ENTRADA
|
||||
Fecha = fechaActual,
|
||||
UbicacionOrigenId = articulo.UbicacionId,
|
||||
UbicacionDestinoId = articulo.UbicacionId,
|
||||
EstadoAnteriorId = articulo.EstadoId,
|
||||
EstadoNuevoId = articulo.EstadoId,
|
||||
Cantidad = prestamo.Cantidad,
|
||||
Observacion = $"Devolución de préstamo #{prestamo.Id}. {observacion}",
|
||||
UsuarioId = usuario
|
||||
};
|
||||
|
||||
_context.MovimientosInventario.Add(movimiento);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> RegistrarDevolucionParcialAsync(long prestamoId, List<string> codigosDevolucion, string observacion, string usuario)
|
||||
{
|
||||
// Implementación básica para seguir la interfaz, aunque el controlador actual no la usa directamente
|
||||
// Esta lógica sería más para artículos unitarios con códigos específicos (PrestamoDetalle)
|
||||
return await RegistrarDevolucionAsync(prestamoId, "Devolución parcial - " + observacion, usuario);
|
||||
}
|
||||
}
|
||||
102
RS_system/Services/UbicacionService.cs
Normal file
102
RS_system/Services/UbicacionService.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rs_system.Data;
|
||||
using Rs_system.Models;
|
||||
|
||||
namespace Rs_system.Services;
|
||||
|
||||
public class UbicacionService : IUbicacionService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public UbicacionService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Ubicacion>> GetAllAsync()
|
||||
{
|
||||
return await _context.Ubicaciones
|
||||
.Where(u => !u.Eliminado)
|
||||
.OrderBy(u => u.Nombre)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Ubicacion?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Ubicaciones
|
||||
.FirstOrDefaultAsync(u => u.Id == id && !u.Eliminado);
|
||||
}
|
||||
|
||||
public async Task<bool> CreateAsync(Ubicacion ubicacion)
|
||||
{
|
||||
try
|
||||
{
|
||||
ubicacion.CreadoEn = DateTime.UtcNow;
|
||||
ubicacion.ActualizadoEn = DateTime.UtcNow;
|
||||
ubicacion.Eliminado = false;
|
||||
|
||||
_context.Ubicaciones.Add(ubicacion);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(Ubicacion ubicacion)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = await _context.Ubicaciones.FindAsync(ubicacion.Id);
|
||||
if (existing == null || existing.Eliminado) return false;
|
||||
|
||||
existing.Nombre = ubicacion.Nombre;
|
||||
existing.Descripcion = ubicacion.Descripcion;
|
||||
existing.Responsable = ubicacion.Responsable;
|
||||
existing.Activo = ubicacion.Activo;
|
||||
existing.ActualizadoEn = DateTime.UtcNow;
|
||||
|
||||
_context.Ubicaciones.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ubicacion = await _context.Ubicaciones.FindAsync(id);
|
||||
if (ubicacion == null || ubicacion.Eliminado) return false;
|
||||
|
||||
ubicacion.Eliminado = true;
|
||||
ubicacion.ActualizadoEn = DateTime.UtcNow;
|
||||
|
||||
_context.Ubicaciones.Update(ubicacion);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(string nombre, int? excludeId = null)
|
||||
{
|
||||
var query = _context.Ubicaciones.AsQueryable();
|
||||
|
||||
if (excludeId.HasValue)
|
||||
{
|
||||
query = query.Where(u => u.Id != excludeId.Value);
|
||||
}
|
||||
|
||||
return await query.AnyAsync(u => u.Nombre.ToLower() == nombre.ToLower() && !u.Eliminado);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user