feat: add manual membership payment and update commission queries
Build and Deploy / build (push) Successful in 2m29s

This commit is contained in:
masoodafar-web
2025-12-12 10:23:52 +03:30
parent 19a717fc87
commit c6abee2650
18 changed files with 617 additions and 374 deletions
@@ -1,24 +1,37 @@
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetUserPayouts;
public record GetUserPayoutsQuery : IRequest<GetUserPayoutsResponseDto>
{
/// <summary>
/// موقعیت صفحه‌بندی
/// </summary>
public PaginationState? PaginationState { get; init; }
/// <summary>
/// مرتب‌سازی بر اساس
/// </summary>
public string? SortBy { get; init; }
/// <summary>
/// فیلتر
/// </summary>
public GetUserPayoutsFilter? Filter { get; init; }
}
public class GetUserPayoutsFilter
{
/// <summary>
/// شناسه کاربر
/// </summary>
public long UserId { get; init; }
public long? UserId { get; set; }
/// <summary>
/// شماره هفته (اختیاری - برای فیلتر)
/// </summary>
public string? WeekNumber { get; init; }
public string? WeekNumber { get; set; }
/// <summary>
/// شماره صفحه (پیش‌فرض: 1)
/// وضعیت (اختیاری)
/// </summary>
public int PageNumber { get; init; } = 1;
/// <summary>
/// تعداد در هر صفحه (پیش‌فرض: 10)
/// </summary>
public int PageSize { get; init; } = 10;
public int? Status { get; set; }
}
@@ -1,5 +1,3 @@
using CMSMicroservice.Protobuf.Protos.Commission;
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetUserPayouts;
@@ -15,10 +13,55 @@ public class GetUserPayoutsQueryHandler : IRequestHandler<GetUserPayoutsQuery, G
public async Task<GetUserPayoutsResponseDto> Handle(GetUserPayoutsQuery request, CancellationToken cancellationToken)
{
var cmsRequest = new GetUserCommissionPayoutsRequest
{
PageIndex = request.PaginationState?.PageNumber ?? 1,
PageSize = request.PaginationState?.PageSize ?? 10
};
if (request.Filter?.UserId.HasValue == true && request.Filter.UserId.Value > 0)
{
cmsRequest.UserId = request.Filter.UserId.Value;
}
if (!string.IsNullOrWhiteSpace(request.Filter?.WeekNumber))
{
cmsRequest.WeekNumber = request.Filter.WeekNumber;
}
if (request.Filter?.Status.HasValue == true)
{
cmsRequest.Status = request.Filter.Status.Value;
}
var response = await _context.Commissions.GetUserCommissionPayoutsAsync(
request.Adapt<GetUserCommissionPayoutsRequest>(),
cmsRequest,
cancellationToken: cancellationToken);
return response.Adapt<GetUserPayoutsResponseDto>();
return new GetUserPayoutsResponseDto
{
MetaData = new MetaData
{
CurrentPage = response.MetaData.CurrentPage,
PageSize = response.MetaData.PageSize,
TotalCount = response.MetaData.TotalCount,
TotalPage = response.MetaData.TotalPage
},
Models = response.Models.Select(m => new GetUserPayoutsResponseModel
{
Id = m.Id,
UserId = m.UserId,
UserName = m.UserName,
WeekNumber = m.WeekNumber,
BalancesEarned = m.BalancesEarned,
ValuePerBalance = m.ValuePerBalance,
TotalAmount = m.TotalAmount,
Status = m.Status,
WithdrawalMethod = m.WithdrawalMethod,
IbanNumber = m.IbanNumber,
Created = m.Created.ToDateTime(),
LastModified = m.LastModified?.ToDateTime()
}).ToList()
};
}
}
@@ -0,0 +1,24 @@
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetUserPayouts;
public class GetUserPayoutsQueryValidator : AbstractValidator<GetUserPayoutsQuery>
{
public GetUserPayoutsQueryValidator()
{
RuleFor(x => x.PaginationState)
.NotNull()
.WithMessage("موقعیت صفحه‌بندی الزامی است");
When(x => x.Filter != null, () =>
{
RuleFor(x => x.Filter!.UserId)
.GreaterThan(0)
.When(x => x.Filter!.UserId.HasValue)
.WithMessage("شناسه کاربر معتبر نیست");
RuleFor(x => x.Filter!.WeekNumber)
.Matches(@"^\d{4}-W\d{2}$")
.When(x => !string.IsNullOrWhiteSpace(x.Filter!.WeekNumber))
.WithMessage("فرمت شماره هفته باید به صورت YYYY-Www باشد (مثال: 2025-W48)");
});
}
}
@@ -3,27 +3,17 @@ namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetUserPayouts;
public class GetUserPayoutsResponseDto
{
/// <summary>
/// لیست Payout ها
/// متادیتا
/// </summary>
public List<UserPayoutDto> Payouts { get; set; } = new();
public MetaData MetaData { get; set; }
/// <summary>
/// تعداد کل رکوردها
/// مدل خروجی
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// شماره صفحه فعلی
/// </summary>
public int PageNumber { get; set; }
/// <summary>
/// تعداد در هر صفحه
/// </summary>
public int PageSize { get; set; }
public List<GetUserPayoutsResponseModel>? Models { get; set; }
}
public class UserPayoutDto
public class GetUserPayoutsResponseModel
{
/// <summary>
/// شناسه Payout
@@ -35,43 +25,53 @@ public class UserPayoutDto
/// </summary>
public long UserId { get; set; }
/// <summary>
/// نام کاربر
/// </summary>
public string UserName { get; set; } = string.Empty;
/// <summary>
/// شماره هفته
/// </summary>
public string WeekNumber { get; set; } = string.Empty;
/// <summary>
/// بالانس Leg چپ
/// تعداد تعادل‌های کسب شده
/// </summary>
public decimal LeftLegBalance { get; set; }
public int BalancesEarned { get; set; }
/// <summary>
/// بالانس Leg راست
/// ارزش هر تعادل
/// </summary>
public decimal RightLegBalance { get; set; }
public long ValuePerBalance { get; set; }
/// <summary>
/// بالانس Leg ضعیف‌تر
/// مبلغ کل
/// </summary>
public decimal WeakerLegBalance { get; set; }
public long TotalAmount { get; set; }
/// <summary>
/// مبلغ کمیسیون (تومان)
/// وضعیت (0=Pending, 1=Paid, 2=Failed)
/// </summary>
public decimal CommissionAmount { get; set; }
public int Status { get; set; }
/// <summary>
/// وضعیت پرداخت
/// روش برداشت
/// </summary>
public string Status { get; set; } = string.Empty;
public int? WithdrawalMethod { get; set; }
/// <summary>
/// تاریخ پرداخت
/// شماره شبا
/// </summary>
public DateTime? PaidAt { get; set; }
public string IbanNumber { get; set; } = string.Empty;
/// <summary>
/// تاریخ ایجاد
/// </summary>
public DateTime CreatedAt { get; set; }
public DateTime Created { get; set; }
/// <summary>
/// آخرین تغییر
/// </summary>
public DateTime? LastModified { get; set; }
}
@@ -1,29 +1,37 @@
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
public record GetUserWeeklyBalancesQuery : IRequest<GetUserWeeklyBalancesResponseDto>
{
/// <summary>
/// موقعیت صفحه‌بندی
/// </summary>
public PaginationState? PaginationState { get; init; }
/// <summary>
/// مرتب‌سازی بر اساس
/// </summary>
public string? SortBy { get; init; }
/// <summary>
/// فیلتر
/// </summary>
public GetUserWeeklyBalancesFilter? Filter { get; init; }
}
public class GetUserWeeklyBalancesFilter
{
/// <summary>
/// شناسه کاربر (فیلتر اختیاری)
/// </summary>
public long? UserId { get; init; }
public long? UserId { get; set; }
/// <summary>
/// شماره هفته (فیلتر اختیاری)
/// </summary>
public string? WeekNumber { get; init; }
public string? WeekNumber { get; set; }
/// <summary>
/// فقط تعادل‌های فعال (منقضی نشده)
/// </summary>
public bool OnlyActive { get; init; }
/// <summary>
/// شماره صفحه
/// </summary>
public int PageIndex { get; init; } = 1;
/// <summary>
/// تعداد در صفحه
/// </summary>
public int PageSize { get; init; } = 10;
public bool? OnlyActive { get; set; }
}
@@ -17,19 +17,19 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
{
var grpcRequest = new GetUserWeeklyBalancesRequest
{
OnlyActive = request.OnlyActive,
PageIndex = request.PageIndex,
PageSize = request.PageSize
OnlyActive = request.Filter?.OnlyActive ?? false,
PageIndex = request.PaginationState?.PageNumber ?? 1,
PageSize = request.PaginationState?.PageSize ?? 10
};
if (request.UserId.HasValue)
if (request.Filter?.UserId.HasValue == true && request.Filter.UserId.Value > 0)
{
grpcRequest.UserId = request.UserId.Value;
grpcRequest.UserId = request.Filter.UserId.Value;
}
if (!string.IsNullOrWhiteSpace(request.WeekNumber))
if (!string.IsNullOrWhiteSpace(request.Filter?.WeekNumber))
{
grpcRequest.WeekNumber = request.WeekNumber;
grpcRequest.WeekNumber = request.Filter.WeekNumber;
}
var response = await _context.Commissions.GetUserWeeklyBalancesAsync(grpcRequest, cancellationToken: cancellationToken);
@@ -0,0 +1,24 @@
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
public class GetUserWeeklyBalancesQueryValidator : AbstractValidator<GetUserWeeklyBalancesQuery>
{
public GetUserWeeklyBalancesQueryValidator()
{
RuleFor(x => x.PaginationState)
.NotNull()
.WithMessage("موقعیت صفحه‌بندی الزامی است");
When(x => x.Filter != null, () =>
{
RuleFor(x => x.Filter!.UserId)
.GreaterThan(0)
.When(x => x.Filter!.UserId.HasValue)
.WithMessage("شناسه کاربر معتبر نیست");
RuleFor(x => x.Filter!.WeekNumber)
.Matches(@"^\d{4}-W\d{2}$")
.When(x => !string.IsNullOrWhiteSpace(x.Filter!.WeekNumber))
.WithMessage("فرمت شماره هفته باید به صورت YYYY-Www باشد (مثال: 2025-W48)");
});
}
}
@@ -0,0 +1,9 @@
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.ProcessManualMembershipPayment;
public record ProcessManualMembershipPaymentCommand : IRequest<ProcessManualMembershipPaymentResponseDto>
{
public long UserId { get; init; }
public long Amount { get; init; }
public string ReferenceNumber { get; init; } = string.Empty;
public string? Description { get; init; }
}
@@ -0,0 +1,55 @@
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.ProcessManualMembershipPayment;
public class ProcessManualMembershipPaymentCommandHandler : IRequestHandler<ProcessManualMembershipPaymentCommand, ProcessManualMembershipPaymentResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ICurrentUserService _currentUser;
private readonly ILogger<ProcessManualMembershipPaymentCommandHandler> _logger;
public ProcessManualMembershipPaymentCommandHandler(
IApplicationContractContext context,
ICurrentUserService currentUser,
ILogger<ProcessManualMembershipPaymentCommandHandler> logger)
{
_context = context;
_currentUser = currentUser;
_logger = logger;
}
public async Task<ProcessManualMembershipPaymentResponseDto> Handle(
ProcessManualMembershipPaymentCommand request,
CancellationToken cancellationToken)
{
_logger.LogInformation(
"Processing manual membership payment via BFF for UserId {UserId}, Amount {Amount}",
request.UserId,
request.Amount);
var grpcRequest = new ProcessManualMembershipPaymentRequest
{
UserId = request.UserId,
Amount = request.Amount,
ReferenceNumber = request.ReferenceNumber
};
if (!string.IsNullOrWhiteSpace(request.Description))
{
grpcRequest.Description = request.Description;
}
var response = await _context.ManualPayments.ProcessManualMembershipPaymentAsync(
grpcRequest,
cancellationToken: cancellationToken);
return new ProcessManualMembershipPaymentResponseDto
{
TransactionId = response.TransactionId,
OrderId = response.OrderId,
NewWalletBalance = response.NewWalletBalance,
Message = response.Message
};
}
}
@@ -0,0 +1,21 @@
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.ProcessManualMembershipPayment;
public class ProcessManualMembershipPaymentCommandValidator : AbstractValidator<ProcessManualMembershipPaymentCommand>
{
public ProcessManualMembershipPaymentCommandValidator()
{
RuleFor(x => x.UserId)
.GreaterThan(0)
.WithMessage("شناسه کاربر معتبر نیست");
RuleFor(x => x.Amount)
.GreaterThan(0)
.WithMessage("مبلغ باید بزرگتر از صفر باشد");
RuleFor(x => x.ReferenceNumber)
.NotEmpty()
.WithMessage("شماره مرجع الزامی است")
.MaximumLength(50)
.WithMessage("شماره مرجع نباید بیشتر از 50 کاراکتر باشد");
}
}
@@ -0,0 +1,9 @@
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.ProcessManualMembershipPayment;
public class ProcessManualMembershipPaymentResponseDto
{
public long TransactionId { get; set; }
public long OrderId { get; set; }
public long NewWalletBalance { get; set; }
public string Message { get; set; } = string.Empty;
}