feat(bd): V021 crea dbo.ChargeableCharConfig + SPs + índices (PRC-001)
This commit is contained in:
79
database/migrations/V021_ROLLBACK.sql
Normal file
79
database/migrations/V021_ROLLBACK.sql
Normal file
@@ -0,0 +1,79 @@
|
||||
-- V021_ROLLBACK.sql
|
||||
-- PRC-001: Reversa de V021__create_chargeable_char_config.sql.
|
||||
--
|
||||
-- Pasos:
|
||||
-- 1. Deshabilita SYSTEM_VERSIONING en dbo.ChargeableCharConfig (requerido antes de DROP TABLE).
|
||||
-- 2. Elimina el PERIOD FOR SYSTEM_TIME y las columnas hidden SysStartTime/SysEndTime.
|
||||
-- 3. Drop de dbo.ChargeableCharConfig_History.
|
||||
-- 4. Drop de dbo.ChargeableCharConfig (constraints + índices en cascada).
|
||||
-- 5. Drop de dbo.usp_ChargeableCharConfig_InsertWithClose.
|
||||
-- 6. Drop de dbo.usp_ChargeableCharConfig_GetActiveForMedio.
|
||||
--
|
||||
-- ADVERTENCIA: destruye toda la configuración de caracteres tasables. Solo DEV/TEST.
|
||||
-- Run on: SIGCM2 (dev), SIGCM2_Test_App, SIGCM2_Test_Api.
|
||||
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
SET ANSI_NULLS ON;
|
||||
SET NOCOUNT ON;
|
||||
GO
|
||||
|
||||
-- 1. Deshabilita SYSTEM_VERSIONING (imprescindible antes de DROP TABLE temporal).
|
||||
IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.ChargeableCharConfig') AND temporal_type = 2)
|
||||
BEGIN
|
||||
ALTER TABLE dbo.ChargeableCharConfig SET (SYSTEM_VERSIONING = OFF);
|
||||
PRINT 'ChargeableCharConfig: SYSTEM_VERSIONING = OFF.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- 2. Elimina el PERIOD y las hidden cols.
|
||||
IF COL_LENGTH('dbo.ChargeableCharConfig', 'SysStartTime') IS NOT NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.ChargeableCharConfig
|
||||
DROP PERIOD FOR SYSTEM_TIME;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = 'DF_ChargeableCharConfig_SysStartTime')
|
||||
ALTER TABLE dbo.ChargeableCharConfig DROP CONSTRAINT DF_ChargeableCharConfig_SysStartTime;
|
||||
IF EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = 'DF_ChargeableCharConfig_SysEndTime')
|
||||
ALTER TABLE dbo.ChargeableCharConfig DROP CONSTRAINT DF_ChargeableCharConfig_SysEndTime;
|
||||
|
||||
ALTER TABLE dbo.ChargeableCharConfig DROP COLUMN SysStartTime;
|
||||
ALTER TABLE dbo.ChargeableCharConfig DROP COLUMN SysEndTime;
|
||||
PRINT 'ChargeableCharConfig: PERIOD + hidden cols dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- 3. Drop de la history table.
|
||||
IF OBJECT_ID(N'dbo.ChargeableCharConfig_History', N'U') IS NOT NULL
|
||||
BEGIN
|
||||
DROP TABLE dbo.ChargeableCharConfig_History;
|
||||
PRINT 'Table dbo.ChargeableCharConfig_History dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- 4. Drop de la tabla principal.
|
||||
IF OBJECT_ID(N'dbo.ChargeableCharConfig', N'U') IS NOT NULL
|
||||
BEGIN
|
||||
DROP TABLE dbo.ChargeableCharConfig;
|
||||
PRINT 'Table dbo.ChargeableCharConfig dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- 5. Drop del SP InsertWithClose.
|
||||
IF OBJECT_ID(N'dbo.usp_ChargeableCharConfig_InsertWithClose', N'P') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE dbo.usp_ChargeableCharConfig_InsertWithClose;
|
||||
PRINT 'Procedure dbo.usp_ChargeableCharConfig_InsertWithClose dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- 6. Drop del SP GetActiveForMedio.
|
||||
IF OBJECT_ID(N'dbo.usp_ChargeableCharConfig_GetActiveForMedio', N'P') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE dbo.usp_ChargeableCharConfig_GetActiveForMedio;
|
||||
PRINT 'Procedure dbo.usp_ChargeableCharConfig_GetActiveForMedio dropped.';
|
||||
END
|
||||
GO
|
||||
|
||||
PRINT '';
|
||||
PRINT 'V021 rollback complete — dbo.ChargeableCharConfig, dbo.ChargeableCharConfig_History, usp_ChargeableCharConfig_InsertWithClose, usp_ChargeableCharConfig_GetActiveForMedio removed.';
|
||||
GO
|
||||
256
database/migrations/V021__create_chargeable_char_config.sql
Normal file
256
database/migrations/V021__create_chargeable_char_config.sql
Normal file
@@ -0,0 +1,256 @@
|
||||
-- V021__create_chargeable_char_config.sql
|
||||
-- PRC-001: ChargeableCharConfig — configuración de caracteres especiales tasables con vigencia civil.
|
||||
--
|
||||
-- Cambios:
|
||||
-- 1. dbo.ChargeableCharConfig (FK Medios NULL=global, SYSTEM_VERSIONING ON, retention 10 años).
|
||||
-- 2. Índices: filtered UX vigente por (MedioId,Symbol); cover IX para GetActiveForMedio.
|
||||
-- 3. SP dbo.usp_ChargeableCharConfig_InsertWithClose (SERIALIZABLE + UPDLOCK, forward-only).
|
||||
-- 4. SP dbo.usp_ChargeableCharConfig_GetActiveForMedio (CTE + ROW_NUMBER per-medio/global).
|
||||
--
|
||||
-- Patrón: V019 (SYSTEM_VERSIONING + PAGE compression + SERIALIZABLE SP).
|
||||
-- Idempotente: seguro para re-ejecutar.
|
||||
-- Reversa: V021_ROLLBACK.sql.
|
||||
-- Run on: SIGCM2 (dev), SIGCM2_Test_App, SIGCM2_Test_Api.
|
||||
--
|
||||
-- Notas:
|
||||
-- - SysStartTime/SysEndTime como hidden cols: evita colisión con business cols ValidFrom/ValidTo (D4).
|
||||
-- - DECIMAL(18,4) para PricePerUnit (mayor granularidad que ProductPrices) (D8).
|
||||
-- - MedioId NULL = global fallback; per-medio overrides global in GetActiveForMedio (D2/D6).
|
||||
-- - Forward-only estricto: THROW 50409 si new ValidFrom <= activo.ValidFrom (D9).
|
||||
-- - UX filtered WHERE ValidTo IS NULL: SQL Server trata (NULL,'$') como valor igual → enforza 1 vigente global (D7).
|
||||
-- - dbo.ChargeableCharConfig_History debe agregarse a TablesToIgnore en SqlTestFixture.cs (Respawn).
|
||||
--
|
||||
-- SDD Design: engram sdd/prc-001-word-counter-spike/design
|
||||
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
SET ANSI_NULLS ON;
|
||||
SET NOCOUNT ON;
|
||||
GO
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
-- 1. dbo.ChargeableCharConfig
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
IF OBJECT_ID(N'dbo.ChargeableCharConfig', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.ChargeableCharConfig (
|
||||
Id BIGINT IDENTITY(1,1) NOT NULL
|
||||
CONSTRAINT PK_ChargeableCharConfig PRIMARY KEY,
|
||||
MedioId INT NULL, -- NULL = global fallback
|
||||
Symbol NVARCHAR(4) NOT NULL,
|
||||
Category NVARCHAR(32) NOT NULL, -- enum-as-string: Currency/Percentage/Exclamation/Question/Other
|
||||
PricePerUnit DECIMAL(18,4) NOT NULL,
|
||||
ValidFrom DATE NOT NULL,
|
||||
ValidTo DATE NULL,
|
||||
IsActive BIT NOT NULL
|
||||
CONSTRAINT DF_ChargeableCharConfig_IsActive DEFAULT(1),
|
||||
FechaCreacion DATETIME2(3) NOT NULL
|
||||
CONSTRAINT DF_ChargeableCharConfig_FechaCreacion DEFAULT(SYSUTCDATETIME()),
|
||||
CONSTRAINT FK_ChargeableCharConfig_Medio
|
||||
FOREIGN KEY (MedioId) REFERENCES dbo.Medio(Id) ON DELETE NO ACTION,
|
||||
CONSTRAINT CK_ChargeableCharConfig_Price_Positive
|
||||
CHECK (PricePerUnit > 0),
|
||||
CONSTRAINT CK_ChargeableCharConfig_Symbol_NotEmpty
|
||||
CHECK (LEN(Symbol) > 0),
|
||||
CONSTRAINT CK_ChargeableCharConfig_ValidRange
|
||||
CHECK (ValidTo IS NULL OR ValidTo >= ValidFrom)
|
||||
);
|
||||
PRINT 'Table dbo.ChargeableCharConfig created.';
|
||||
END
|
||||
ELSE
|
||||
PRINT 'Table dbo.ChargeableCharConfig already exists — skip.';
|
||||
GO
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
-- 2. SYSTEM_VERSIONING — ChargeableCharConfig
|
||||
-- SysStartTime/SysEndTime para no colisionar con business cols ValidFrom/ValidTo (D4).
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
IF COL_LENGTH('dbo.ChargeableCharConfig', 'SysStartTime') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE dbo.ChargeableCharConfig
|
||||
ADD
|
||||
SysStartTime DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
|
||||
CONSTRAINT DF_ChargeableCharConfig_SysStartTime DEFAULT(SYSUTCDATETIME()),
|
||||
SysEndTime DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
|
||||
CONSTRAINT DF_ChargeableCharConfig_SysEndTime DEFAULT(CONVERT(DATETIME2(3),'9999-12-31 23:59:59.999')),
|
||||
PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime);
|
||||
PRINT 'ChargeableCharConfig: PERIOD FOR SYSTEM_TIME added (SysStartTime/SysEndTime).';
|
||||
END
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.ChargeableCharConfig') AND temporal_type = 2)
|
||||
BEGIN
|
||||
ALTER TABLE dbo.ChargeableCharConfig
|
||||
SET (SYSTEM_VERSIONING = ON (
|
||||
HISTORY_TABLE = dbo.ChargeableCharConfig_History,
|
||||
HISTORY_RETENTION_PERIOD = 10 YEARS
|
||||
));
|
||||
PRINT 'ChargeableCharConfig: SYSTEM_VERSIONING = ON (history: dbo.ChargeableCharConfig_History, retention: 10 years).';
|
||||
END
|
||||
ELSE
|
||||
PRINT 'ChargeableCharConfig: SYSTEM_VERSIONING already ON — skip.';
|
||||
GO
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'ChargeableCharConfig_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 = 'ChargeableCharConfig_History' AND p.data_compression = 2
|
||||
)
|
||||
BEGIN
|
||||
ALTER TABLE dbo.ChargeableCharConfig_History REBUILD WITH (DATA_COMPRESSION = PAGE);
|
||||
PRINT 'ChargeableCharConfig_History: rebuilt with PAGE compression.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
-- 3. Índices
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- Un único vigente por (MedioId, Symbol).
|
||||
-- SQL Server trata NULL como "distinto" en índices únicos: (NULL,'$') colisiona consigo mismo
|
||||
-- → enforza exactamente 1 vigente global por símbolo (D7).
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM sys.indexes
|
||||
WHERE name = 'UX_ChargeableCharConfig_Vigente'
|
||||
AND object_id = OBJECT_ID('dbo.ChargeableCharConfig')
|
||||
)
|
||||
BEGIN
|
||||
CREATE UNIQUE INDEX UX_ChargeableCharConfig_Vigente
|
||||
ON dbo.ChargeableCharConfig (MedioId, Symbol)
|
||||
WHERE ValidTo IS NULL;
|
||||
PRINT 'Index UX_ChargeableCharConfig_Vigente created.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- Cover para GetActiveForMedio y List.
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM sys.indexes
|
||||
WHERE name = 'IX_ChargeableCharConfig_Query'
|
||||
AND object_id = OBJECT_ID('dbo.ChargeableCharConfig')
|
||||
)
|
||||
BEGIN
|
||||
CREATE INDEX IX_ChargeableCharConfig_Query
|
||||
ON dbo.ChargeableCharConfig (MedioId, Symbol, ValidFrom, ValidTo)
|
||||
INCLUDE (PricePerUnit, IsActive, Category);
|
||||
PRINT 'Index IX_ChargeableCharConfig_Query created.';
|
||||
END
|
||||
GO
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
-- 4. SP — dbo.usp_ChargeableCharConfig_InsertWithClose
|
||||
-- Cierre atómico forward-only: SERIALIZABLE + UPDLOCK + HOLDLOCK.
|
||||
-- @MedioId NULL = global; existencia validada sólo cuando NOT NULL.
|
||||
-- THROW 50404: Medio not found.
|
||||
-- THROW 50409: ForwardOnly — new ValidFrom must be > active.ValidFrom.
|
||||
-- Params de salida: @NewId (BIGINT), @ClosedId (BIGINT — NULL si primer precio).
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
GO
|
||||
CREATE OR ALTER PROCEDURE dbo.usp_ChargeableCharConfig_InsertWithClose
|
||||
@MedioId INT = NULL,
|
||||
@Symbol NVARCHAR(4),
|
||||
@Category NVARCHAR(32),
|
||||
@PricePerUnit DECIMAL(18,4),
|
||||
@ValidFrom DATE,
|
||||
@NewId BIGINT OUTPUT,
|
||||
@ClosedId BIGINT OUTPUT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
||||
|
||||
BEGIN TRY
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- Validar MedioId sólo cuando se proporciona (NULL = global fallback siempre válido).
|
||||
IF @MedioId IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM dbo.Medio WITH (NOLOCK) WHERE Id = @MedioId)
|
||||
BEGIN
|
||||
ROLLBACK;
|
||||
THROW 50404, 'Medio not found', 1;
|
||||
END
|
||||
|
||||
-- Lee el vigente actual con bloqueo de rango para serialización.
|
||||
DECLARE @ActiveId BIGINT, @ActiveValidFrom DATE;
|
||||
SELECT TOP 1
|
||||
@ActiveId = Id,
|
||||
@ActiveValidFrom = ValidFrom
|
||||
FROM dbo.ChargeableCharConfig WITH (UPDLOCK, HOLDLOCK, ROWLOCK)
|
||||
WHERE ((@MedioId IS NULL AND MedioId IS NULL)
|
||||
OR (@MedioId IS NOT NULL AND MedioId = @MedioId))
|
||||
AND Symbol = @Symbol
|
||||
AND ValidTo IS NULL;
|
||||
|
||||
-- Forward-only estricto: new ValidFrom debe ser ESTRICTAMENTE mayor al activo.
|
||||
IF @ActiveId IS NOT NULL AND @ValidFrom <= @ActiveValidFrom
|
||||
BEGIN
|
||||
ROLLBACK;
|
||||
THROW 50409, 'ChargeableCharConfigForwardOnly: new ValidFrom must be > active.ValidFrom', 1;
|
||||
END
|
||||
|
||||
-- Cierra el vigente previo: ValidTo = ValidFrom(nuevo) - 1 día.
|
||||
IF @ActiveId IS NOT NULL
|
||||
BEGIN
|
||||
UPDATE dbo.ChargeableCharConfig
|
||||
SET ValidTo = DATEADD(DAY, -1, @ValidFrom)
|
||||
WHERE Id = @ActiveId;
|
||||
SET @ClosedId = @ActiveId;
|
||||
END
|
||||
ELSE
|
||||
SET @ClosedId = NULL;
|
||||
|
||||
-- Inserta el nuevo vigente.
|
||||
INSERT INTO dbo.ChargeableCharConfig
|
||||
(MedioId, Symbol, Category, PricePerUnit, ValidFrom, ValidTo, IsActive)
|
||||
VALUES
|
||||
(@MedioId, @Symbol, @Category, @PricePerUnit, @ValidFrom, NULL, 1);
|
||||
SET @NewId = SCOPE_IDENTITY();
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
|
||||
THROW;
|
||||
END CATCH
|
||||
END
|
||||
GO
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
-- 5. SP — dbo.usp_ChargeableCharConfig_GetActiveForMedio
|
||||
-- Resolución per-medio + global fallback: 1 fila por Symbol.
|
||||
-- CTE + ROW_NUMBER PARTITION BY Symbol ORDER BY per-medio(0) vs global(1).
|
||||
-- ═══════════════════════════════════════════════════════════════════════
|
||||
GO
|
||||
CREATE OR ALTER PROCEDURE dbo.usp_ChargeableCharConfig_GetActiveForMedio
|
||||
@MedioId INT,
|
||||
@AsOfDate DATE
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
WITH Candidates AS (
|
||||
SELECT
|
||||
Id, MedioId, Symbol, Category, PricePerUnit, ValidFrom, ValidTo, IsActive,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY Symbol
|
||||
ORDER BY
|
||||
CASE WHEN MedioId = @MedioId THEN 0 ELSE 1 END, -- prefer specific over global
|
||||
ValidFrom DESC
|
||||
) AS rn
|
||||
FROM dbo.ChargeableCharConfig
|
||||
WHERE IsActive = 1
|
||||
AND ValidFrom <= @AsOfDate
|
||||
AND (ValidTo IS NULL OR ValidTo >= @AsOfDate)
|
||||
AND (MedioId = @MedioId OR MedioId IS NULL)
|
||||
)
|
||||
SELECT Id, MedioId, Symbol, Category, PricePerUnit, ValidFrom, ValidTo, IsActive
|
||||
FROM Candidates
|
||||
WHERE rn = 1;
|
||||
END
|
||||
GO
|
||||
|
||||
PRINT '';
|
||||
PRINT 'V021 applied — dbo.ChargeableCharConfig (temporal, retention 10y) + UX_ChargeableCharConfig_Vigente + IX_ChargeableCharConfig_Query + usp_ChargeableCharConfig_InsertWithClose + usp_ChargeableCharConfig_GetActiveForMedio.';
|
||||
PRINT 'Next migration: V022 (seed ChargeableCharConfig).';
|
||||
GO
|
||||
Reference in New Issue
Block a user