feat(adm-009): V014 seed 4 TipoDeIva + 24 IngresosBrutos + permiso fiscal:gestionar

This commit is contained in:
2026-04-17 17:41:25 -03:00
parent 58ff15a0c0
commit f4bd84c3f1
3 changed files with 218 additions and 9 deletions

View File

@@ -44,6 +44,9 @@ public sealed class SqlTestFixture : IAsyncLifetime
// IMAC asigna numeros AFIP, no nosotros — ver memoria architecture/facturacion-imac-numeracion).
await EnsureV013SchemaAsync();
// V014 (ADM-009): ensure dbo.TipoDeIva + dbo.IngresosBrutos + temporal + seed + permiso fiscal.
await EnsureV014SchemaAsync();
_respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions
{
DbAdapter = DbAdapter.SqlServer,
@@ -62,6 +65,12 @@ public sealed class SqlTestFixture : IAsyncLifetime
new Respawn.Graph.Table("dbo", "Medio_History"),
new Respawn.Graph.Table("dbo", "Seccion_History"),
new Respawn.Graph.Table("dbo", "PuntoDeVenta_History"),
// ADM-009 (V014): TipoDeIva + IngresosBrutos son temporales.
new Respawn.Graph.Table("dbo", "TipoDeIva_History"),
new Respawn.Graph.Table("dbo", "IngresosBrutos_History"),
// Seed de TipoDeIva e IngresosBrutos son datos de referencia — no limpiar con Respawn.
new Respawn.Graph.Table("dbo", "TipoDeIva"),
new Respawn.Graph.Table("dbo", "IngresosBrutos"),
]
});
@@ -173,7 +182,9 @@ public sealed class SqlTestFixture : IAsyncLifetime
-- V011 (ADM-001): permiso para CRUD de Secciones
('administracion:secciones:gestionar', N'Gestionar secciones por medio', N'Crear, editar y desactivar secciones de un medio','administracion'),
-- V013 (ADM-008): permiso para CRUD de Puntos de Venta
('administracion:puntos_de_venta:gestionar', N'Gestionar puntos de venta', N'Crear, editar y desactivar puntos de venta AFIP','administracion')
('administracion:puntos_de_venta:gestionar', N'Gestionar puntos de venta', N'Crear, editar y desactivar puntos de venta AFIP','administracion'),
-- V014 (ADM-009): permiso para tablas fiscales
('administracion:fiscal:gestionar', N'Gestionar tablas fiscales', N'Gestionar tablas fiscales (IVA, IIBB)', 'administracion')
) AS s (Codigo, Nombre, Descripcion, Modulo)
ON t.Codigo = s.Codigo
WHEN NOT MATCHED BY TARGET THEN
@@ -217,6 +228,8 @@ public sealed class SqlTestFixture : IAsyncLifetime
('admin', 'administracion:secciones:gestionar'),
-- V013 (ADM-008)
('admin', 'administracion:puntos_de_venta:gestionar'),
-- V014 (ADM-009)
('admin', 'administracion:fiscal:gestionar'),
('cajero', 'ventas:contado:crear'),
('cajero', 'ventas:contado:modificar'),
('cajero', 'ventas:contado:cobrar'),
@@ -567,4 +580,197 @@ public sealed class SqlTestFixture : IAsyncLifetime
await _connection.ExecuteAsync(addConstraint);
await _connection.ExecuteAsync(migrateRows);
}
/// <summary>
/// ADM-009 (V014): applies dbo.TipoDeIva + dbo.IngresosBrutos schema + temporal tables + seed + permiso fiscal.
/// Mirrors V014__create_tablas_fiscales.sql (idempotente).
/// Permiso y asignacion se siembran desde SeedPermisosCanonicalAsync / SeedRolPermisosCanonicalAsync.
/// Seed de TipoDeIva e IngresosBrutos se aplica aqui (datos de referencia estables).
/// </summary>
private async Task EnsureV014SchemaAsync()
{
// ── 1. dbo.TipoDeIva ─────────────────────────────────────────────────
const string createTipoDeIva = """
IF OBJECT_ID(N'dbo.TipoDeIva', N'U') IS NULL
BEGIN
CREATE TABLE dbo.TipoDeIva (
Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_TipoDeIva PRIMARY KEY,
Codigo VARCHAR(32) NOT NULL,
Descripcion NVARCHAR(100) NOT NULL,
Porcentaje DECIMAL(5,2) NOT NULL,
AplicaIVA BIT NOT NULL,
Activo BIT NOT NULL CONSTRAINT DF_TipoDeIva_Activo DEFAULT(1),
VigenciaDesde DATE NOT NULL,
VigenciaHasta DATE NULL,
PredecesorId INT NULL,
FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_TipoDeIva_FechaCreacion DEFAULT(SYSUTCDATETIME()),
FechaModificacion DATETIME2(3) NULL,
CONSTRAINT CK_TipoDeIva_Porcentaje CHECK (Porcentaje >= 0 AND Porcentaje <= 100),
CONSTRAINT CK_TipoDeIva_Vigencia CHECK (VigenciaHasta IS NULL OR VigenciaHasta >= VigenciaDesde),
CONSTRAINT UQ_TipoDeIva_Codigo_Vigencia UNIQUE (Codigo, VigenciaDesde),
CONSTRAINT FK_TipoDeIva_Predecesor FOREIGN KEY (PredecesorId) REFERENCES dbo.TipoDeIva(Id)
);
END
""";
const string createTipoDeIvaIdx1 = """
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_TipoDeIva_Codigo_VigenciaDesde' AND object_id = OBJECT_ID('dbo.TipoDeIva'))
CREATE INDEX IX_TipoDeIva_Codigo_VigenciaDesde ON dbo.TipoDeIva(Codigo, VigenciaDesde DESC);
""";
const string createTipoDeIvaIdx2 = """
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_TipoDeIva_PredecesorId' AND object_id = OBJECT_ID('dbo.TipoDeIva'))
CREATE INDEX IX_TipoDeIva_PredecesorId ON dbo.TipoDeIva(PredecesorId) WHERE PredecesorId IS NOT NULL;
""";
const string addTipoDeIvaPeriod = """
IF COL_LENGTH('dbo.TipoDeIva', 'ValidFrom') IS NULL
BEGIN
ALTER TABLE dbo.TipoDeIva
ADD
ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
CONSTRAINT DF_TipoDeIva_ValidFrom DEFAULT(SYSUTCDATETIME()),
ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
CONSTRAINT DF_TipoDeIva_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')),
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
END
""";
const string setTipoDeIvaVersioning = """
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.TipoDeIva') AND temporal_type = 2)
BEGIN
ALTER TABLE dbo.TipoDeIva
SET (SYSTEM_VERSIONING = ON (
HISTORY_TABLE = dbo.TipoDeIva_History,
HISTORY_RETENTION_PERIOD = 10 YEARS
));
END
""";
// ── 2. dbo.IngresosBrutos ────────────────────────────────────────────
const string createIIBB = """
IF OBJECT_ID(N'dbo.IngresosBrutos', N'U') IS NULL
BEGIN
CREATE TABLE dbo.IngresosBrutos (
Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_IngresosBrutos PRIMARY KEY,
Provincia VARCHAR(50) NOT NULL,
Descripcion NVARCHAR(100) NOT NULL,
Alicuota DECIMAL(5,2) NOT NULL,
Activo BIT NOT NULL CONSTRAINT DF_IIBB_Activo DEFAULT(1),
VigenciaDesde DATE NOT NULL,
VigenciaHasta DATE NULL,
PredecesorId INT NULL,
FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_IIBB_FechaCreacion DEFAULT(SYSUTCDATETIME()),
FechaModificacion DATETIME2(3) NULL,
CONSTRAINT CK_IIBB_Alicuota CHECK (Alicuota >= 0 AND Alicuota <= 100),
CONSTRAINT CK_IIBB_Vigencia CHECK (VigenciaHasta IS NULL OR VigenciaHasta >= VigenciaDesde),
CONSTRAINT UQ_IIBB_Provincia_Vigencia UNIQUE (Provincia, VigenciaDesde),
CONSTRAINT FK_IIBB_Predecesor FOREIGN KEY (PredecesorId) REFERENCES dbo.IngresosBrutos(Id)
);
END
""";
const string createIIBBIdx1 = """
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_IIBB_Provincia_VigenciaDesde' AND object_id = OBJECT_ID('dbo.IngresosBrutos'))
CREATE INDEX IX_IIBB_Provincia_VigenciaDesde ON dbo.IngresosBrutos(Provincia, VigenciaDesde DESC);
""";
const string createIIBBIdx2 = """
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_IIBB_PredecesorId' AND object_id = OBJECT_ID('dbo.IngresosBrutos'))
CREATE INDEX IX_IIBB_PredecesorId ON dbo.IngresosBrutos(PredecesorId) WHERE PredecesorId IS NOT NULL;
""";
const string addIIBBPeriod = """
IF COL_LENGTH('dbo.IngresosBrutos', 'ValidFrom') IS NULL
BEGIN
ALTER TABLE dbo.IngresosBrutos
ADD
ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
CONSTRAINT DF_IIBB_ValidFrom DEFAULT(SYSUTCDATETIME()),
ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
CONSTRAINT DF_IIBB_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')),
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
END
""";
const string setIIBBVersioning = """
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.IngresosBrutos') AND temporal_type = 2)
BEGIN
ALTER TABLE dbo.IngresosBrutos
SET (SYSTEM_VERSIONING = ON (
HISTORY_TABLE = dbo.IngresosBrutos_History,
HISTORY_RETENTION_PERIOD = 10 YEARS
));
END
""";
// ── 3. Seed TipoDeIva ────────────────────────────────────────────────
const string seedTipoDeIva = """
SET QUOTED_IDENTIFIER ON;
MERGE dbo.TipoDeIva AS t
USING (VALUES
('EXENTO', N'Exento de IVA', CAST(0 AS DECIMAL(5,2)), CAST(0 AS BIT), CAST('2020-01-01' AS DATE)),
('NO_GRAVADO', N'No gravado', CAST(0 AS DECIMAL(5,2)), CAST(0 AS BIT), CAST('2020-01-01' AS DATE)),
('IVA_105', N'IVA alicuota diferencial 10.5%', CAST(10.5 AS DECIMAL(5,2)), CAST(1 AS BIT), CAST('2020-01-01' AS DATE)),
('IVA_21', N'IVA alicuota general 21%', CAST(21 AS DECIMAL(5,2)), CAST(1 AS BIT), CAST('2020-01-01' AS DATE))
) AS s (Codigo, Descripcion, Porcentaje, AplicaIVA, VigenciaDesde)
ON t.Codigo = s.Codigo AND t.PredecesorId IS NULL
WHEN NOT MATCHED BY TARGET THEN
INSERT (Codigo, Descripcion, Porcentaje, AplicaIVA, Activo, VigenciaDesde, VigenciaHasta, PredecesorId)
VALUES (s.Codigo, s.Descripcion, s.Porcentaje, s.AplicaIVA, 1, s.VigenciaDesde, NULL, NULL);
""";
// ── 4. Seed IngresosBrutos ────────────────────────────────────────────
// 24 filas: 23 provincias INDEC + CABA. Alicuota=0 placeholder.
const string seedIIBB = """
SET QUOTED_IDENTIFIER ON;
MERGE dbo.IngresosBrutos AS t
USING (VALUES
('BUENOS_AIRES', N'Ingresos Brutos - Buenos Aires'),
('CABA', N'Ingresos Brutos - Ciudad Autonoma de Buenos Aires'),
('CATAMARCA', N'Ingresos Brutos - Catamarca'),
('CHACO', N'Ingresos Brutos - Chaco'),
('CHUBUT', N'Ingresos Brutos - Chubut'),
('CORDOBA', N'Ingresos Brutos - Cordoba'),
('CORRIENTES', N'Ingresos Brutos - Corrientes'),
('ENTRE_RIOS', N'Ingresos Brutos - Entre Rios'),
('FORMOSA', N'Ingresos Brutos - Formosa'),
('JUJUY', N'Ingresos Brutos - Jujuy'),
('LA_PAMPA', N'Ingresos Brutos - La Pampa'),
('LA_RIOJA', N'Ingresos Brutos - La Rioja'),
('MENDOZA', N'Ingresos Brutos - Mendoza'),
('MISIONES', N'Ingresos Brutos - Misiones'),
('NEUQUEN', N'Ingresos Brutos - Neuquen'),
('RIO_NEGRO', N'Ingresos Brutos - Rio Negro'),
('SALTA', N'Ingresos Brutos - Salta'),
('SAN_JUAN', N'Ingresos Brutos - San Juan'),
('SAN_LUIS', N'Ingresos Brutos - San Luis'),
('SANTA_CRUZ', N'Ingresos Brutos - Santa Cruz'),
('SANTA_FE', N'Ingresos Brutos - Santa Fe'),
('SANTIAGO_DEL_ESTERO', N'Ingresos Brutos - Santiago del Estero'),
('TIERRA_DEL_FUEGO', N'Ingresos Brutos - Tierra del Fuego'),
('TUCUMAN', N'Ingresos Brutos - Tucuman')
) AS s (Provincia, Descripcion)
ON t.Provincia = s.Provincia AND t.PredecesorId IS NULL
WHEN NOT MATCHED BY TARGET THEN
INSERT (Provincia, Descripcion, Alicuota, Activo, VigenciaDesde, VigenciaHasta, PredecesorId)
VALUES (s.Provincia, s.Descripcion, CAST(0 AS DECIMAL(5,2)), 1, CAST('2020-01-01' AS DATE), NULL, NULL);
""";
// Apply in order
await _connection.ExecuteAsync(createTipoDeIva);
await _connection.ExecuteAsync(createTipoDeIvaIdx1);
await _connection.ExecuteAsync(createTipoDeIvaIdx2);
await _connection.ExecuteAsync(addTipoDeIvaPeriod);
await _connection.ExecuteAsync(setTipoDeIvaVersioning);
await _connection.ExecuteAsync(createIIBB);
await _connection.ExecuteAsync(createIIBBIdx1);
await _connection.ExecuteAsync(createIIBBIdx2);
await _connection.ExecuteAsync(addIIBBPeriod);
await _connection.ExecuteAsync(setIIBBVersioning);
await _connection.ExecuteAsync(seedTipoDeIva);
await _connection.ExecuteAsync(seedIIBB);
// Permiso 'administracion:fiscal:gestionar' y asignacion a admin se siembran
// desde SeedPermisosCanonicalAsync / SeedRolPermisosCanonicalAsync (post-respawn).
}
}