From 1dbb3ee502f080a4673459418c0e880a569dd29f Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sat, 27 Dec 2025 02:30:40 +0330 Subject: [PATCH] feat: implement unified command for Daya loan status check and processing - Refactored DayaLoanCheckWorker to use CheckAndProcessDayaLoansCommand, enhancing functionality and logging --- .../CheckAndProcessDayaLoansCommand.cs | 15 + .../CheckAndProcessDayaLoansCommandHandler.cs | 301 ++++++++++++++++++ .../CheckAndProcessDayaLoansResponseDto.cs | 70 ++++ .../CheckDayaLoanStatusCommand.cs | 14 - .../CheckDayaLoanStatusCommandHandler.cs | 235 -------------- .../CheckDayaLoanStatusResponseDto.cs | 18 -- .../ProcessDayaLoanApprovalCommand.cs | 32 -- .../ProcessDayaLoanApprovalCommandHandler.cs | 192 ----------- ...ProcessDayaLoanApprovalCommandValidator.cs | 21 -- .../ProcessDayaLoanApprovalResponseDto.cs | 12 - .../Common/SmsTemplates.cs | 6 +- .../Workers/DayaLoanCheckWorker.cs | 64 +--- 12 files changed, 404 insertions(+), 576 deletions(-) create mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommand.cs create mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs create mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansResponseDto.cs delete mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusCommand.cs delete mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusResponseDto.cs delete mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommand.cs delete mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommandHandler.cs delete mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommandValidator.cs delete mode 100644 src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalResponseDto.cs diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommand.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommand.cs new file mode 100644 index 0000000..40f8aaf --- /dev/null +++ b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommand.cs @@ -0,0 +1,15 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.DayaLoanCQ.Commands.CheckAndProcessDayaLoans; + +/// +/// Command یکپارچه برای استعلام وضعیت وام از سرویس دایا و پردازش خودکار وام‌های تأیید شده +/// این Command هم استعلام می‌کند و هم در صورت تأیید، کیف پول را شارژ می‌کند +/// +public record CheckAndProcessDayaLoansCommand : IRequest +{ + /// + /// لیست کدهای ملی برای استعلام + /// + public required List NationalCodes { get; init; } +} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs new file mode 100644 index 0000000..346dcad --- /dev/null +++ b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs @@ -0,0 +1,301 @@ +using CMSMicroservice.Domain.Events; +using CMSMicroservice.Domain.Enums; +using CMSMicroservice.Domain.Common; +using CMSMicroservice.Application.DayaLoanCQ.Services; +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.DayaLoanCQ.Commands.CheckAndProcessDayaLoans; + +/// +/// Handler یکپارچه برای استعلام و پردازش وام دایا +/// 1. استعلام از API دایا +/// 2. ذخیره/به‌روزرسانی DayaLoanContract +/// 3. شارژ کیف پول برای وام‌های تأیید شده +/// 4. ثبت Order پکیج طلایی +/// 5. ارسال SMS +/// +public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IDayaLoanApiService _dayaApiService; + private readonly IKavenegarService _smsService; + private readonly ILogger _logger; + + public CheckAndProcessDayaLoansCommandHandler( + IApplicationDbContext context, + IDayaLoanApiService dayaApiService, + IKavenegarService smsService, + ILogger logger) + { + _context = context; + _dayaApiService = dayaApiService; + _smsService = smsService; + _logger = logger; + } + + public async Task Handle(CheckAndProcessDayaLoansCommand request, CancellationToken cancellationToken) + { + var results = new List(); + var processedCount = 0; + + try + { + // 1. استعلام از سرویس دایا + _logger.LogInformation("Checking Daya loan status for {Count} national codes", request.NationalCodes.Count); + var dayaResults = await _dayaApiService.CheckLoanStatusAsync(request.NationalCodes, cancellationToken); + + foreach (var dayaResult in dayaResults) + { + var result = new DayaLoanProcessResult + { + NationalCode = dayaResult.NationalCode, + Status = dayaResult.Status, + ContractNumber = dayaResult.ContractNumber, + WasProcessed = false, + Message = "استعلام موفق" + }; + + try + { + // 2. پیدا کردن کاربر + var user = await _context.Users + .Include(u => u.UserWallets) + .FirstOrDefaultAsync(u => u.NationalCode == dayaResult.NationalCode, cancellationToken); + + if (user == null) + { + result.Message = "کاربر یافت نشد"; + results.Add(result); + continue; + } + + result.UserId = user.Id; + + // 3. ذخیره/به‌روزرسانی DayaLoanContract + var contract = await _context.DayaLoanContracts + .FirstOrDefaultAsync(d => d.NationalCode == dayaResult.NationalCode, cancellationToken); + + if (contract == null) + { + contract = new DayaLoanContract + { + UserId = user.Id, + NationalCode = dayaResult.NationalCode, + Status = dayaResult.Status, + ContractNumber = dayaResult.ContractNumber, + LastCheckDate = DateTime.Now, + IsProcessed = false + }; + await _context.DayaLoanContracts.AddAsync(contract, cancellationToken); + } + else + { + contract.Status = dayaResult.Status; + contract.ContractNumber = dayaResult.ContractNumber; + contract.LastCheckDate = DateTime.Now; + } + + // 4. آیا باید پردازش مالی انجام شود؟ + var shouldProcess = !contract.IsProcessed && + !user.HasReceivedDayaCredit && + !string.IsNullOrEmpty(dayaResult.ContractNumber) && + (dayaResult.Status == DayaLoanStatus.PendingReceive || + dayaResult.Status == DayaLoanStatus.Received); + + if (shouldProcess) + { + // 5. پردازش مالی + var processResult = await ProcessDayaLoanAsync(user, dayaResult.ContractNumber!, cancellationToken); + + contract.IsProcessed = true; + result.WasProcessed = true; + result.NewWalletBalance = processResult.NewBalance; + result.Message = "وام پردازش و کیف پول شارژ شد"; + processedCount++; + + _logger.LogInformation( + "Daya loan processed for User {UserId}, Contract: {ContractNumber}, NewBalance: {Balance}", + user.Id, dayaResult.ContractNumber, processResult.NewBalance); + } + else if (contract.IsProcessed || user.HasReceivedDayaCredit) + { + result.Message = "قبلاً پردازش شده"; + } + else if (string.IsNullOrEmpty(dayaResult.ContractNumber)) + { + result.Message = "هنوز قرارداد ندارد"; + } + + await _context.SaveChangesAsync(cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing Daya loan for NationalCode {NationalCode}", dayaResult.NationalCode); + result.Message = $"خطا: {ex.Message}"; + } + + results.Add(result); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error calling Daya API service"); + + // در صورت خطا در API + foreach (var nationalCode in request.NationalCodes) + { + if (!results.Any(r => r.NationalCode == nationalCode)) + { + results.Add(new DayaLoanProcessResult + { + NationalCode = nationalCode, + Status = DayaLoanStatus.PendingReceive, + WasProcessed = false, + Message = $"خطا در استعلام: {ex.Message}" + }); + } + } + } + + return new CheckAndProcessDayaLoansResponseDto + { + Results = results, + TotalChecked = request.NationalCodes.Count, + WithContractCount = results.Count(r => !string.IsNullOrEmpty(r.ContractNumber)), + ProcessedCount = processedCount + }; + } + + /// + /// پردازش مالی وام دایا + /// + private async Task<(long NewBalance, long TransactionId)> ProcessDayaLoanAsync( + User user, + string contractNumber, + CancellationToken cancellationToken) + { + // 1. ایجاد تراکنش + var transaction = new Transaction + { + Amount = SystemConstants.DayaLoanAmount, + Description = $"دریافت اعتبار دایا - قرارداد {contractNumber}", + PaymentStatus = PaymentStatus.Success, + PaymentDate = DateTime.Now, + RefId = contractNumber, + Type = TransactionType.DepositExternal1 + }; + + await _context.Transactions.AddAsync(transaction, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + + // 2. یافتن یا ایجاد کیف پول + var wallet = user.UserWallets.FirstOrDefault(); + if (wallet == null) + { + wallet = new UserWallet + { + UserId = user.Id, + Balance = 0, + NetworkBalance = 0, + DiscountBalance = 0 + }; + await _context.UserWallets.AddAsync(wallet, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + // 3. شارژ کیف پول عادی + wallet.Balance += SystemConstants.DayaLoanAmount; + + var mainLog = new UserWalletChangeLog + { + WalletId = wallet.Id, + CurrentBalance = wallet.Balance, + ChangeValue = SystemConstants.DayaLoanAmount, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + IsIncrease = true, + RefrenceId = transaction.Id + }; + await _context.UserWalletChangeLogs.AddAsync(mainLog, cancellationToken); + + // 4. شارژ کیف پول تخفیف (دو برابر) + var discountAmount = SystemConstants.DayaLoanAmount * 2; + wallet.DiscountBalance += discountAmount; + + var discountLog = new UserWalletChangeLog + { + WalletId = wallet.Id, + CurrentBalance = wallet.Balance, + ChangeValue = 0, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = wallet.DiscountBalance, + ChangeDiscountValue = discountAmount, + IsIncrease = true, + RefrenceId = transaction.Id + }; + await _context.UserWalletChangeLogs.AddAsync(discountLog, cancellationToken); + + // 5. به‌روزرسانی کاربر + user.HasReceivedDayaCredit = true; + user.DayaCreditReceivedAt = DateTime.Now; + user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan; + + // 6. ثبت Order پکیج پایه + var goldenPackage = await _context.Packages + .FirstOrDefaultAsync(p => p.Id == 4, cancellationToken); + + if (goldenPackage != null) + { + // 6. ثبت UserPackagePurchase برای پکیج پایه + var goldenPackageId =goldenPackage.Id; + var packagePurchase = new UserPackagePurchase + { + UserId = user.Id, + PackageId = goldenPackageId, + PurchaseMethod = PackagePurchaseMethod.DayaLoan, + PurchasedAt = DateTime.Now, + Amount = SystemConstants.DayaLoanAmount, + TransactionId = transaction.Id + }; + + await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken); + + } + + // 7. Domain Event + user.AddDomainEvent(new DayaLoanApprovedEvent(user, transaction, contractNumber)); + + await _context.SaveChangesAsync(cancellationToken); + + // 8. ارسال SMS + await SendDayaLoanSmsAsync(user); + + return (wallet.Balance, transaction.Id); + } + + /// + /// ارسال SMS اطلاع‌رسانی + /// + private async Task SendDayaLoanSmsAsync(User user) + { + try + { + var userName = SmsTemplates.GetUserName(user.FirstName+" "+user.LastName); + var message = SmsTemplates.DayaLoanReceived(userName); + + await _smsService.SendAsync(user.Mobile, message); + + // ارسال SMS به ادمین برای اطلاع + var adminMessage = $"وام دایا دریافت شد\nکاربر: {user.FirstName} {user.LastName}\nموبایل: {user.Mobile}\nکدملی: {user.NationalCode}"; + await _smsService.SendAsync("09199877503", adminMessage); + + _logger.LogInformation("Daya loan SMS sent to User {UserId}", user.Id); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to send Daya loan SMS to User {UserId}", user.Id); + } + } +} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansResponseDto.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansResponseDto.cs new file mode 100644 index 0000000..fb7942c --- /dev/null +++ b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansResponseDto.cs @@ -0,0 +1,70 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.DayaLoanCQ.Commands.CheckAndProcessDayaLoans; + +/// +/// پاسخ Command استعلام و پردازش وام دایا +/// +public class CheckAndProcessDayaLoansResponseDto +{ + /// + /// نتایج استعلام و پردازش + /// + public required List Results { get; set; } + + /// + /// تعداد کل استعلام شده + /// + public int TotalChecked { get; set; } + + /// + /// تعداد موفق (دارای قرارداد) + /// + public int WithContractCount { get; set; } + + /// + /// تعداد پردازش شده (شارژ کیف پول) + /// + public int ProcessedCount { get; set; } +} + +/// +/// نتیجه پردازش هر کاربر +/// +public class DayaLoanProcessResult +{ + /// + /// کد ملی + /// + public required string NationalCode { get; set; } + + /// + /// شناسه کاربر + /// + public long? UserId { get; set; } + + /// + /// وضعیت وام در دایا + /// + public DayaLoanStatus Status { get; set; } + + /// + /// شماره قرارداد + /// + public string? ContractNumber { get; set; } + + /// + /// آیا پردازش مالی انجام شد؟ + /// + public bool WasProcessed { get; set; } + + /// + /// موجودی کیف پول بعد از شارژ + /// + public long? NewWalletBalance { get; set; } + + /// + /// پیام + /// + public required string Message { get; set; } +} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusCommand.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusCommand.cs deleted file mode 100644 index 3416f54..0000000 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.DayaLoanCQ.Commands.CheckDayaLoanStatus; - -/// -/// Command برای استعلام وضعیت وام از سرویس دایا -/// -public record CheckDayaLoanStatusCommand : IRequest -{ - /// - /// لیست کدهای ملی برای استعلام - /// - public List NationalCodes { get; init; } -} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusCommandHandler.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusCommandHandler.cs deleted file mode 100644 index 0f2b57c..0000000 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusCommandHandler.cs +++ /dev/null @@ -1,235 +0,0 @@ -using CMSMicroservice.Domain.Events; -using CMSMicroservice.Domain.Enums; -using CMSMicroservice.Domain.Common; -using CMSMicroservice.Application.DayaLoanCQ.Services; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.DayaLoanCQ.Commands.CheckDayaLoanStatus; - -public class CheckDayaLoanStatusCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IDayaLoanApiService _dayaApiService; - private readonly ILogger _logger; - - public CheckDayaLoanStatusCommandHandler( - IApplicationDbContext context, - IDayaLoanApiService dayaApiService, - ILogger logger) - { - _context = context; - _dayaApiService = dayaApiService; - _logger = logger; - } - - public async Task Handle(CheckDayaLoanStatusCommand request, CancellationToken cancellationToken) - { - var results = new List(); - - try - { - // فراخوانی سرویس دایا (Mock یا Real) - var dayaResults = await _dayaApiService.CheckLoanStatusAsync(request.NationalCodes, cancellationToken); - - foreach (var dayaResult in dayaResults) - { - try - { - results.Add(new DayaLoanStatusItem - { - NationalCode = dayaResult.NationalCode, - Status = dayaResult.Status, - ContractNumber = dayaResult.ContractNumber, - Message = "استعلام موفق" - }); - - // ذخیره یا به‌روزرسانی در دیتابیس - var existingContract = await _context.DayaLoanContracts - .FirstOrDefaultAsync(d => d.NationalCode == dayaResult.NationalCode, cancellationToken); - - if (existingContract != null) - { - var previousStatus = existingContract.Status; - existingContract.LastCheckDate = DateTime.Now; - existingContract.Status = dayaResult.Status; - existingContract.ContractNumber = dayaResult.ContractNumber; - - // بررسی تغییر وضعیت به PendingReceive یا Received - // فقط اگر قبلاً پردازش نشده باشد - if (!existingContract.IsProcessed && - !string.IsNullOrEmpty(dayaResult.ContractNumber) && - (dayaResult.Status == DayaLoanStatus.PendingReceive || dayaResult.Status == DayaLoanStatus.Received)) - { - await ProcessDayaLoanReceivedAsync(existingContract.UserId, dayaResult.ContractNumber, cancellationToken); - existingContract.IsProcessed = true; - - _logger.LogInformation( - "Daya loan processed for User {UserId}, ContractNumber: {ContractNumber}, Amount: {Amount}", - existingContract.UserId, dayaResult.ContractNumber, SystemConstants.DayaLoanAmount); - } - } - else - { - var user = await _context.Users - .FirstOrDefaultAsync(u => u.NationalCode == dayaResult.NationalCode, cancellationToken); - - if (user != null) - { - var isProcessed = false; - - // اگر وضعیت PendingReceive یا Received بود و قرارداد دارد، فوری پردازش کن - if (!string.IsNullOrEmpty(dayaResult.ContractNumber) && - (dayaResult.Status == DayaLoanStatus.PendingReceive || dayaResult.Status == DayaLoanStatus.Received)) - { - await ProcessDayaLoanReceivedAsync(user.Id, dayaResult.ContractNumber, cancellationToken); - isProcessed = true; - - _logger.LogInformation( - "Daya loan processed for new contract - User {UserId}, ContractNumber: {ContractNumber}, Amount: {Amount}", - user.Id, dayaResult.ContractNumber, SystemConstants.DayaLoanAmount); - } - - var newContract = new DayaLoanContract - { - UserId = user.Id, - NationalCode = dayaResult.NationalCode, - Status = dayaResult.Status, - ContractNumber = dayaResult.ContractNumber, - LastCheckDate = DateTime.Now, - IsProcessed = isProcessed - }; - - await _context.DayaLoanContracts.AddAsync(newContract, cancellationToken); - } - } - - await _context.SaveChangesAsync(cancellationToken); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error processing Daya result for {NationalCode}", dayaResult.NationalCode); - } - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error calling Daya API service"); - - // در صورت خطا، نتایج خالی برمی‌گردانیم - foreach (var nationalCode in request.NationalCodes) - { - results.Add(new DayaLoanStatusItem - { - NationalCode = nationalCode, - Status = DayaLoanStatus.PendingReceive, - ContractNumber = null, - Message = $"خطا در استعلام: {ex.Message}" - }); - } - } - - return new CheckDayaLoanStatusResponseDto - { - Results = results, - TotalChecked = request.NationalCodes.Count, - SuccessCount = results.Count(r => r.ContractNumber != null) - }; - } - - /// - /// پردازش دریافت وام دایا برای کاربر - /// 1. شارژ کیف پول (Balance + DiscountBalance) - /// 2. ثبت تراکنش - /// 3. ثبت لاگ کیف پول - /// 4. به‌روزرسانی وضعیت کاربر - /// - private async Task ProcessDayaLoanReceivedAsync(long userId, string contractNumber, CancellationToken cancellationToken) - { - // پیدا کردن کاربر - var user = await _context.Users - .FirstOrDefaultAsync(u => u.Id == userId, cancellationToken); - - if (user == null) - { - _logger.LogWarning("User {UserId} not found for Daya loan processing", userId); - return; - } - - // بررسی اینکه قبلاً دریافت نکرده باشد - if (user.HasReceivedDayaCredit) - { - _logger.LogWarning("User {UserId} has already received Daya credit", userId); - return; - } - - // پیدا کردن یا ایجاد کیف پول کاربر - var wallet = await _context.UserWallets - .FirstOrDefaultAsync(w => w.UserId == userId, cancellationToken); - - if (wallet == null) - { - wallet = new UserWallet - { - UserId = userId, - Balance = 0, - NetworkBalance = 0, - DiscountBalance = 0 - }; - await _context.UserWallets.AddAsync(wallet, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - } - - // 1. شارژ کیف پول - هم موجودی اصلی و هم موجودی تخفیف - var previousBalance = wallet.Balance; - var previousDiscountBalance = wallet.DiscountBalance; - - wallet.Balance += SystemConstants.DayaLoanAmount; - wallet.DiscountBalance += SystemConstants.DayaLoanAmount; - - // 2. ثبت تراکنش - RefId = شماره قرارداد، Status = 0 (Success)، Type = 2 (DepositExternal1) - var transaction = new Transaction - { - Amount = SystemConstants.DayaLoanAmount, - Description = $"شارژ کیف پول از وام دایا - قرارداد {contractNumber}", - PaymentStatus = PaymentStatus.Success, // 0 - PaymentDate = DateTime.Now, - RefId = contractNumber, - Type = TransactionType.DepositExternal1 // 2 - }; - - await _context.Transactions.AddAsync(transaction, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - - // 3. ثبت لاگ کیف پول با شناسه تراکنش - var walletLog = new UserWalletChangeLog - { - WalletId = wallet.Id, - CurrentBalance = wallet.Balance, - ChangeValue = SystemConstants.DayaLoanAmount, - CurrentNetworkBalance = wallet.NetworkBalance, - ChangeNerworkValue = 0, - CurrentDiscountBalance = wallet.DiscountBalance, - ChangeDiscountValue = SystemConstants.DayaLoanAmount, - IsIncrease = true, - RefrenceId = transaction.Id - }; - - await _context.UserWalletChangeLogs.AddAsync(walletLog, cancellationToken); - - // 4. به‌روزرسانی وضعیت کاربر - user.HasReceivedDayaCredit = true; - user.DayaCreditReceivedAt = DateTime.Now; - user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan; // 1 - - await _context.SaveChangesAsync(cancellationToken); - - _logger.LogInformation( - "Daya loan fully processed for User {UserId}: " + - "Wallet Balance {PreviousBalance} -> {NewBalance}, " + - "DiscountBalance {PreviousDiscountBalance} -> {NewDiscountBalance}, " + - "Transaction Id: {TransactionId}", - userId, previousBalance, wallet.Balance, - previousDiscountBalance, wallet.DiscountBalance, - transaction.Id); - } -} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusResponseDto.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusResponseDto.cs deleted file mode 100644 index ec9c54c..0000000 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckDayaLoanStatus/CheckDayaLoanStatusResponseDto.cs +++ /dev/null @@ -1,18 +0,0 @@ -using CMSMicroservice.Domain.Enums; - -namespace CMSMicroservice.Application.DayaLoanCQ.Commands.CheckDayaLoanStatus; - -public class CheckDayaLoanStatusResponseDto -{ - public List Results { get; set; } - public int TotalChecked { get; set; } - public int SuccessCount { get; set; } -} - -public class DayaLoanStatusItem -{ - public string NationalCode { get; set; } - public DayaLoanStatus Status { get; set; } - public string? ContractNumber { get; set; } - public string Message { get; set; } -} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommand.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommand.cs deleted file mode 100644 index 5d60b9c..0000000 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommand.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CMSMicroservice.Domain.Enums; -using CMSMicroservice.Domain.Common; - -namespace CMSMicroservice.Application.DayaLoanCQ.Commands.ProcessDayaLoanApproval; - -/// -/// Command برای پردازش تایید وام دایا و شارژ کیف پول -/// -public record ProcessDayaLoanApprovalCommand : IRequest -{ - /// - /// شناسه کاربر - /// - public long UserId { get; init; } - - /// - /// شماره قرارداد دایا - /// - public string ContractNumber { get; init; } - - /// - /// مبلغ کیف پول عادی - /// - public long WalletAmount { get; init; } = SystemConstants.DayaLoanAmount; - - - - /// - /// مبلغ کیف پول تخفیف (دو برابر) - /// - public long DiscountWalletAmount { get; init; } = SystemConstants.DayaLoanAmount * 2; -} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommandHandler.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommandHandler.cs deleted file mode 100644 index 6ea84c4..0000000 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommandHandler.cs +++ /dev/null @@ -1,192 +0,0 @@ -using CMSMicroservice.Domain.Events; -using CMSMicroservice.Domain.Enums; -using CMSMicroservice.Domain.Common; -using CMSMicroservice.Application.Common.Interfaces; -using Microsoft.Extensions.Logging; - -namespace CMSMicroservice.Application.DayaLoanCQ.Commands.ProcessDayaLoanApproval; - -public class ProcessDayaLoanApprovalCommandHandler : IRequestHandler -{ - private readonly IApplicationDbContext _context; - private readonly IKavenegarService _smsService; - private readonly ILogger _logger; - - public ProcessDayaLoanApprovalCommandHandler( - IApplicationDbContext context, - IKavenegarService smsService, - ILogger logger) - { - _context = context; - _smsService = smsService; - _logger = logger; - } - - public async Task Handle(ProcessDayaLoanApprovalCommand request, CancellationToken cancellationToken) - { - // پیدا کردن کاربر - var user = await _context.Users - .Include(u => u.UserWallets) - .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); - - if (user == null) - { - throw new NotFoundException(nameof(User), request.UserId); - } - - // چک کردن که قبلاً دریافت نکرده باشد - if (user.HasReceivedDayaCredit) - { - throw new InvalidOperationException($"کاربر {request.UserId} قبلاً اعتبار دایا را دریافت کرده است"); - } - - // ایجاد تراکنش با RefId = شماره قرارداد دایا - var transaction = new Transaction - { - Amount = request.WalletAmount , // 168 میلیون - Description = $"دریافت اعتبار دایا - قرارداد {request.ContractNumber}", - PaymentStatus = PaymentStatus.Success, - PaymentDate = DateTime.Now, - RefId = request.ContractNumber, // شماره قرارداد دایا - Type = TransactionType.DepositExternal1 - }; - - await _context.Transactions.AddAsync(transaction, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - - // یافتن یا ایجاد کیف پول کاربر - var wallet = user.UserWallets.FirstOrDefault(); - if (wallet == null) - { - wallet = new UserWallet - { - UserId = request.UserId, - Balance = 0, - NetworkBalance = 0, - DiscountBalance = 0 - }; - await _context.UserWallets.AddAsync(wallet, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - } - - // شارژ کیف پول عادی (56 میلیون) - var balanceBeforeMain = wallet.Balance; - wallet.Balance += request.WalletAmount; - - // لاگ کیف پول عادی - var mainLog = new UserWalletChangeLog - { - WalletId = wallet.Id, - CurrentBalance = wallet.Balance, - ChangeValue = request.WalletAmount, - CurrentNetworkBalance = wallet.NetworkBalance, - ChangeNerworkValue = 0, - IsIncrease = true, - RefrenceId = transaction.Id - }; - await _context.UserWalletChangeLogs.AddAsync(mainLog, cancellationToken); - - - - // شارژ کیف پول تخفیف (56 میلیون) - var balanceBeforeDiscount = wallet.DiscountBalance; - wallet.DiscountBalance += request.DiscountWalletAmount; - - // لاگ کیف پول تخفیف - var discountLog = new UserWalletChangeLog - { - WalletId = wallet.Id, - CurrentBalance = wallet.Balance, - ChangeValue = 0, - CurrentNetworkBalance = wallet.NetworkBalance, - ChangeNerworkValue = 0, - CurrentDiscountBalance = wallet.DiscountBalance, - ChangeDiscountValue = request.DiscountWalletAmount, - IsIncrease = true, - RefrenceId = transaction.Id - }; - await _context.UserWalletChangeLogs.AddAsync(discountLog, cancellationToken); - - // به‌روزرسانی وضعیت کاربر - user.HasReceivedDayaCredit = true; - user.DayaCreditReceivedAt = DateTime.Now; - - // تنظیم نحوه خرید پکیج به DayaLoan - user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan; - - // ثبت سفارش پکیج (فعلاً پکیج پایه) - var goldenPackage = await _context.Packages - .FirstOrDefaultAsync(p => p.Id==4, cancellationToken); - - if (goldenPackage != null) - { - // پیدا کردن آدرس پیش‌فرض کاربر - var defaultAddress = await _context.UserAddresses - .Where(a => a.UserId == request.UserId) - .OrderByDescending(a => a.Created) - .FirstOrDefaultAsync(cancellationToken); - - if (defaultAddress != null) - { - var packageOrder = new UserOrder - { - UserId = request.UserId, - PackageId = goldenPackage.Id, - Amount = request.WalletAmount, // 56 میلیون - PaymentStatus = PaymentStatus.Success, - PaymentDate = DateTime.Now, - DeliveryStatus = DeliveryStatus.None, - UserAddressId = defaultAddress.Id, - TransactionId = transaction.Id, - PaymentMethod = PaymentMethod.IPG - }; - - await _context.UserOrders.AddAsync(packageOrder, cancellationToken); - } - } - - // ثبت Event - user.AddDomainEvent(new DayaLoanApprovedEvent(user, transaction, request.ContractNumber)); - - await _context.SaveChangesAsync(cancellationToken); - - // ارسال SMS به کاربر - await SendDayaLoanSmsAsync(user); - - return new ProcessDayaLoanApprovalResponseDto - { - UserId = user.Id, - TransactionId = transaction.Id, - ContractNumber = request.ContractNumber, - MainWalletBalance = wallet.Balance, - LockedWalletBalance = wallet.NetworkBalance, - DiscountWalletBalance = wallet.DiscountBalance, - Message = "اعتبار دایا با موفقیت دریافت شد" - }; - } - - /// - /// ارسال SMS اطلاع‌رسانی دریافت اعتبار دایا - /// - private async Task SendDayaLoanSmsAsync(User user) - { - try - { - var userName = SmsTemplates.GetUserName(user.FirstName); - var message = SmsTemplates.DayaLoanReceived(userName); - - await _smsService.SendAsync(user.Mobile, message); - - _logger.LogInformation( - "Daya loan SMS sent to User {UserId}, Mobile: {Mobile}", - user.Id, user.Mobile); - } - catch (Exception ex) - { - // خطای SMS نباید فرایند اصلی رو متوقف کنه - _logger.LogError(ex, - "Failed to send Daya loan SMS to User {UserId}, Mobile: {Mobile}", - user.Id, user.Mobile); - } - } -} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommandValidator.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommandValidator.cs deleted file mode 100644 index de3c9fb..0000000 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalCommandValidator.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace CMSMicroservice.Application.DayaLoanCQ.Commands.ProcessDayaLoanApproval; - -public class ProcessDayaLoanApprovalCommandValidator : AbstractValidator -{ - public ProcessDayaLoanApprovalCommandValidator() - { - RuleFor(v => v.UserId) - .GreaterThan(0) - .WithMessage("شناسه کاربر باید بزرگتر از صفر باشد"); - - RuleFor(v => v.ContractNumber) - .NotEmpty() - .WithMessage("شماره قرارداد الزامی است") - .MaximumLength(100) - .WithMessage("شماره قرارداد نباید بیش از 100 کاراکتر باشد"); - - RuleFor(v => v.WalletAmount) - .GreaterThan(0) - .WithMessage("مبلغ کیف پول باید بزرگتر از صفر باشد"); - } -} diff --git a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalResponseDto.cs b/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalResponseDto.cs deleted file mode 100644 index 0dcff74..0000000 --- a/src/CMSMicroservice.Application/DayaLoanCQ/Commands/ProcessDayaLoanApproval/ProcessDayaLoanApprovalResponseDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace CMSMicroservice.Application.DayaLoanCQ.Commands.ProcessDayaLoanApproval; - -public class ProcessDayaLoanApprovalResponseDto -{ - public long UserId { get; set; } - public long TransactionId { get; set; } - public string ContractNumber { get; set; } - public long MainWalletBalance { get; set; } - public long LockedWalletBalance { get; set; } - public long DiscountWalletBalance { get; set; } - public string Message { get; set; } -} diff --git a/src/CMSMicroservice.Domain/Common/SmsTemplates.cs b/src/CMSMicroservice.Domain/Common/SmsTemplates.cs index ca7df31..680730c 100644 --- a/src/CMSMicroservice.Domain/Common/SmsTemplates.cs +++ b/src/CMSMicroservice.Domain/Common/SmsTemplates.cs @@ -9,7 +9,7 @@ public static class SmsTemplates /// پیام دریافت اعتبار دایا /// public static string DayaLoanReceived(string userName) => - $"{userName} گرامی، اعتبار شما واریز گردید. شما می‌توانید با مراجعه به سامانه کارابازار فرایند فعال‌سازی خود را ادامه دهید. با تشکر از حسن انتخاب شما"; + $"{userName} عزیز، تامین اعتبار شما انجام شد. شما می‌توانید با مراجعه به سامانه کارابازار فرایند فعال‌سازی خود را ادامه دهید. با تشکر از حسن انتخاب شما"; /// /// پیام فعال‌سازی باشگاه مشتریان @@ -64,8 +64,8 @@ public static class SmsTemplates /// /// دریافت نام کاربر یا مقدار پیش‌فرض /// - public static string GetUserName(string? firstName) => - !string.IsNullOrWhiteSpace(firstName) ? firstName : "کاربر"; + public static string GetUserName(string? fullName) => + !string.IsNullOrWhiteSpace(fullName) ? fullName : "کاربر"; #endregion } diff --git a/src/CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs b/src/CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs index 610b980..a2e4348 100644 --- a/src/CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs +++ b/src/CMSMicroservice.WebApi/Workers/DayaLoanCheckWorker.cs @@ -1,9 +1,7 @@ using Hangfire; using MediatR; using Microsoft.Extensions.Logging; -using CMSMicroservice.Application.DayaLoanCQ.Commands.CheckDayaLoanStatus; -using CMSMicroservice.Application.DayaLoanCQ.Commands.ProcessDayaLoanApproval; -using CMSMicroservice.Domain.Enums; +using CMSMicroservice.Application.DayaLoanCQ.Commands.CheckAndProcessDayaLoans; using Microsoft.EntityFrameworkCore; using CMSMicroservice.Infrastructure.Persistence; using System.Linq; @@ -11,7 +9,7 @@ using System.Linq; namespace CMSMicroservice.WebApi.Workers; /// -/// Worker برای استعلام خودکار وضعیت وام دایا (هر 15 دقیقه) +/// Worker برای استعلام خودکار وضعیت وام دایا و پردازش خودکار (هر 20 دقیقه) /// public class DayaLoanCheckWorker { @@ -39,18 +37,15 @@ public class DayaLoanCheckWorker try { - // پیدا کردن کاربرانی که: // 1. اعتبار دایا را دریافت نکرده‌اند // 2. کد ملی دارند - // 3. قبلاً شماره قرارداد نگرفته‌اند var pendingUsers = await _context.Users .Where(u => u.HasReceivedDayaCredit == false && u.NationalCode != null && - u.NationalCode != "" - ) - .Select(u => new { u.Id, u.NationalCode }) + u.NationalCode != "") + .Select(u => u.NationalCode!) .ToListAsync(); if (!pendingUsers.Any()) @@ -61,47 +56,19 @@ public class DayaLoanCheckWorker _logger.LogInformation("Found {Count} users with pending Daya loan status", pendingUsers.Count); - // استعلام از دایا - var checkCommand = new CheckDayaLoanStatusCommand + // استعلام و پردازش یکجا با Command یکپارچه + var command = new CheckAndProcessDayaLoansCommand { - NationalCodes = pendingUsers.Select(u => u.NationalCode).ToList() + NationalCodes = pendingUsers }; - var checkResult = await _mediator.Send(checkCommand); + var result = await _mediator.Send(command); - // پردازش نتایج - foreach (var result in checkResult.Results) - { - // فقط وضعیت PendingReceive را پردازش می‌کنیم (یعنی وام درخواست شده) - if (result.Status == DayaLoanStatus.PendingReceive && !string.IsNullOrEmpty(result.ContractNumber)) - { - var user = pendingUsers.FirstOrDefault(u => u.NationalCode == result.NationalCode); - if (user != null) - { - try - { - // پردازش تایید وام و شارژ کیف پول - var processCommand = new ProcessDayaLoanApprovalCommand - { - UserId = user.Id, - ContractNumber = result.ContractNumber, - }; - - var processResult = await _mediator.Send(processCommand); - - _logger.LogInformation("Daya loan processed for user {UserId}. Contract: {ContractNumber}", - user.Id, result.ContractNumber); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error processing Daya loan for user {UserId}", user.Id); - } - } - } - } - - _logger.LogInformation("DayaLoanCheckWorker completed. Checked: {Total}, Processed: {Success}", - checkResult.TotalChecked, checkResult.SuccessCount); + _logger.LogInformation( + "DayaLoanCheckWorker completed. Checked: {Total}, WithContract: {WithContract}, Processed: {Processed}", + result.TotalChecked, + result.WithContractCount, + result.ProcessedCount); } catch (Exception ex) { @@ -111,15 +78,14 @@ public class DayaLoanCheckWorker } /// - /// متد برای Schedule کردن Worker (هر 15 دقیقه) + /// متد برای Schedule کردن Worker (هر 20 دقیقه) /// public static void Schedule(IRecurringJobManager recurringJobManager) { - // هر 15 دقیقه: */15 * * * * recurringJobManager.AddOrUpdate( "daya-loan-check", worker => worker.ExecuteAsync(), - "*/20 * * * *", // هر 15 دقیقه + "*/20 * * * *", TimeZoneInfo.Local ); }