feat(bd): V018 crea dbo.Product + SqlTestFixture consolida V018 + permisos catalogo (PRD-002 W6)
This commit is contained in:
@@ -63,6 +63,9 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
// V017 (PRD-001): ensure dbo.ProductType + temporal + permiso 'catalogo:tipos:gestionar'.
|
||||
await EnsureV017SchemaAsync();
|
||||
|
||||
// V018 (PRD-002): ensure dbo.Product + temporal + permiso 'catalogo:productos:gestionar'.
|
||||
await EnsureV018SchemaAsync();
|
||||
|
||||
_respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions
|
||||
{
|
||||
DbAdapter = DbAdapter.SqlServer,
|
||||
@@ -91,6 +94,8 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
new Respawn.Graph.Table("dbo", "Rubro_History"),
|
||||
// PRD-001 (V017): ProductType es temporal — history no puede deletearse directo.
|
||||
new Respawn.Graph.Table("dbo", "ProductType_History"),
|
||||
// PRD-002 (V018): Product es temporal — history no puede deletearse directo.
|
||||
new Respawn.Graph.Table("dbo", "Product_History"),
|
||||
]
|
||||
});
|
||||
|
||||
@@ -213,7 +218,11 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
-- V014 (ADM-009): permiso para tablas fiscales
|
||||
('administracion:fiscal:gestionar', N'Gestionar tablas fiscales', N'Gestionar tablas fiscales (IVA, IIBB)', 'administracion'),
|
||||
-- V016 (CAT-001): permiso para gestionar árbol de rubros
|
||||
('catalogo:rubros:gestionar', N'Gestionar rubros del catálogo', N'Crear, editar, mover y desactivar rubros del árbol de catálogo comercial', 'catalogo')
|
||||
('catalogo:rubros:gestionar', N'Gestionar rubros del catálogo', N'Crear, editar, mover y desactivar rubros del árbol de catálogo comercial', 'catalogo'),
|
||||
-- V017 (PRD-001): permiso para gestionar tipos de producto
|
||||
('catalogo:tipos:gestionar', N'Gestionar tipos de producto', N'Crear, editar y desactivar ProductTypes del catálogo (flags + límites multimedia)', 'catalogo'),
|
||||
-- V018 (PRD-002): permiso para gestionar productos del catálogo
|
||||
('catalogo:productos:gestionar', N'Gestionar productos del catálogo', N'Crear, editar y desactivar productos del catálogo comercial', 'catalogo')
|
||||
) AS s (Codigo, Nombre, Descripcion, Modulo)
|
||||
ON t.Codigo = s.Codigo
|
||||
WHEN NOT MATCHED BY TARGET THEN
|
||||
@@ -261,6 +270,10 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
('admin', 'administracion:fiscal:gestionar'),
|
||||
-- V016 (CAT-001)
|
||||
('admin', 'catalogo:rubros:gestionar'),
|
||||
-- V017 (PRD-001)
|
||||
('admin', 'catalogo:tipos:gestionar'),
|
||||
-- V018 (PRD-002)
|
||||
('admin', 'catalogo:productos:gestionar'),
|
||||
('cajero', 'ventas:contado:crear'),
|
||||
('cajero', 'ventas:contado:modificar'),
|
||||
('cajero', 'ventas:contado:cobrar'),
|
||||
@@ -1011,4 +1024,102 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
||||
await _connection.ExecuteAsync(createUqIndex);
|
||||
await _connection.ExecuteAsync(createCoveringIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PRD-002 (V018): applies dbo.Product schema + temporal + filtered UQ + covering indexes
|
||||
/// idempotentemente. Mirrors V018__create_product.sql.
|
||||
/// Permiso 'catalogo:productos:gestionar' y asignación a admin se siembran
|
||||
/// desde SeedPermisosCanonicalAsync / SeedRolPermisosCanonicalAsync (post-respawn).
|
||||
/// </summary>
|
||||
public async Task EnsureV018SchemaAsync()
|
||||
{
|
||||
const string createProduct = """
|
||||
IF OBJECT_ID(N'dbo.Product', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.Product (
|
||||
Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Product PRIMARY KEY,
|
||||
Nombre NVARCHAR(300) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL,
|
||||
MedioId INT NOT NULL,
|
||||
ProductTypeId INT NOT NULL,
|
||||
RubroId INT NULL,
|
||||
BasePrice DECIMAL(18,4) NOT NULL,
|
||||
PriceDurationDays INT NULL,
|
||||
IsActive BIT NOT NULL CONSTRAINT DF_Product_IsActive DEFAULT(1),
|
||||
FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_Product_FechaCreacion DEFAULT(SYSUTCDATETIME()),
|
||||
FechaModificacion DATETIME2(3) NULL,
|
||||
CONSTRAINT FK_Product_Medio FOREIGN KEY (MedioId) REFERENCES dbo.Medio(Id) ON DELETE NO ACTION,
|
||||
CONSTRAINT FK_Product_ProductType FOREIGN KEY (ProductTypeId) REFERENCES dbo.ProductType(Id) ON DELETE NO ACTION,
|
||||
CONSTRAINT FK_Product_Rubro FOREIGN KEY (RubroId) REFERENCES dbo.Rubro(Id) ON DELETE NO ACTION,
|
||||
CONSTRAINT CK_Product_BasePrice_NonNegative CHECK (BasePrice >= 0),
|
||||
CONSTRAINT CK_Product_PriceDurationDays_Positive CHECK (PriceDurationDays IS NULL OR PriceDurationDays >= 1)
|
||||
);
|
||||
END
|
||||
""";
|
||||
|
||||
const string addProductPeriod = """
|
||||
IF COL_LENGTH('dbo.Product', 'ValidFrom') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Product
|
||||
ADD
|
||||
ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
|
||||
CONSTRAINT DF_Product_ValidFrom DEFAULT(SYSUTCDATETIME()),
|
||||
ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
|
||||
CONSTRAINT DF_Product_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')),
|
||||
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
|
||||
END
|
||||
""";
|
||||
|
||||
const string setProductVersioning = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Product') AND temporal_type = 2)
|
||||
BEGIN
|
||||
ALTER TABLE dbo.Product
|
||||
SET (SYSTEM_VERSIONING = ON (
|
||||
HISTORY_TABLE = dbo.Product_History,
|
||||
HISTORY_RETENTION_PERIOD = 10 YEARS
|
||||
));
|
||||
END
|
||||
""";
|
||||
|
||||
const string createUqIndex = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'UQ_Product_MedioId_ProductTypeId_Nombre_Active' AND object_id = OBJECT_ID('dbo.Product'))
|
||||
BEGIN
|
||||
CREATE UNIQUE INDEX UQ_Product_MedioId_ProductTypeId_Nombre_Active
|
||||
ON dbo.Product (MedioId, ProductTypeId, Nombre)
|
||||
WHERE IsActive = 1;
|
||||
END
|
||||
""";
|
||||
|
||||
const string createProductTypeIdx = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Product_ProductTypeId_IsActive' AND object_id = OBJECT_ID('dbo.Product'))
|
||||
BEGIN
|
||||
CREATE INDEX IX_Product_ProductTypeId_IsActive
|
||||
ON dbo.Product (ProductTypeId, IsActive);
|
||||
END
|
||||
""";
|
||||
|
||||
const string createMedioIdx = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Product_MedioId_IsActive' AND object_id = OBJECT_ID('dbo.Product'))
|
||||
BEGIN
|
||||
CREATE INDEX IX_Product_MedioId_IsActive
|
||||
ON dbo.Product (MedioId, IsActive);
|
||||
END
|
||||
""";
|
||||
|
||||
const string createRubroIdx = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Product_RubroId_IsActive' AND object_id = OBJECT_ID('dbo.Product'))
|
||||
BEGIN
|
||||
CREATE INDEX IX_Product_RubroId_IsActive
|
||||
ON dbo.Product (RubroId, IsActive)
|
||||
WHERE RubroId IS NOT NULL;
|
||||
END
|
||||
""";
|
||||
|
||||
await _connection.ExecuteAsync(createProduct);
|
||||
await _connection.ExecuteAsync(addProductPeriod);
|
||||
await _connection.ExecuteAsync(setProductVersioning);
|
||||
await _connection.ExecuteAsync(createUqIndex);
|
||||
await _connection.ExecuteAsync(createProductTypeIdx);
|
||||
await _connection.ExecuteAsync(createMedioIdx);
|
||||
await _connection.ExecuteAsync(createRubroIdx);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user