ADM-009: Tablas Fiscales (IVA + IIBB) — append-only versioned ref data #22
177
src/api/SIGCM2.Domain/Entities/IngresosBrutos.cs
Normal file
177
src/api/SIGCM2.Domain/Entities/IngresosBrutos.cs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
using SIGCM2.Domain.Fiscal;
|
||||||
|
|
||||||
|
namespace SIGCM2.Domain.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entrada de Ingresos Brutos por provincia con versionado append-only.
|
||||||
|
/// Alicuota es INMUTABLE post-creación; cambiar el valor requiere crear una nueva versión
|
||||||
|
/// vía <see cref="NuevaVersion"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class IngresosBrutos
|
||||||
|
{
|
||||||
|
public int Id { get; }
|
||||||
|
public ProvinciaArgentina Provincia { get; } // INMUTABLE
|
||||||
|
public string Descripcion { get; }
|
||||||
|
public decimal Alicuota { get; } // INMUTABLE — usar NuevaVersion para cambiar
|
||||||
|
public bool Activo { get; }
|
||||||
|
public DateOnly VigenciaDesde { get; }
|
||||||
|
public DateOnly? VigenciaHasta { get; }
|
||||||
|
public int? PredecesorId { get; }
|
||||||
|
public DateTime FechaCreacion { get; }
|
||||||
|
public DateTime? FechaModificacion { get; }
|
||||||
|
|
||||||
|
private IngresosBrutos(
|
||||||
|
int id,
|
||||||
|
ProvinciaArgentina provincia,
|
||||||
|
string descripcion,
|
||||||
|
decimal alicuota,
|
||||||
|
bool activo,
|
||||||
|
DateOnly vigenciaDesde,
|
||||||
|
DateOnly? vigenciaHasta,
|
||||||
|
int? predecesorId,
|
||||||
|
DateTime fechaCreacion,
|
||||||
|
DateTime? fechaModificacion)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Provincia = provincia;
|
||||||
|
Descripcion = descripcion;
|
||||||
|
Alicuota = alicuota;
|
||||||
|
Activo = activo;
|
||||||
|
VigenciaDesde = vigenciaDesde;
|
||||||
|
VigenciaHasta = vigenciaHasta;
|
||||||
|
PredecesorId = predecesorId;
|
||||||
|
FechaCreacion = fechaCreacion;
|
||||||
|
FechaModificacion = fechaModificacion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory para crear una nueva entrada de IIBB (Id=0 — BD asigna via IDENTITY; Activo=true).
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException">Si Alicuota fuera de rango 0-100.</exception>
|
||||||
|
public static IngresosBrutos ForCreation(
|
||||||
|
ProvinciaArgentina provincia,
|
||||||
|
string descripcion,
|
||||||
|
decimal alicuota,
|
||||||
|
DateOnly vigenciaDesde,
|
||||||
|
DateOnly? vigenciaHasta = null,
|
||||||
|
int? predecesorId = null)
|
||||||
|
{
|
||||||
|
ValidateAlicuota(alicuota, nameof(alicuota));
|
||||||
|
ValidateVigencias(vigenciaDesde, vigenciaHasta);
|
||||||
|
|
||||||
|
return new(
|
||||||
|
id: 0,
|
||||||
|
provincia: provincia,
|
||||||
|
descripcion: descripcion,
|
||||||
|
alicuota: alicuota,
|
||||||
|
activo: true,
|
||||||
|
vigenciaDesde: vigenciaDesde,
|
||||||
|
vigenciaHasta: vigenciaHasta,
|
||||||
|
predecesorId: predecesorId,
|
||||||
|
fechaCreacion: default,
|
||||||
|
fechaModificacion: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory para reconstruir desde repositorio (Dapper). No aplica validaciones de dominio.
|
||||||
|
/// </summary>
|
||||||
|
public static IngresosBrutos FromDb(
|
||||||
|
int id,
|
||||||
|
ProvinciaArgentina provincia,
|
||||||
|
string descripcion,
|
||||||
|
decimal alicuota,
|
||||||
|
bool activo,
|
||||||
|
DateOnly vigenciaDesde,
|
||||||
|
DateOnly? vigenciaHasta,
|
||||||
|
int? predecesorId,
|
||||||
|
DateTime fechaCreacion,
|
||||||
|
DateTime? fechaModificacion)
|
||||||
|
=> new(id, provincia, descripcion, alicuota, activo,
|
||||||
|
vigenciaDesde, vigenciaHasta, predecesorId, fechaCreacion, fechaModificacion);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea una nueva versión con la alícuota actualizada.
|
||||||
|
/// Retorna la predecesora cerrada y la nueva versión.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Si la predecesora ya está cerrada (VigenciaHasta != null).</exception>
|
||||||
|
/// <exception cref="ArgumentException">Si vigenciaDesde no es posterior a la predecesora, o nuevaAlicuota fuera de rango.</exception>
|
||||||
|
public (IngresosBrutos predecesoraCerrada, IngresosBrutos nuevaVersion) NuevaVersion(
|
||||||
|
decimal nuevaAlicuota,
|
||||||
|
DateOnly vigenciaDesde)
|
||||||
|
{
|
||||||
|
if (VigenciaHasta is not null)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"La versión {Id} ya está cerrada (VigenciaHasta={VigenciaHasta}). No puede generar nueva versión.");
|
||||||
|
|
||||||
|
if (vigenciaDesde <= VigenciaDesde)
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"vigenciaDesde ({vigenciaDesde}) debe ser posterior a VigenciaDesde de la predecesora ({VigenciaDesde}).",
|
||||||
|
nameof(vigenciaDesde));
|
||||||
|
|
||||||
|
ValidateAlicuota(nuevaAlicuota, nameof(nuevaAlicuota));
|
||||||
|
|
||||||
|
var cerrada = new IngresosBrutos(
|
||||||
|
id: Id,
|
||||||
|
provincia: Provincia,
|
||||||
|
descripcion: Descripcion,
|
||||||
|
alicuota: Alicuota,
|
||||||
|
activo: Activo,
|
||||||
|
vigenciaDesde: VigenciaDesde,
|
||||||
|
vigenciaHasta: vigenciaDesde.AddDays(-1),
|
||||||
|
predecesorId: PredecesorId,
|
||||||
|
fechaCreacion: FechaCreacion,
|
||||||
|
fechaModificacion: DateTime.UtcNow);
|
||||||
|
|
||||||
|
var nueva = ForCreation(
|
||||||
|
provincia: Provincia,
|
||||||
|
descripcion: Descripcion,
|
||||||
|
alicuota: nuevaAlicuota,
|
||||||
|
vigenciaDesde: vigenciaDesde,
|
||||||
|
vigenciaHasta: null,
|
||||||
|
predecesorId: Id);
|
||||||
|
|
||||||
|
return (cerrada, nueva);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cosmetic mutators (NO WithAlicuota, NO WithProvincia) ─────────────────
|
||||||
|
|
||||||
|
/// <summary>Actualiza la descripción. Alicuota y Provincia permanecen inmutables.</summary>
|
||||||
|
public IngresosBrutos WithDescripcion(string descripcion)
|
||||||
|
=> new(Id, Provincia, descripcion, Alicuota, Activo,
|
||||||
|
VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, DateTime.UtcNow);
|
||||||
|
|
||||||
|
/// <summary>Retorna instancia con Activo=false.</summary>
|
||||||
|
public IngresosBrutos Deactivate()
|
||||||
|
=> new(Id, Provincia, Descripcion, Alicuota, false,
|
||||||
|
VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, DateTime.UtcNow);
|
||||||
|
|
||||||
|
/// <summary>Retorna instancia con Activo=true.</summary>
|
||||||
|
public IngresosBrutos Reactivate()
|
||||||
|
=> new(Id, Provincia, Descripcion, Alicuota, true,
|
||||||
|
VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, DateTime.UtcNow);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cierra la vigencia seteando VigenciaHasta. Usado por el handler de NuevaVersion.
|
||||||
|
/// </summary>
|
||||||
|
public IngresosBrutos CerrarVigencia(DateOnly vigenciaHasta)
|
||||||
|
=> new(Id, Provincia, Descripcion, Alicuota, Activo,
|
||||||
|
VigenciaDesde, vigenciaHasta, PredecesorId, FechaCreacion, DateTime.UtcNow);
|
||||||
|
|
||||||
|
// ── Private helpers ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static void ValidateAlicuota(decimal alicuota, string paramName)
|
||||||
|
{
|
||||||
|
if (alicuota < 0m || alicuota > 100m)
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Alicuota ({alicuota}) debe estar entre 0 y 100.",
|
||||||
|
paramName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateVigencias(DateOnly desde, DateOnly? hasta)
|
||||||
|
{
|
||||||
|
if (hasta.HasValue && hasta.Value < desde)
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"VigenciaHasta ({hasta}) no puede ser anterior a VigenciaDesde ({desde}).",
|
||||||
|
"vigenciaHasta");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user