/* ================================================================================ 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; */