① nút đính kèm 'Bảng so sánh' gate drafter+phase (cả 2 app) · ② gom 3 cặp dự án trùng dấu-cách trên PROD (5 phiếu+5 NS repoint, 3 ẩn mềm) + vá gốc chuẩn hóa Code +4 test. CI #407 PASS, bundle rotate ×2 verify 2-nguồn. Session log + merge script (docs/changelog/data-fixes) + STATUS/HANDOFF + garble tally ×3 + harvest investigator/reviewer/cicd. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 lines
4.9 KiB
Transact-SQL
100 lines
4.9 KiB
Transact-SQL
-- ============================================================================
|
|
-- Merge du an trung do khac DAU-CACH trong Code (giu ban KHONG-dau-cach).
|
|
-- "FLOCK 01" -> "FLOCK01" | "FLOCK 03" -> "FLOCK03" | "CAL 01" -> "CAL01"
|
|
--
|
|
-- AN-TOAN / IDEMPOTENT:
|
|
-- * Survivor = ban no-space (co trong seed => restart-durable). Retire ban space.
|
|
-- * Moi cap chay trong 1 TRANSACTION rieng; VERIFY 0 live-ref TRUOC khi an mem;
|
|
-- lech -> ROLLBACK cap do + in [ABORT], KHONG an mem.
|
|
-- * >1 ban song cung code (reseed cycle) -> [ABORT], can nguoi reconcile 1 GUID.
|
|
-- * Chay lai an-toan: cap da gop -> [SKIP] (khong con ban space song).
|
|
-- * PeWorkItemBudgets co UNIQUE (ProjectId,WorkItemId) filter IsDeleted=0:
|
|
-- repoint neu survivor CHUA co budget live cung WorkItem; nguoc lai (collision)
|
|
-- -> soft-delete ban space (giu survivor). KHONG xoa cung. DeletedBy = NULL.
|
|
-- * Refs la loose-Guid (KHONG co physical FK) -> khong vo thu tu; nhung DB KHONG
|
|
-- co luoi an toan => VERIFY la bat buoc.
|
|
-- Chay: sqlcmd -S <server> -d <db> -b -i merge-duplicate-projects.sql (ASCII -> khong can -f 65001)
|
|
-- ============================================================================
|
|
-- Filtered-unique index tren PeWorkItemBudgets => BAT BUOC QUOTED_IDENTIFIER ON +
|
|
-- ANSI_NULLS ON khi UPDATE. Set o batch rieng (GO) de ap cho connection, tranh
|
|
-- phu thuoc caller nho truyen -I.
|
|
SET QUOTED_IDENTIFIER ON;
|
|
SET ANSI_NULLS ON;
|
|
GO
|
|
SET NOCOUNT ON;
|
|
SET XACT_ABORT ON;
|
|
|
|
DECLARE @pairs TABLE (spaceCode nvarchar(50), keepCode nvarchar(50));
|
|
INSERT INTO @pairs (spaceCode, keepCode) VALUES
|
|
(N'FLOCK 01', N'FLOCK01'),
|
|
(N'FLOCK 03', N'FLOCK03'),
|
|
(N'CAL 01', N'CAL01');
|
|
|
|
DECLARE @spaceCode nvarchar(50), @keepCode nvarchar(50);
|
|
DECLARE @spaceId uniqueidentifier, @keepId uniqueidentifier;
|
|
DECLARE @spaceCnt int, @keepCnt int, @ct int, @pe int, @bgMove int, @bgCollide int, @liveRef int;
|
|
|
|
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR SELECT spaceCode, keepCode FROM @pairs;
|
|
OPEN cur;
|
|
FETCH NEXT FROM cur INTO @spaceCode, @keepCode;
|
|
WHILE @@FETCH_STATUS = 0
|
|
BEGIN
|
|
SELECT @spaceCnt = COUNT(*), @spaceId = MIN(Id) FROM Projects WHERE Code = @spaceCode AND IsDeleted = 0;
|
|
SELECT @keepCnt = COUNT(*), @keepId = MIN(Id) FROM Projects WHERE Code = @keepCode AND IsDeleted = 0;
|
|
|
|
IF @spaceCnt = 0
|
|
PRINT '[SKIP] ' + @spaceCode + ' -> ' + @keepCode + ' : khong co ban co-dau-cach song (da gop / khong ton tai).';
|
|
ELSE IF @spaceCnt > 1
|
|
PRINT '[ABORT] ' + @spaceCode + ' : >1 ban song cung code — can nguoi xem tay.';
|
|
ELSE IF @keepCnt = 0
|
|
PRINT '[ABORT] ' + @spaceCode + ' -> ' + @keepCode + ' : KHONG co ban giu (no-space) song — dung gop.';
|
|
ELSE IF @keepCnt > 1
|
|
PRINT '[ABORT] ' + @keepCode + ' : >1 ban giu song (reseed cycle?) — reconcile 1 GUID truoc.';
|
|
ELSE
|
|
BEGIN
|
|
BEGIN TRAN;
|
|
UPDATE Contracts SET ProjectId = @keepId WHERE ProjectId = @spaceId; SET @ct = @@ROWCOUNT;
|
|
UPDATE PurchaseEvaluations SET ProjectId = @keepId WHERE ProjectId = @spaceId; SET @pe = @@ROWCOUNT;
|
|
|
|
-- Budget: repoint neu survivor chua co live-budget cung WorkItem
|
|
UPDATE b SET b.ProjectId = @keepId
|
|
FROM PeWorkItemBudgets b
|
|
WHERE b.ProjectId = @spaceId AND b.IsDeleted = 0
|
|
AND NOT EXISTS (SELECT 1 FROM PeWorkItemBudgets s
|
|
WHERE s.ProjectId = @keepId AND s.WorkItemId = b.WorkItemId AND s.IsDeleted = 0);
|
|
SET @bgMove = @@ROWCOUNT;
|
|
|
|
-- Budget: collision (survivor da co) -> soft-delete ban space
|
|
UPDATE b SET b.IsDeleted = 1, b.DeletedAt = SYSUTCDATETIME()
|
|
FROM PeWorkItemBudgets b
|
|
WHERE b.ProjectId = @spaceId AND b.IsDeleted = 0
|
|
AND EXISTS (SELECT 1 FROM PeWorkItemBudgets s
|
|
WHERE s.ProjectId = @keepId AND s.WorkItemId = b.WorkItemId AND s.IsDeleted = 0);
|
|
SET @bgCollide = @@ROWCOUNT;
|
|
|
|
-- Budget da soft-delete: repoint de gon audit (khong dung unique filter IsDeleted=0)
|
|
UPDATE PeWorkItemBudgets SET ProjectId = @keepId WHERE ProjectId = @spaceId AND IsDeleted = 1;
|
|
|
|
SET @liveRef = (SELECT COUNT(*) FROM Contracts WHERE ProjectId = @spaceId)
|
|
+ (SELECT COUNT(*) FROM PurchaseEvaluations WHERE ProjectId = @spaceId)
|
|
+ (SELECT COUNT(*) FROM PeWorkItemBudgets WHERE ProjectId = @spaceId AND IsDeleted = 0);
|
|
|
|
IF @liveRef <> 0
|
|
BEGIN
|
|
ROLLBACK TRAN;
|
|
PRINT '[ABORT] ' + @spaceCode + ' : con ' + CAST(@liveRef AS varchar(9)) + ' live-ref sau repoint — ROLLBACK, KHONG an mem.';
|
|
END
|
|
ELSE
|
|
BEGIN
|
|
UPDATE Projects SET IsDeleted = 1, DeletedAt = SYSUTCDATETIME() WHERE Id = @spaceId;
|
|
COMMIT TRAN;
|
|
PRINT '[OK] ' + @spaceCode + ' -> ' + @keepCode + ' : Ct=' + CAST(@ct AS varchar(9))
|
|
+ ' PE=' + CAST(@pe AS varchar(9)) + ' BgMoved=' + CAST(@bgMove AS varchar(9))
|
|
+ ' BgCollideSoftDel=' + CAST(@bgCollide AS varchar(9)) + ' ; da an mem ban space.';
|
|
END
|
|
END
|
|
|
|
FETCH NEXT FROM cur INTO @spaceCode, @keepCode;
|
|
END
|
|
CLOSE cur; DEALLOCATE cur;
|