feat(db): add SQL script to fix incorrect bulk credit payouts in user wallet histories
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 19m38s

Introduced a new SQL script to identify and correct erroneous bulk credit entries in user wallet histories. The script allows for a dry run to preview changes before applying fixes, ensuring accurate balance adjustments and removal of incorrect records.
This commit is contained in:
m-moghaddam
2026-06-20 17:27:23 +03:30
parent 5f8e30905d
commit 37820ea257
@@ -0,0 +1,341 @@
/*
================================================================================
FixBulkCreditPayoutsWrongBalance.sql
================================================================================
شناسایی لاگ اشتباه BulkCredit در UserWalletHistories:
RefrenceId IN (payoutهای BulkCredit شده)
AND CreatedBy <> 'SP' -- لاگ SP = NetworkBalance درست، حذف نشود
AND ChangeValue > 0 -- واریز اشتباه به Balance
AND ChangeDiscountValue <= 0
اصلاح:
- کسر ChangeValue از Balance همان WalletId
- حذف رکوردهای UserWalletHistories اشتباه
@DryRun = 1 → فقط گزارش
@DryRun = 0 → اعمال fix
================================================================================
*/
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @DryRun BIT = 1;
DECLARE @WeeksBack INT = 4;
DECLARE @SinceDate DATETIME = DATEADD(WEEK, -@WeeksBack, GETUTCDATE());
-- کاربرانی که fix برایشان اعمال می‌شود حتی اگر Balance منفی شود
DECLARE @AllowNegativeUserIds TABLE (UserId BIGINT PRIMARY KEY);
INSERT INTO @AllowNegativeUserIds (UserId) VALUES (106); -- مهرداد شجاعی
PRINT CONCAT('DryRun = ', @DryRun, ' | WeeksBack = ', @WeeksBack);
PRINT '';
-- payoutهای BulkCredit شده (از CommissionPayoutHistories)
;WITH BulkCreditedPayoutIds AS (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0
AND cph.Action = 1
AND cph.Created >= @SinceDate
AND (
cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%'
OR cph.Reason LIKE N'%واریز دسته%جمعی%'
)
),
WrongWalletLogs AS (
SELECT
wh.Id AS WalletHistoryId,
wh.WalletId,
wh.RefrenceId AS PayoutId,
p.UserId,
wh.ChangeValue AS WrongBalanceCredit,
wh.Created AS WrongCreditAt,
wh.CreatedBy
FROM CMS.UserWalletHistories wh
INNER JOIN CMS.UserCommissionPayouts p
ON p.Id = wh.RefrenceId AND p.IsDeleted = 0
WHERE wh.IsDeleted = 0
AND wh.RefrenceId IN (SELECT PayoutId FROM BulkCreditedPayoutIds)
AND wh.CreatedBy <> N'SP'
AND wh.ChangeValue > 0
AND wh.ChangeDiscountValue <= 0
)
-- 1) خلاصه
SELECT
(SELECT COUNT(*) FROM BulkCreditedPayoutIds) AS BulkCreditedPayoutCount,
(SELECT COUNT(DISTINCT p.UserId)
FROM CMS.UserCommissionPayouts p
WHERE p.Id IN (SELECT PayoutId FROM BulkCreditedPayoutIds)) AS BulkCreditedUserCount,
(SELECT COUNT(*) FROM WrongWalletLogs) AS WrongWalletHistoryCount,
(SELECT COUNT(DISTINCT UserId) FROM WrongWalletLogs) AS AffectedUserCount,
(SELECT ISNULL(SUM(WrongBalanceCredit), 0) FROM WrongWalletLogs) AS TotalWrongBalanceCredit;
PRINT '';
PRINT '--- PayoutIdهای BulkCredit (برای مقایسه با لیست دستی) ---';
SELECT PayoutId FROM (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0 AND cph.Action = 1 AND cph.Created >= @SinceDate
AND (cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%' OR cph.Reason LIKE N'%واریز دسته%جمعی%')
) x ORDER BY PayoutId;
PRINT '';
PRINT '--- UserWalletHistories اشتباه ---';
;WITH BulkCreditedPayoutIds AS (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0 AND cph.Action = 1 AND cph.Created >= @SinceDate
AND (cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%' OR cph.Reason LIKE N'%واریز دسته%جمعی%')
)
SELECT
wh.Id AS WalletHistoryId,
wh.RefrenceId AS PayoutId,
p.UserId,
u.Mobile,
CONCAT(u.FirstName, N' ', u.LastName) AS FullName,
p.WeekDefinitionId,
p.TotalAmount AS PayoutAmount,
wh.ChangeValue AS WrongBalanceCredit,
uw.Balance AS CurrentBalance,
uw.NetworkBalance,
wh.Created AS WrongCreditAt,
wh.CreatedBy
FROM CMS.UserWalletHistories wh
INNER JOIN CMS.UserCommissionPayouts p ON p.Id = wh.RefrenceId AND p.IsDeleted = 0
INNER JOIN CMS.UserWallets uw ON uw.Id = wh.WalletId AND uw.IsDeleted = 0
INNER JOIN CMS.Users u ON u.Id = p.UserId AND u.IsDeleted = 0
WHERE wh.IsDeleted = 0
AND wh.RefrenceId IN (SELECT PayoutId FROM BulkCreditedPayoutIds)
AND wh.CreatedBy <> N'SP'
AND wh.ChangeValue > 0
AND wh.ChangeDiscountValue <= 0
ORDER BY p.UserId, wh.RefrenceId;
PRINT '';
PRINT '--- خلاصه به تفکیک کاربر ---';
;WITH BulkCreditedPayoutIds AS (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0 AND cph.Action = 1 AND cph.Created >= @SinceDate
AND (cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%' OR cph.Reason LIKE N'%واریز دسته%جمعی%')
),
WrongWalletLogs AS (
SELECT wh.Id AS WalletHistoryId, wh.WalletId, wh.RefrenceId AS PayoutId,
p.UserId, wh.ChangeValue AS WrongBalanceCredit
FROM CMS.UserWalletHistories wh
INNER JOIN CMS.UserCommissionPayouts p ON p.Id = wh.RefrenceId AND p.IsDeleted = 0
WHERE wh.IsDeleted = 0
AND wh.RefrenceId IN (SELECT PayoutId FROM BulkCreditedPayoutIds)
AND wh.CreatedBy <> N'SP'
AND wh.ChangeValue > 0
AND wh.ChangeDiscountValue <= 0
)
SELECT
w.UserId,
u.Mobile,
CONCAT(u.FirstName, N' ', u.LastName) AS FullName,
COUNT(w.WalletHistoryId) AS WrongHistoryCount,
COUNT(DISTINCT w.PayoutId) AS AffectedPayoutCount,
SUM(w.WrongBalanceCredit) AS TotalWrongCredit,
uw.Balance AS CurrentBalance,
uw.Balance - SUM(w.WrongBalanceCredit) AS BalanceAfterFix,
CASE
WHEN w.UserId IN (SELECT UserId FROM @AllowNegativeUserIds)
AND uw.Balance < SUM(w.WrongBalanceCredit) THEN N'OK — منفی مجاز'
WHEN uw.Balance < SUM(w.WrongBalanceCredit) THEN N'⚠ موجودی کافی نیست'
ELSE N'OK'
END AS FixStatus
FROM WrongWalletLogs w
INNER JOIN CMS.Users u ON u.Id = w.UserId AND u.IsDeleted = 0
INNER JOIN CMS.UserWallets uw ON uw.Id = w.WalletId AND uw.IsDeleted = 0
GROUP BY w.UserId, u.Mobile, u.FirstName, u.LastName, uw.Id, uw.Balance
ORDER BY TotalWrongCredit DESC;
PRINT '';
PRINT '--- blockers (به‌جز کاربران با منفی مجاز) ---';
;WITH BulkCreditedPayoutIds AS (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0 AND cph.Action = 1 AND cph.Created >= @SinceDate
AND (cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%' OR cph.Reason LIKE N'%واریز دسته%جمعی%')
),
WrongPerWallet AS (
SELECT wh.WalletId, SUM(wh.ChangeValue) AS TotalWrongCredit
FROM CMS.UserWalletHistories wh
WHERE wh.IsDeleted = 0
AND wh.RefrenceId IN (SELECT PayoutId FROM BulkCreditedPayoutIds)
AND wh.CreatedBy <> N'SP'
AND wh.ChangeValue > 0
AND wh.ChangeDiscountValue <= 0
GROUP BY wh.WalletId
)
SELECT uw.UserId, u.Mobile, uw.Balance AS CurrentBalance,
w.TotalWrongCredit, uw.Balance - w.TotalWrongCredit AS BalanceAfterFix
FROM WrongPerWallet w
INNER JOIN CMS.UserWallets uw ON uw.Id = w.WalletId AND uw.IsDeleted = 0
INNER JOIN CMS.Users u ON u.Id = uw.UserId AND u.IsDeleted = 0
WHERE uw.Balance < w.TotalWrongCredit
AND uw.UserId NOT IN (SELECT UserId FROM @AllowNegativeUserIds);
PRINT '';
PRINT '--- منفی مجاز (fix اعمال می‌شود) ---';
;WITH BulkCreditedPayoutIds AS (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0 AND cph.Action = 1 AND cph.Created >= @SinceDate
AND (cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%' OR cph.Reason LIKE N'%واریز دسته%جمعی%')
),
WrongPerWallet AS (
SELECT wh.WalletId, SUM(wh.ChangeValue) AS TotalWrongCredit
FROM CMS.UserWalletHistories wh
WHERE wh.IsDeleted = 0
AND wh.RefrenceId IN (SELECT PayoutId FROM BulkCreditedPayoutIds)
AND wh.CreatedBy <> N'SP'
AND wh.ChangeValue > 0
AND wh.ChangeDiscountValue <= 0
GROUP BY wh.WalletId
)
SELECT uw.UserId, u.Mobile, uw.Balance AS CurrentBalance,
w.TotalWrongCredit, uw.Balance - w.TotalWrongCredit AS BalanceAfterFix
FROM WrongPerWallet w
INNER JOIN CMS.UserWallets uw ON uw.Id = w.WalletId AND uw.IsDeleted = 0
INNER JOIN CMS.Users u ON u.Id = uw.UserId AND u.IsDeleted = 0
WHERE uw.Balance < w.TotalWrongCredit
AND uw.UserId IN (SELECT UserId FROM @AllowNegativeUserIds);
-- ── اعمال fix ──
IF @DryRun = 1
BEGIN
PRINT '';
PRINT 'DryRun — تغییری اعمال نشد. برای fix: @DryRun = 0';
RETURN;
END
DECLARE @BlockerCount INT;
DECLARE @FixedWallets INT;
DECLARE @DeletedHistories INT;
DECLARE @TotalReverted BIGINT;
DECLARE @ErrMsg NVARCHAR(4000);
;WITH BulkCreditedPayoutIds AS (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0 AND cph.Action = 1 AND cph.Created >= @SinceDate
AND (cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%' OR cph.Reason LIKE N'%واریز دسته%جمعی%')
),
WrongPerWallet AS (
SELECT wh.WalletId, SUM(wh.ChangeValue) AS TotalWrongCredit
FROM CMS.UserWalletHistories wh
WHERE wh.IsDeleted = 0
AND wh.RefrenceId IN (SELECT PayoutId FROM BulkCreditedPayoutIds)
AND wh.CreatedBy <> N'SP'
AND wh.ChangeValue > 0
AND wh.ChangeDiscountValue <= 0
GROUP BY wh.WalletId
)
SELECT @BlockerCount = COUNT(*)
FROM WrongPerWallet w
INNER JOIN CMS.UserWallets uw ON uw.Id = w.WalletId AND uw.IsDeleted = 0
WHERE uw.Balance < w.TotalWrongCredit
AND uw.UserId NOT IN (SELECT UserId FROM @AllowNegativeUserIds);
IF @BlockerCount > 0
BEGIN
RAISERROR(N'%d کیف پول Balance منفی می‌شود — fix متوقف شد.', 16, 1, @BlockerCount);
RETURN;
END
BEGIN TRY
BEGIN TRANSACTION;
;WITH BulkCreditedPayoutIds AS (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0 AND cph.Action = 1 AND cph.Created >= @SinceDate
AND (cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%' OR cph.Reason LIKE N'%واریز دسته%جمعی%')
),
WrongPerWallet AS (
SELECT wh.WalletId, SUM(wh.ChangeValue) AS TotalWrongCredit
FROM CMS.UserWalletHistories wh
WHERE wh.IsDeleted = 0
AND wh.RefrenceId IN (SELECT PayoutId FROM BulkCreditedPayoutIds)
AND wh.CreatedBy <> N'SP'
AND wh.ChangeValue > 0
AND wh.ChangeDiscountValue <= 0
GROUP BY wh.WalletId
)
UPDATE uw
SET uw.Balance = uw.Balance - w.TotalWrongCredit,
uw.LastModified = GETUTCDATE(),
uw.LastModifiedBy = N'FixBulkCreditPayoutsWrongBalance'
FROM CMS.UserWallets uw
INNER JOIN WrongPerWallet w ON w.WalletId = uw.Id
WHERE uw.IsDeleted = 0;
SET @FixedWallets = @@ROWCOUNT;
;WITH BulkCreditedPayoutIds AS (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0 AND cph.Action = 1 AND cph.Created >= @SinceDate
AND (cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%' OR cph.Reason LIKE N'%واریز دسته%جمعی%')
)
SELECT @TotalReverted = ISNULL(SUM(wh.ChangeValue), 0)
FROM CMS.UserWalletHistories wh
WHERE wh.IsDeleted = 0
AND wh.RefrenceId IN (SELECT PayoutId FROM BulkCreditedPayoutIds)
AND wh.CreatedBy <> N'SP'
AND wh.ChangeValue > 0
AND wh.ChangeDiscountValue <= 0;
;WITH BulkCreditedPayoutIds AS (
SELECT DISTINCT cph.UserCommissionPayoutId AS PayoutId
FROM CMS.CommissionPayoutHistories cph
WHERE cph.IsDeleted = 0 AND cph.Action = 1 AND cph.Created >= @SinceDate
AND (cph.Reason LIKE N'%تایید پرداخت دسته%جمعی%' OR cph.Reason LIKE N'%واریز دسته%جمعی%')
)
DELETE wh
FROM CMS.UserWalletHistories wh
WHERE wh.IsDeleted = 0
AND wh.RefrenceId IN (SELECT PayoutId FROM BulkCreditedPayoutIds)
AND wh.CreatedBy <> N'SP'
AND wh.ChangeValue > 0
AND wh.ChangeDiscountValue <= 0;
SET @DeletedHistories = @@ROWCOUNT;
COMMIT TRANSACTION;
PRINT CONCAT('کیف پول اصلاح‌شده: ', @FixedWallets);
PRINT CONCAT('لاگ حذف‌شده: ', @DeletedHistories);
PRINT CONCAT('مجموع برگردانده‌شده: ', @TotalReverted);
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
SET @ErrMsg = ERROR_MESSAGE();
RAISERROR(@ErrMsg, 16, 1);
END CATCH;
GO
/*
-- کوئری ساده دستی (معادل همین منطق با لیست PayoutId ثابت):
SELECT *
FROM CMS.UserWalletHistories
WHERE RefrenceId IN (
115,116,117,118,119,125,126,127,128,129,130,131,132,133,134,
135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,
150,151,152,153
)
AND CreatedBy <> N'SP'
AND ChangeValue > 0
AND ChangeDiscountValue <= 0;
*/