From de70152d3e7721c3e7a3488f286700883e2c9b60 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Sun, 19 Apr 2026 09:34:23 -0300 Subject: [PATCH] feat(bd): V017 crea dbo.ProductType con SYSTEM_VERSIONING + permiso catalogo:tipos:gestionar (PRD-001) --- database/README.md | 1 + database/migrations/V017_ROLLBACK.sql | 71 ++++++++ .../migrations/V017__create_product_type.sql | 158 ++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 database/migrations/V017_ROLLBACK.sql create mode 100644 database/migrations/V017__create_product_type.sql diff --git a/database/README.md b/database/README.md index d505f3e..98c7b02 100644 --- a/database/README.md +++ b/database/README.md @@ -33,6 +33,7 @@ database/ | V014 | `V014__create_tablas_fiscales.sql` | ADM-009 | TiposDeIva + IngresosBrutos (versioning por cadena) + permisos fiscales | | V015 | `V015__create_local_timezone_views.sql` | UDT-011 | Vistas admin con OccurredAt convertido a hora Argentina | | **V016** | **`V016__create_rubro.sql`** | **CAT-001** | **Rubro (adjacency list, temporal 10y) + permiso `catalogo:rubros:gestionar`** | +| **V017** | **`V017__create_product_type.sql`** | **PRD-001** | **ProductType (flags + multimedia limits, temporal 10y) + permiso `catalogo:tipos:gestionar`** | ## Convenciones diff --git a/database/migrations/V017_ROLLBACK.sql b/database/migrations/V017_ROLLBACK.sql new file mode 100644 index 0000000..4ec52cf --- /dev/null +++ b/database/migrations/V017_ROLLBACK.sql @@ -0,0 +1,71 @@ +-- V017_ROLLBACK.sql +-- Reversa de V017__create_product_type.sql. +-- PRD-001: ProductType rollback. +-- +-- ADVERTENCIA: Si PRD-002 ya fue mergeado (IProductQueryRepository real), hacer rollback +-- de PRD-002 primero (la interfaz es removida por esta rollback). +-- +-- Idempotente: cada paso usa IF EXISTS guards. + +SET QUOTED_IDENTIFIER ON; +SET ANSI_NULLS ON; +SET NOCOUNT ON; +GO + +-- 1. Desactivar SYSTEM_VERSIONING +IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.ProductType') AND temporal_type = 2) +BEGIN + ALTER TABLE dbo.ProductType SET (SYSTEM_VERSIONING = OFF); + PRINT 'ProductType: SYSTEM_VERSIONING = OFF.'; +END +GO + +-- 2. Remover PERIOD FOR SYSTEM_TIME +IF EXISTS (SELECT 1 FROM sys.periods WHERE object_id = OBJECT_ID('dbo.ProductType')) +BEGIN + ALTER TABLE dbo.ProductType DROP PERIOD FOR SYSTEM_TIME; + PRINT 'ProductType: PERIOD FOR SYSTEM_TIME dropped.'; +END +GO + +-- 3. Remover columnas HIDDEN + default constraints +IF COL_LENGTH('dbo.ProductType', 'ValidFrom') IS NOT NULL +BEGIN + ALTER TABLE dbo.ProductType DROP CONSTRAINT IF EXISTS DF_ProductType_ValidFrom; + ALTER TABLE dbo.ProductType DROP CONSTRAINT IF EXISTS DF_ProductType_ValidTo; + ALTER TABLE dbo.ProductType DROP COLUMN ValidFrom, ValidTo; + PRINT 'ProductType: ValidFrom/ValidTo columns dropped.'; +END +GO + +-- 4. Drop history table +IF OBJECT_ID(N'dbo.ProductType_History', N'U') IS NOT NULL +BEGIN + DROP TABLE dbo.ProductType_History; + PRINT 'Table dbo.ProductType_History dropped.'; +END +GO + +-- 5. Drop main table +IF OBJECT_ID(N'dbo.ProductType', N'U') IS NOT NULL +BEGIN + DROP TABLE dbo.ProductType; + PRINT 'Table dbo.ProductType dropped.'; +END +GO + +-- 6. Remover RolPermiso para catalogo:tipos:gestionar +DELETE rp FROM dbo.RolPermiso rp + JOIN dbo.Permiso p ON p.Id = rp.PermisoId + WHERE p.Codigo = 'catalogo:tipos:gestionar'; +PRINT 'RolPermiso rows for catalogo:tipos:gestionar deleted.'; +GO + +-- 7. Remover Permiso +DELETE FROM dbo.Permiso WHERE Codigo = 'catalogo:tipos:gestionar'; +PRINT 'Permiso catalogo:tipos:gestionar deleted.'; +GO + +PRINT ''; +PRINT 'V017 rolled back successfully.'; +GO diff --git a/database/migrations/V017__create_product_type.sql b/database/migrations/V017__create_product_type.sql new file mode 100644 index 0000000..0477778 --- /dev/null +++ b/database/migrations/V017__create_product_type.sql @@ -0,0 +1,158 @@ +-- V017__create_product_type.sql +-- PRD-001: ProductType — tipología dinámica de productos con flags de comportamiento + límites multimedia. +-- +-- Cambios: +-- 1. dbo.ProductType (flags + multimedia limits, SYSTEM_VERSIONING ON, retention 10 años). +-- 2. Índice filtrado unique UQ_ProductType_Nombre_Activo (unicidad CI entre activos). +-- 3. Índice cubriente IX_ProductType_IsActive_Cover. +-- 4. Permiso 'catalogo:tipos:gestionar' + asignación a rol 'admin'. +-- +-- Patrón: V016 (dbo.Rubro con SYSTEM_VERSIONING + PAGE compression + MERGE permisos). +-- Idempotente: seguro para re-ejecutar. +-- Reversa: V017_ROLLBACK.sql. +-- Run on: SIGCM2 (dev) y SIGCM2_Test (integration tests). +-- +-- Notas: +-- - SIN seed de datos — PRD-008 (V018) seedea los 12 tipos legacy. +-- - SIN FK desde dbo.Product — PRD-002 agrega ALTER TABLE con FK. +-- - Invariante aplicada en Application: si AllowImages=0, los 4 campos multimedia son NULL (handler normaliza). +-- - MaxImages/MaxImageSizeMB/MaxImageWidth/MaxImageHeight: NULL = sin límite; >=1 = tope (validator rechaza <=0). +-- - Desviación del UDT: "0 = ilimitado" → usamos NULL (convención canónica). Ver PRD-001 archive-report. +-- +-- SDD Design: engram sdd/prd-001-product-type-flags-multimedia/design + +SET QUOTED_IDENTIFIER ON; +SET ANSI_NULLS ON; +SET NOCOUNT ON; +GO + +-- ═══════════════════════════════════════════════════════════════════════ +-- 1. dbo.ProductType +-- ═══════════════════════════════════════════════════════════════════════ + +IF OBJECT_ID(N'dbo.ProductType', N'U') IS NULL +BEGIN + CREATE TABLE dbo.ProductType ( + Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_ProductType PRIMARY KEY, + Nombre NVARCHAR(200) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL, + + -- Flags de comportamiento + HasDuration BIT NOT NULL CONSTRAINT DF_ProductType_HasDuration DEFAULT(0), + RequiresText BIT NOT NULL CONSTRAINT DF_ProductType_RequiresText DEFAULT(0), + RequiresCategory BIT NOT NULL CONSTRAINT DF_ProductType_RequiresCategory DEFAULT(0), + IsBundle BIT NOT NULL CONSTRAINT DF_ProductType_IsBundle DEFAULT(0), + + -- Multimedia (AllowImages=0 => handler normaliza los 4 siguientes a NULL) + AllowImages BIT NOT NULL CONSTRAINT DF_ProductType_AllowImages DEFAULT(0), + MaxImages INT NULL, -- NULL = sin límite; >=1 tope (validator rechaza <=0) + MaxImageSizeMB DECIMAL(10,2) NULL, -- NULL = sin límite; DECIMAL(10,2) permite 0.5 MB, 2.75 MB + MaxImageWidth INT NULL, -- NULL = sin límite; >=1 px + MaxImageHeight INT NULL, -- NULL = sin límite; >=1 px + + -- Lifecycle + IsActive BIT NOT NULL CONSTRAINT DF_ProductType_IsActive DEFAULT(1), + FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_ProductType_FechaCreacion DEFAULT(SYSUTCDATETIME()), + FechaModificacion DATETIME2(3) NULL + ); + PRINT 'Table dbo.ProductType created.'; +END +ELSE + PRINT 'Table dbo.ProductType already exists — skip.'; +GO + +-- ═══════════════════════════════════════════════════════════════════════ +-- 2. SYSTEM_VERSIONING — ProductType +-- ═══════════════════════════════════════════════════════════════════════ + +IF COL_LENGTH('dbo.ProductType', 'ValidFrom') IS NULL +BEGIN + ALTER TABLE dbo.ProductType + ADD + ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL + CONSTRAINT DF_ProductType_ValidFrom DEFAULT(SYSUTCDATETIME()), + ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL + CONSTRAINT DF_ProductType_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')), + PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo); + PRINT 'ProductType: PERIOD FOR SYSTEM_TIME added.'; +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.ProductType') AND temporal_type = 2) +BEGIN + ALTER TABLE dbo.ProductType + SET (SYSTEM_VERSIONING = ON ( + HISTORY_TABLE = dbo.ProductType_History, + HISTORY_RETENTION_PERIOD = 10 YEARS + )); + PRINT 'ProductType: SYSTEM_VERSIONING = ON (history: dbo.ProductType_History, retention: 10 years).'; +END +ELSE + PRINT 'ProductType: SYSTEM_VERSIONING already ON — skip.'; +GO + +IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'ProductType_History' AND schema_id = SCHEMA_ID('dbo')) + AND NOT EXISTS ( + SELECT 1 FROM sys.partitions p + JOIN sys.tables t ON t.object_id = p.object_id + WHERE t.name = 'ProductType_History' AND p.data_compression = 2 + ) +BEGIN + ALTER TABLE dbo.ProductType_History REBUILD WITH (DATA_COMPRESSION = PAGE); + PRINT 'ProductType_History: rebuilt with PAGE compression.'; +END +GO + +-- ═══════════════════════════════════════════════════════════════════════ +-- 3. Índices +-- ═══════════════════════════════════════════════════════════════════════ + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'UQ_ProductType_Nombre_Activo' AND object_id = OBJECT_ID('dbo.ProductType')) +BEGIN + CREATE UNIQUE INDEX UQ_ProductType_Nombre_Activo + ON dbo.ProductType(Nombre) + WHERE IsActive = 1; + PRINT 'Index UQ_ProductType_Nombre_Activo created.'; +END +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_ProductType_IsActive_Cover' AND object_id = OBJECT_ID('dbo.ProductType')) +BEGIN + CREATE INDEX IX_ProductType_IsActive_Cover + ON dbo.ProductType(IsActive) + INCLUDE (Nombre, HasDuration, RequiresText, RequiresCategory, IsBundle, AllowImages); + PRINT 'Index IX_ProductType_IsActive_Cover created.'; +END +GO + +-- ═══════════════════════════════════════════════════════════════════════ +-- 4. Permiso: catalogo:tipos:gestionar + asignación a rol 'admin' +-- ═══════════════════════════════════════════════════════════════════════ + +MERGE dbo.Permiso AS t +USING (VALUES + ('catalogo:tipos:gestionar', + N'Gestionar tipos de producto', + N'Crear, editar y desactivar ProductTypes del catálogo (flags + límites multimedia)', + 'catalogo') +) AS s (Codigo, Nombre, Descripcion, Modulo) +ON t.Codigo = s.Codigo +WHEN NOT MATCHED BY TARGET THEN + INSERT (Codigo, Nombre, Descripcion, Modulo) + VALUES (s.Codigo, s.Nombre, s.Descripcion, s.Modulo); +GO + +MERGE dbo.RolPermiso AS t +USING ( + SELECT r.Id AS RolId, p.Id AS PermisoId + FROM (VALUES ('admin', 'catalogo:tipos:gestionar')) AS x (RolCodigo, PermisoCodigo) + JOIN dbo.Rol r ON r.Codigo = x.RolCodigo + JOIN dbo.Permiso p ON p.Codigo = x.PermisoCodigo +) AS s ON t.RolId = s.RolId AND t.PermisoId = s.PermisoId +WHEN NOT MATCHED BY TARGET THEN + INSERT (RolId, PermisoId) VALUES (s.RolId, s.PermisoId); +GO + +PRINT ''; +PRINT 'V017 applied — dbo.ProductType (temporal, retention 10y) + permiso catalogo:tipos:gestionar.'; +PRINT 'Next: V018 (PRD-008 — seed 12 tipos legacy).'; +GO