F2-F7: نوتیفیکیشن‌ها+نام‌پکیج، ستون پکیج در CSV، مقادیر داینامیک کیف‌پول جادویی، ولیدیتور SystemConstants
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m29s

F2: اضافه شدن packageName به SmsTemplates، IUserNotificationService، UserNotificationService
    - DayaLoan، ClubActivated، CommissionDeposited حالا نام پکیج را نشان می‌دهند
F4: اضافه شدن ستون پکیج به CSV خروجی‌ها
    - commission.proto: package_name در WithdrawalRequestModel
    - manualpayment.proto: package_name در ManualPaymentModel
    - کوئری‌هندلرها UserPackagePurchases لوکاپ اضافه شد
F6: مقادیر داینامیک کیف‌پول جادویی از پکیج
    - userwallet.proto: magic_multiplier + magic_max_credit
    - UserWalletService: پاپیولیت فیلدهای جدید + فالبک به SystemConstants
F7: ولیدیتورها از SystemConstants استفاده می‌کنند
    - WalletMaxSafeAmount (10B ریال) به عنوان حصار ایمنی
    - MagicWalletMinCharge، DiscountWalletMinCharge ثابت‌های مرکزی
    - سقف واقعی per-package در هندلرها اعمال می‌شود
Proto: v0.0.189
This commit is contained in:
masoodafar-web
2026-02-27 08:47:46 +03:30
parent e5bc3a952a
commit 61b7e4f8f2
18 changed files with 129 additions and 33 deletions
@@ -51,6 +51,15 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRe
.PaginatedListAsync(paginationState: request.PaginationState)
.ToListAsync(cancellationToken);
// دریافت PackageName از آخرین خرید پکیج کاربران
var userIds = models.Select(x => x.UserId).Distinct().ToList();
var userPackageNames = await _context.UserPackagePurchases
.Where(p => userIds.Contains(p.UserId))
.Include(p => p.Package)
.GroupBy(p => p.UserId)
.Select(g => new { UserId = g.Key, PackageName = g.OrderByDescending(x => x.PurchasedAt).First().Package.Title })
.ToDictionaryAsync(x => x.UserId, x => x.PackageName, cancellationToken);
var result = models.Select(x => new WithdrawalRequestModel
{
Id = x.Id,
@@ -69,7 +78,8 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRe
BankReferenceId = x.BankReferenceId,
BankTrackingCode = x.BankTrackingCode,
PaymentFailureReason = x.PaymentFailureReason,
Created = x.Created
Created = x.Created,
PackageName = userPackageNames.GetValueOrDefault(x.UserId)
}).ToList();
return new GetWithdrawalRequestsResponseDto
@@ -25,4 +25,5 @@ public class WithdrawalRequestModel
public string? BankTrackingCode { get; set; }
public string? PaymentFailureReason { get; set; }
public DateTime Created { get; set; }
public string? PackageName { get; set; }
}
@@ -34,14 +34,16 @@ public interface IUserNotificationService
Task SendCommissionReceivedNotificationAsync(
long userId,
decimal amount,
int weekNumber,
int weekNumber,
string? packageName = null,
CancellationToken cancellationToken = default);
/// <summary>
/// ارسال اعلان فعال‌سازی عضویت باشگاه
/// </summary>
Task SendClubActivationNotificationAsync(
long userId,
long userId,
string? packageName = null,
CancellationToken cancellationToken = default);
/// <summary>
@@ -49,6 +51,7 @@ public interface IUserNotificationService
/// </summary>
Task SendPayoutErrorNotificationAsync(
long userId,
string errorMessage,
string errorMessage,
string? packageName = null,
CancellationToken cancellationToken = default);
}
@@ -122,6 +122,7 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
contract.IsProcessed = true;
result.WasProcessed = true;
result.NewWalletBalance = processResult.NewBalance;
result.PackageName = processResult.PackageName;
result.Message = "وام پردازش و کیف پول شارژ شد";
processedCount++;
@@ -181,7 +182,7 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
/// <summary>
/// پردازش مالی وام دایا
/// </summary>
private async Task<(long NewBalance, long TransactionId)> ProcessDayaLoanAsync(
private async Task<(long NewBalance, long TransactionId, string PackageName)> ProcessDayaLoanAsync(
User user,
string contractNumber,
CancellationToken cancellationToken)
@@ -279,25 +280,25 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
await _context.SaveChangesAsync(cancellationToken);
// 8. ارسال SMS
await SendDayaLoanSmsAsync(user);
await SendDayaLoanSmsAsync(user, package.Title);
return (wallet.Balance, transaction.Id);
return (wallet.Balance, transaction.Id, package.Title);
}
/// <summary>
/// ارسال SMS اطلاع‌رسانی
/// </summary>
private async Task SendDayaLoanSmsAsync(User user)
private async Task SendDayaLoanSmsAsync(User user, string packageName)
{
try
{
var userName = SmsTemplates.GetUserName(user.FirstName+" "+user.LastName);
var message = SmsTemplates.DayaLoanReceived(userName);
var message = SmsTemplates.DayaLoanReceived(userName, packageName);
await _smsService.SendAsync(user.Mobile, message);
// ارسال SMS به ادمین برای اطلاع
var adminMessage = $"وام دایا دریافت شد\nکاربر: {user.FirstName} {user.LastName}\nموبایل: {user.Mobile}\nکدملی: {user.NationalCode}";
var adminMessage = $"وام دایا دریافت شد\nکاربر: {user.FirstName} {user.LastName}\nموبایل: {user.Mobile}\nکدملی: {user.NationalCode}\nپکیج: {packageName}";
await _smsService.SendAsync("09199877503", adminMessage);
_logger.LogInformation("Daya loan SMS sent to User {UserId}", user.Id);
@@ -63,6 +63,11 @@ public class DayaLoanProcessResult
/// </summary>
public long? NewWalletBalance { get; set; }
/// <summary>
/// نام پکیج
/// </summary>
public string? PackageName { get; set; }
/// <summary>
/// پیام
/// </summary>
@@ -1,3 +1,4 @@
using CMSMicroservice.Domain.Common;
using FluentValidation;
namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment;
@@ -10,11 +11,12 @@ public class CreateManualPaymentCommandValidator : AbstractValidator<CreateManua
.GreaterThan(0)
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
// حصار ایمنی — مبلغ واقعی از پکیج کاربر خوانده می‌شود
RuleFor(x => x.Amount)
.GreaterThan(0)
.WithMessage("مبلغ باید بزرگتر از صفر باشد")
.LessThanOrEqualTo(1_000_000_000)
.WithMessage("مبلغ نمی‌تواند بیشتر از 1 میلیارد ریال باشد");
.LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount)
.WithMessage("مبلغ وارد شده بیش از حد مجاز است");
RuleFor(x => x.Type)
.IsInEnum()
@@ -69,6 +69,14 @@ public class GetAllManualPaymentsQueryHandler
// Pagination
var skip = (request.PageNumber - 1) * request.PageSize;
// دریافت PackageName از آخرین خرید پکیج کاربر
var userPackageNames = await _context.UserPackagePurchases
.Include(p => p.Package)
.GroupBy(p => p.UserId)
.Select(g => new { UserId = g.Key, PackageName = g.OrderByDescending(x => x.PurchasedAt).First().Package.Title })
.ToDictionaryAsync(x => x.UserId, x => x.PackageName, cancellationToken);
var data = await query
.Skip(skip)
.Take(request.PageSize)
@@ -95,6 +103,13 @@ public class GetAllManualPaymentsQueryHandler
Created = m.Created
})
.ToListAsync(cancellationToken);
// تکمیل PackageName
foreach (var item in data)
{
if (userPackageNames.TryGetValue(item.UserId, out var pkgName))
item.PackageName = pkgName;
}
var metaData = new MetaData
{
@@ -30,4 +30,5 @@ public class ManualPaymentDto
public string? RejectionReason { get; set; }
public long? TransactionId { get; set; }
public DateTime Created { get; set; }
public string? PackageName { get; set; }
}
@@ -1,3 +1,4 @@
using CMSMicroservice.Domain.Common;
using FluentValidation;
namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet;
@@ -10,10 +11,11 @@ public class ChargeDiscountWalletCommandValidator : AbstractValidator<ChargeDisc
.GreaterThan(0)
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
// حصار ایمنی — سقف تخفیفی فعلاً سراسری است
RuleFor(x => x.Amount)
.GreaterThanOrEqualTo(10_000)
.WithMessage("حداقل مبلغ شارژ 10,000 تومان است")
.LessThanOrEqualTo(1_000_000_000)
.WithMessage("حداکثر مبلغ شارژ 1,000,000,000 تومان است");
.GreaterThanOrEqualTo(SystemConstants.DiscountWalletMinCharge)
.WithMessage("حداقل مبلغ شارژ ۱,۰۰۰ تومان است")
.LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount)
.WithMessage("مبلغ وارد شده بیش از حد مجاز است");
}
}
@@ -1,3 +1,4 @@
using CMSMicroservice.Domain.Common;
using FluentValidation;
namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet;
@@ -10,10 +11,11 @@ public class ChargeMagicWalletCommandValidator : AbstractValidator<ChargeMagicWa
.GreaterThan(0)
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
// حصار ایمنی — سقف واقعی per-package در هندلر اعمال می‌شود
RuleFor(x => x.Amount)
.GreaterThanOrEqualTo(100_000)
.GreaterThanOrEqualTo(SystemConstants.MagicWalletMinCharge)
.WithMessage("حداقل مبلغ شارژ جادویی ۱۰,۰۰۰ تومان است")
.LessThanOrEqualTo(1_000_000_000)
.WithMessage("حداکثر مبلغ شارژ جادویی ۱۰۰,۰۰۰,۰۰۰ تومان است");
.LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount)
.WithMessage("مبلغ وارد شده بیش از حد مجاز است");
}
}