Merge remote-tracking branch 'origin/kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 19m16s

This commit is contained in:
m-moghaddam
2026-06-20 17:28:19 +03:30
2 changed files with 344 additions and 40 deletions
@@ -1,5 +1,4 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Entities.Commission;
using CMSMicroservice.Domain.Entities.History;
using CMSMicroservice.Domain.Enums;
@@ -25,7 +24,6 @@ public class BulkCreditPayoutsCommandHandler : IRequestHandler<BulkCreditPayouts
public async Task<BulkCreditPayoutsResult> Handle(BulkCreditPayoutsCommand request, CancellationToken cancellationToken)
{
// بارگذاری همه پرداخت‌های Pending این هفته
var pendingPayouts = await _context.UserCommissionPayouts
.Where(p => p.WeekDefinitionId == request.WeekDefinitionId
&& p.Status == CommissionPayoutStatus.Pending
@@ -37,72 +35,37 @@ public class BulkCreditPayoutsCommandHandler : IRequestHandler<BulkCreditPayouts
return new BulkCreditPayoutsResult { CreditedCount = 0, TotalCredited = 0 };
}
// بارگذاری کیف پول همه کاربران مربوطه (یک query)
var userIds = pendingPayouts.Select(p => p.UserId).Distinct().ToList();
var wallets = await _context.UserWallets
.Where(w => userIds.Contains(w.UserId) && !w.IsDeleted)
.ToDictionaryAsync(w => w.UserId, cancellationToken);
var performedBy = _currentUser.GetPerformedBy() ?? "System";
var now = DateTime.UtcNow;
var walletHistories = new List<UserWalletHistory>();
var payoutHistories = new List<CommissionPayoutHistory>();
var creditedCount = 0;
long totalCredited = 0;
foreach (var payout in pendingPayouts)
{
if (!wallets.TryGetValue(payout.UserId, out var wallet))
{
_logger.LogWarning(
"Wallet not found for UserId={UserId}, PayoutId={PayoutId} — skipping",
payout.UserId, payout.Id);
continue;
}
var oldBalance = wallet.Balance;
// واریز مبلغ به کیف پول
wallet.Balance += payout.TotalAmount;
walletHistories.Add(new UserWalletHistory
{
WalletId = wallet.Id,
CurrentBalance = wallet.Balance,
CurrentNetworkBalance = wallet.NetworkBalance,
CurrentDiscountBalance = wallet.DiscountBalance,
ChangeValue = payout.TotalAmount,
ChangeNerworkValue = 0,
ChangeDiscountValue = 0,
IsIncrease = true,
RefrenceId = payout.Id
});
// به‌روزرسانی وضعیت پرداخت
payout.Status = CommissionPayoutStatus.Paid;
payout.PaidAt = now;
payout.LastModified = now;
payoutHistories.Add(new CommissionPayoutHistory
{
UserCommissionPayoutId = payout.Id,
UserCommissionPayoutId = payout.Id,
UserId = payout.UserId,
WeekDefinitionId = payout.WeekDefinitionId,
AmountBefore = 0,
AmountBefore = payout.TotalAmount,
AmountAfter = payout.TotalAmount,
OldStatus = CommissionPayoutStatus.Pending,
NewStatus = CommissionPayoutStatus.Paid,
Action = CommissionPayoutAction.Paid,
PerformedBy = performedBy,
Reason = $"واریز دسته‌جمعی کمیسیون هفته {payout.WeekDefinitionId} توسط {performedBy}"
Reason = $"تایید پرداخت دسته‌جمعی کمیسیون هفته {payout.WeekDefinitionId} توسط {performedBy}"
});
creditedCount++;
totalCredited += payout.TotalAmount;
}
await _context.UserWalletHistories.AddRangeAsync(walletHistories, cancellationToken);
await _context.CommissionPayoutHistories.AddRangeAsync(payoutHistories, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
@@ -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;
*/