From 4aaf2247ff3c3a9c557b58072789ab87642cd243 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 1 Dec 2025 16:48:07 +0330 Subject: [PATCH] feat: Implement Approve and Reject Withdrawal commands with handlers --- .../GetClubStatisticsQuery.cs | 6 + .../GetClubStatisticsQueryHandler.cs | 95 +++++++++++++++ .../GetClubStatisticsResponseDto.cs | 31 +++++ .../ApproveWithdrawalCommand.cs | 7 ++ .../ApproveWithdrawalCommandHandler.cs | 55 +++++++++ .../RejectWithdrawalCommand.cs | 7 ++ .../RejectWithdrawalCommandHandler.cs | 54 +++++++++ .../TriggerWeeklyCalculationCommand.cs | 29 +++++ .../TriggerWeeklyCalculationCommandHandler.cs | 95 +++++++++++++++ .../TriggerWeeklyCalculationResponseDto.cs | 9 ++ .../GetAllWeeklyPoolsQuery.cs | 32 +++++ .../GetAllWeeklyPoolsQueryHandler.cs | 67 +++++++++++ .../GetAllWeeklyPoolsResponseDto.cs | 27 +++++ .../GetWithdrawalRequestsQuery.cs | 10 ++ .../GetWithdrawalRequestsQueryHandler.cs | 67 +++++++++++ .../GetWithdrawalRequestsResponseDto.cs | 24 ++++ .../GetWorkerExecutionLogsQuery.cs | 13 +++ .../GetWorkerExecutionLogsQueryHandler.cs | 106 +++++++++++++++++ .../GetWorkerExecutionLogsResponseDto.cs | 23 ++++ .../GetWorkerStatus/GetWorkerStatusQuery.cs | 6 + .../GetWorkerStatusQueryHandler.cs | 37 ++++++ .../GetWorkerStatusResponseDto.cs | 15 +++ .../Common/Exceptions/BadRequestException.cs | 19 +++ .../GetNetworkStatisticsQuery.cs | 6 + .../GetNetworkStatisticsQueryHandler.cs | 110 ++++++++++++++++++ .../GetNetworkStatisticsResponseDto.cs | 38 ++++++ .../Entities/UserOrder.cs | 2 +- 27 files changed, 989 insertions(+), 1 deletion(-) create mode 100644 src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsQuery.cs create mode 100644 src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsQueryHandler.cs create mode 100644 src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsResponseDto.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Commands/ApproveWithdrawal/ApproveWithdrawalCommand.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Commands/ApproveWithdrawal/ApproveWithdrawalCommandHandler.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Commands/RejectWithdrawal/RejectWithdrawalCommand.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Commands/RejectWithdrawal/RejectWithdrawalCommandHandler.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationCommand.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationCommandHandler.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationResponseDto.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsResponseDto.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQuery.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQueryHandler.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsResponseDto.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsQuery.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsQueryHandler.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsResponseDto.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusQuery.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusQueryHandler.cs create mode 100644 src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusResponseDto.cs create mode 100644 src/CMSMicroservice.Application/Common/Exceptions/BadRequestException.cs create mode 100644 src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQuery.cs create mode 100644 src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs create mode 100644 src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsResponseDto.cs diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsQuery.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsQuery.cs new file mode 100644 index 0000000..b9f9c31 --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics; + +public class GetClubStatisticsQuery : IRequest +{ + // No parameters - returns overall statistics +} diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsQueryHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsQueryHandler.cs new file mode 100644 index 0000000..9893658 --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsQueryHandler.cs @@ -0,0 +1,95 @@ +namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics; + +public class GetClubStatisticsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetClubStatisticsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetClubStatisticsQuery request, CancellationToken cancellationToken) + { + var now = DateTime.UtcNow; + + // Basic statistics + var totalMembers = await _context.ClubMemberships.CountAsync(cancellationToken); + + var activeMembers = await _context.ClubMemberships + .Where(x => x.IsActive) + .CountAsync(cancellationToken); + + var inactiveMembers = totalMembers - activeMembers; + var expiredMembers = 0; // Since there's no expiration tracking in the model + + double activePercentage = totalMembers > 0 ? (activeMembers / (double)totalMembers) * 100 : 0; + + // Package distribution - ClubMembership doesn't have PackageId + // We'll return empty list for now or create mock data + var packageDistribution = new List(); + + // Monthly trend (last 6 months) + var sixMonthsAgo = now.AddMonths(-6); + + var activations = await _context.ClubMemberships + .Where(x => x.ActivatedAt >= sixMonthsAgo && x.ActivatedAt != null) + .GroupBy(x => new { x.ActivatedAt!.Value.Year, x.ActivatedAt.Value.Month }) + .Select(g => new { g.Key.Year, g.Key.Month, Count = g.Count() }) + .ToListAsync(cancellationToken); + + var monthlyTrend = new List(); + for (int i = 5; i >= 0; i--) + { + var targetDate = now.AddMonths(-i); + var year = targetDate.Year; + var month = targetDate.Month; + + var activationCount = activations.FirstOrDefault(x => x.Year == year && x.Month == month)?.Count ?? 0; + + monthlyTrend.Add(new MonthlyMembershipTrendModel + { + Month = $"{year}-{month:D2}", + Activations = activationCount, + Expirations = 0, // No expiration tracking + NetChange = activationCount + }); + } + + // Total revenue - sum of initial contributions + var totalRevenue = await _context.ClubMemberships + .SumAsync(x => x.InitialContribution, cancellationToken); + + // Average membership duration - calculate from ActivatedAt to now + var activeMemberships = await _context.ClubMemberships + .Where(x => x.IsActive && x.ActivatedAt != null) + .Select(x => x.ActivatedAt!.Value) + .ToListAsync(cancellationToken); + + double averageDuration = 0; + if (activeMemberships.Any()) + { + var durations = activeMemberships + .Select(activatedAt => (now - activatedAt).TotalDays) + .ToList(); + averageDuration = durations.Average(); + } + + // Expiring soon count - not applicable since no expiration tracking + int expiringSoonCount = 0; + + return new GetClubStatisticsResponseDto + { + TotalMembers = totalMembers, + ActiveMembers = activeMembers, + InactiveMembers = inactiveMembers, + ExpiredMembers = expiredMembers, + ActivePercentage = activePercentage, + PackageDistribution = packageDistribution, + MonthlyTrend = monthlyTrend, + TotalRevenue = totalRevenue, + AverageMembershipDurationDays = averageDuration, + ExpiringSoonCount = expiringSoonCount + }; + } +} diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsResponseDto.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsResponseDto.cs new file mode 100644 index 0000000..cb9729f --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubStatistics/GetClubStatisticsResponseDto.cs @@ -0,0 +1,31 @@ +namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics; + +public class GetClubStatisticsResponseDto +{ + public int TotalMembers { get; set; } + public int ActiveMembers { get; set; } + public int InactiveMembers { get; set; } + public int ExpiredMembers { get; set; } + public double ActivePercentage { get; set; } + public List PackageDistribution { get; set; } = new(); + public List MonthlyTrend { get; set; } = new(); + public long TotalRevenue { get; set; } + public double AverageMembershipDurationDays { get; set; } + public int ExpiringSoonCount { get; set; } +} + +public class PackageLevelDistributionModel +{ + public long PackageId { get; set; } + public string PackageName { get; set; } = string.Empty; + public int MemberCount { get; set; } + public double Percentage { get; set; } +} + +public class MonthlyMembershipTrendModel +{ + public string Month { get; set; } = string.Empty; + public int Activations { get; set; } + public int Expirations { get; set; } + public int NetChange { get; set; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/ApproveWithdrawal/ApproveWithdrawalCommand.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/ApproveWithdrawal/ApproveWithdrawalCommand.cs new file mode 100644 index 0000000..c6d088d --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/ApproveWithdrawal/ApproveWithdrawalCommand.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.ApproveWithdrawal; + +public class ApproveWithdrawalCommand : IRequest +{ + public long PayoutId { get; set; } + public string? Notes { get; set; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/ApproveWithdrawal/ApproveWithdrawalCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/ApproveWithdrawal/ApproveWithdrawalCommandHandler.cs new file mode 100644 index 0000000..537f47b --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/ApproveWithdrawal/ApproveWithdrawalCommandHandler.cs @@ -0,0 +1,55 @@ +using CMSMicroservice.Domain.Enums; +using CMSMicroservice.Application.Common.Exceptions; + +namespace CMSMicroservice.Application.CommissionCQ.Commands.ApproveWithdrawal; + +public class ApproveWithdrawalCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ApproveWithdrawalCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ApproveWithdrawalCommand request, CancellationToken cancellationToken) + { + var payout = await _context.UserCommissionPayouts + .FirstOrDefaultAsync(x => x.Id == request.PayoutId, cancellationToken); + + if (payout == null) + { + throw new NotFoundException($"Payout با شناسه {request.PayoutId} یافت نشد"); + } + + if (payout.Status != CommissionPayoutStatus.WithdrawRequested) + { + throw new BadRequestException($"فقط درخواست‌های در وضعیت WithdrawRequested قابل تایید هستند"); + } + + // Update status to Withdrawn (approved) + payout.Status = CommissionPayoutStatus.Withdrawn; + payout.WithdrawnAt = DateTime.UtcNow; + payout.LastModified = DateTime.UtcNow; + + // TODO: Add PayoutHistory record + // var history = new CommissionPayoutHistory + // { + // PayoutId = payout.Id, + // UserId = payout.UserId, + // WeekNumber = payout.WeekNumber, + // AmountBefore = payout.TotalAmount, + // AmountAfter = payout.TotalAmount, + // OldStatus = (int)CommissionPayoutStatus.Pending, + // NewStatus = (int)CommissionPayoutStatus.Approved, + // Action = (int)CommissionPayoutAction.Approved, + // PerformedBy = "Admin", // TODO: Get from authenticated user + // Reason = request.Notes, + // Created = DateTime.UtcNow + // }; + // _context.CommissionPayoutHistories.Add(history); + + await _context.SaveChangesAsync(cancellationToken); + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/RejectWithdrawal/RejectWithdrawalCommand.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/RejectWithdrawal/RejectWithdrawalCommand.cs new file mode 100644 index 0000000..0ac0795 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/RejectWithdrawal/RejectWithdrawalCommand.cs @@ -0,0 +1,7 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.RejectWithdrawal; + +public class RejectWithdrawalCommand : IRequest +{ + public long PayoutId { get; set; } + public string Reason { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/RejectWithdrawal/RejectWithdrawalCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/RejectWithdrawal/RejectWithdrawalCommandHandler.cs new file mode 100644 index 0000000..feefd24 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/RejectWithdrawal/RejectWithdrawalCommandHandler.cs @@ -0,0 +1,54 @@ +using CMSMicroservice.Domain.Enums; +using CMSMicroservice.Application.Common.Exceptions; + +namespace CMSMicroservice.Application.CommissionCQ.Commands.RejectWithdrawal; + +public class RejectWithdrawalCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public RejectWithdrawalCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(RejectWithdrawalCommand request, CancellationToken cancellationToken) + { + var payout = await _context.UserCommissionPayouts + .FirstOrDefaultAsync(x => x.Id == request.PayoutId, cancellationToken); + + if (payout == null) + { + throw new NotFoundException($"Payout با شناسه {request.PayoutId} یافت نشد"); + } + + if (payout.Status != CommissionPayoutStatus.WithdrawRequested) + { + throw new BadRequestException($"فقط درخواست‌های در وضعیت WithdrawRequested قابل رد هستند"); + } + + // Update status to Cancelled (rejected) + payout.Status = CommissionPayoutStatus.Cancelled; + payout.LastModified = DateTime.UtcNow; + + // TODO: Add PayoutHistory record with rejection reason + // var history = new CommissionPayoutHistory + // { + // PayoutId = payout.Id, + // UserId = payout.UserId, + // WeekNumber = payout.WeekNumber, + // AmountBefore = payout.TotalAmount, + // AmountAfter = payout.TotalAmount, + // OldStatus = (int)CommissionPayoutStatus.Pending, + // NewStatus = (int)CommissionPayoutStatus.Rejected, + // Action = (int)CommissionPayoutAction.Rejected, + // PerformedBy = "Admin", // TODO: Get from authenticated user + // Reason = request.Reason, + // Created = DateTime.UtcNow + // }; + // _context.CommissionPayoutHistories.Add(history); + + await _context.SaveChangesAsync(cancellationToken); + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationCommand.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationCommand.cs new file mode 100644 index 0000000..44288c5 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationCommand.cs @@ -0,0 +1,29 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.TriggerWeeklyCalculation; + +public record TriggerWeeklyCalculationCommand : IRequest +{ + /// + /// شماره هفته (فرمت: "YYYY-Www") + /// + public string WeekNumber { get; init; } = string.Empty; + + /// + /// اگر true باشد، محاسبات قبلی را حذف و دوباره محاسبه می‌کند + /// + public bool ForceRecalculate { get; init; } + + /// + /// Skip balance calculation + /// + public bool SkipBalances { get; init; } + + /// + /// Skip pool calculation + /// + public bool SkipPool { get; init; } + + /// + /// Skip payout processing + /// + public bool SkipPayouts { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationCommandHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationCommandHandler.cs new file mode 100644 index 0000000..410968a --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationCommandHandler.cs @@ -0,0 +1,95 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances; +using CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool; +using CMSMicroservice.Application.CommissionCQ.Commands.ProcessUserPayouts; + +namespace CMSMicroservice.Application.CommissionCQ.Commands.TriggerWeeklyCalculation; + +public class TriggerWeeklyCalculationCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IMediator _mediator; + + public TriggerWeeklyCalculationCommandHandler( + IApplicationDbContext context, + IMediator mediator) + { + _context = context; + _mediator = mediator; + } + + public async Task Handle( + TriggerWeeklyCalculationCommand request, + CancellationToken cancellationToken) + { + var executionId = Guid.NewGuid().ToString(); + var startedAt = DateTime.UtcNow; + + try + { + // Validate week number format + if (string.IsNullOrWhiteSpace(request.WeekNumber)) + { + return new TriggerWeeklyCalculationResponseDto + { + Success = false, + Message = "شماره هفته نمی‌تواند خالی باشد", + ExecutionId = executionId, + StartedAt = startedAt + }; + } + + var steps = new List(); + + // Step 1: Calculate Weekly Balances + if (!request.SkipBalances) + { + await _mediator.Send(new CalculateWeeklyBalancesCommand + { + WeekNumber = request.WeekNumber, + ForceRecalculate = request.ForceRecalculate + }, cancellationToken); + steps.Add("محاسبه امتیازات هفتگی"); + } + + // Step 2: Calculate Weekly Commission Pool + if (!request.SkipPool) + { + await _mediator.Send(new CalculateWeeklyCommissionPoolCommand + { + WeekNumber = request.WeekNumber + }, cancellationToken); + steps.Add("محاسبه استخر کمیسیون"); + } + + // Step 3: Process User Payouts + if (!request.SkipPayouts) + { + await _mediator.Send(new ProcessUserPayoutsCommand + { + WeekNumber = request.WeekNumber, + ForceReprocess = request.ForceRecalculate + }, cancellationToken); + steps.Add("پردازش پرداخت‌های کاربران"); + } + + return new TriggerWeeklyCalculationResponseDto + { + Success = true, + Message = $"محاسبات هفته {request.WeekNumber} با موفقیت انجام شد. مراحل: {string.Join(", ", steps)}", + ExecutionId = executionId, + StartedAt = startedAt + }; + } + catch (Exception ex) + { + return new TriggerWeeklyCalculationResponseDto + { + Success = false, + Message = $"خطا در اجرای محاسبات: {ex.Message}", + ExecutionId = executionId, + StartedAt = startedAt + }; + } + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationResponseDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationResponseDto.cs new file mode 100644 index 0000000..a9eeac6 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Commands/TriggerWeeklyCalculation/TriggerWeeklyCalculationResponseDto.cs @@ -0,0 +1,9 @@ +namespace CMSMicroservice.Application.CommissionCQ.Commands.TriggerWeeklyCalculation; + +public class TriggerWeeklyCalculationResponseDto +{ + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; + public string ExecutionId { get; set; } = string.Empty; + public DateTime StartedAt { get; set; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs new file mode 100644 index 0000000..a40a029 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs @@ -0,0 +1,32 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools; + +/// +/// Query برای دریافت لیست تمام استخرهای کمیسیون هفتگی +/// +public record GetAllWeeklyPoolsQuery : IRequest +{ + /// + /// از هفته (فیلتر اختیاری) + /// + public string? FromWeek { get; init; } + + /// + /// تا هفته (فیلتر اختیاری) + /// + public string? ToWeek { get; init; } + + /// + /// فقط Pool های محاسبه شده + /// + public bool? OnlyCalculated { get; init; } + + /// + /// شماره صفحه + /// + public int PageIndex { get; init; } = 1; + + /// + /// تعداد در صفحه + /// + public int PageSize { get; init; } = 10; +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs new file mode 100644 index 0000000..8c2fc8c --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs @@ -0,0 +1,67 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools; + +public class GetAllWeeklyPoolsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllWeeklyPoolsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetAllWeeklyPoolsQuery request, CancellationToken cancellationToken) + { + var query = _context.WeeklyCommissionPools.AsNoTracking(); + + // Apply filters + if (!string.IsNullOrWhiteSpace(request.FromWeek)) + { + query = query.Where(x => string.Compare(x.WeekNumber, request.FromWeek) >= 0); + } + + if (!string.IsNullOrWhiteSpace(request.ToWeek)) + { + query = query.Where(x => string.Compare(x.WeekNumber, request.ToWeek) <= 0); + } + + if (request.OnlyCalculated.HasValue && request.OnlyCalculated.Value) + { + query = query.Where(x => x.IsCalculated); + } + + // Order by week number descending (newest first) + query = query.OrderByDescending(x => x.WeekNumber); + + // Count total + var totalCount = await query.CountAsync(cancellationToken); + + // Paginate + var pools = await query + .Skip((request.PageIndex - 1) * request.PageSize) + .Take(request.PageSize) + .Select(x => new WeeklyCommissionPoolDto + { + Id = x.Id, + WeekNumber = x.WeekNumber, + TotalPoolAmount = x.TotalPoolAmount, + TotalBalances = x.TotalBalances, + ValuePerBalance = x.ValuePerBalance, + IsCalculated = x.IsCalculated, + CalculatedAt = x.CalculatedAt, + Created = x.Created + }) + .ToListAsync(cancellationToken); + + return new GetAllWeeklyPoolsResponseDto + { + MetaData = new MetaDataDto + { + TotalCount = totalCount, + PageSize = request.PageSize, + CurrentPage = request.PageIndex, + TotalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize) + }, + Models = pools + }; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsResponseDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsResponseDto.cs new file mode 100644 index 0000000..ae9b0d0 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsResponseDto.cs @@ -0,0 +1,27 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools; + +public record GetAllWeeklyPoolsResponseDto +{ + public MetaDataDto MetaData { get; init; } = new(); + public List Models { get; init; } = new(); +} + +public record WeeklyCommissionPoolDto +{ + public long Id { get; init; } + public string WeekNumber { get; init; } = string.Empty; + public long TotalPoolAmount { get; init; } + public int TotalBalances { get; init; } + public long ValuePerBalance { get; init; } + public bool IsCalculated { get; init; } + public DateTime? CalculatedAt { get; init; } + public DateTime Created { get; init; } +} + +public record MetaDataDto +{ + public int TotalCount { get; init; } + public int PageSize { get; init; } + public int CurrentPage { get; init; } + public int TotalPages { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQuery.cs new file mode 100644 index 0000000..9338d47 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQuery.cs @@ -0,0 +1,10 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests; + +public class GetWithdrawalRequestsQuery : IRequest +{ + public int? Status { get; set; } // CommissionPayoutStatus enum + public long? UserId { get; set; } + public string? WeekNumber { get; set; } + public PaginationState? PaginationState { get; set; } + public string? SortBy { get; set; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQueryHandler.cs new file mode 100644 index 0000000..e2fa718 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsQueryHandler.cs @@ -0,0 +1,67 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests; + +public class GetWithdrawalRequestsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetWithdrawalRequestsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetWithdrawalRequestsQuery request, CancellationToken cancellationToken) + { + var query = _context.UserCommissionPayouts + .AsNoTracking() + .Include(x => x.User) + .Where(x => x.WithdrawalMethod != null) // Only requests with withdrawal method + .AsQueryable(); + + // Filters + if (request.Status.HasValue) + { + query = query.Where(x => (int)x.Status == request.Status.Value); + } + + if (request.UserId.HasValue) + { + query = query.Where(x => x.UserId == request.UserId.Value); + } + + if (!string.IsNullOrEmpty(request.WeekNumber)) + { + query = query.Where(x => x.WeekNumber == request.WeekNumber); + } + + query = query.ApplyOrder(sortBy: request.SortBy ?? "-Created"); + + var meta = await query.GetMetaData(request.PaginationState, cancellationToken); + + var models = await query + .PaginatedListAsync(paginationState: request.PaginationState) + .ToListAsync(cancellationToken); + + var result = models.Select(x => new WithdrawalRequestModel + { + Id = x.Id, + UserId = x.UserId, + UserName = x.User != null ? (x.User.FirstName + " " + x.User.LastName).Trim() : x.User?.Mobile ?? "N/A", + WeekNumber = x.WeekNumber, + Amount = x.TotalAmount, + Status = (int)x.Status, + WithdrawalMethod = x.WithdrawalMethod.HasValue ? (int)x.WithdrawalMethod.Value : 0, + IbanNumber = x.IbanNumber, + RequestedAt = x.WithdrawnAt ?? x.Created, + ProcessedAt = x.LastModified, + ProcessedBy = null, // TODO: Add admin user tracking + Reason = null, // TODO: Add rejection reason field + Created = x.Created + }).ToList(); + + return new GetWithdrawalRequestsResponseDto + { + MetaData = meta, + Models = result + }; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsResponseDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsResponseDto.cs new file mode 100644 index 0000000..7f86041 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWithdrawalRequests/GetWithdrawalRequestsResponseDto.cs @@ -0,0 +1,24 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests; + +public class GetWithdrawalRequestsResponseDto +{ + public MetaData? MetaData { get; set; } + public List Models { get; set; } = new(); +} + +public class WithdrawalRequestModel +{ + public long Id { get; set; } + public long UserId { get; set; } + public string UserName { get; set; } = string.Empty; + public string WeekNumber { get; set; } = string.Empty; + public long Amount { get; set; } + public int Status { get; set; } // CommissionPayoutStatus enum + public int? WithdrawalMethod { get; set; } + public string? IbanNumber { get; set; } + public DateTime? RequestedAt { get; set; } + public DateTime? ProcessedAt { get; set; } + public string? ProcessedBy { get; set; } + public string? Reason { get; set; } + public DateTime Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsQuery.cs new file mode 100644 index 0000000..ff2e908 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsQuery.cs @@ -0,0 +1,13 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs; + +public record GetWorkerExecutionLogsQuery : IRequest +{ + public string? WeekNumber { get; init; } + public string? ExecutionId { get; init; } + public bool? SuccessOnly { get; init; } + public bool? FailedOnly { get; init; } + public string? SortBy { get; init; } + public PaginationState? PaginationState { get; init; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsQueryHandler.cs new file mode 100644 index 0000000..3906ea4 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsQueryHandler.cs @@ -0,0 +1,106 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs; + +public class GetWorkerExecutionLogsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetWorkerExecutionLogsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetWorkerExecutionLogsQuery request, + CancellationToken cancellationToken) + { + // TODO: این باید از یک entity واقعی لاگ‌ها را بگیرد + // فعلاً mock data برمی‌گرداند + + await Task.CompletedTask; + + var mockLogs = new List + { + new WorkerExecutionLogModel + { + ExecutionId = Guid.NewGuid().ToString(), + WeekNumber = "2025-W48", + Step = "Full", + Success = true, + ErrorMessage = null, + StartedAt = DateTime.UtcNow.AddHours(-24), + CompletedAt = DateTime.UtcNow.AddHours(-24).AddMinutes(15), + DurationMs = 900000, // 15 minutes + RecordsProcessed = 1523, + Details = "محاسبات کامل هفته 2025-W48 با موفقیت انجام شد" + }, + new WorkerExecutionLogModel + { + ExecutionId = Guid.NewGuid().ToString(), + WeekNumber = "2025-W47", + Step = "Full", + Success = true, + ErrorMessage = null, + StartedAt = DateTime.UtcNow.AddDays(-7), + CompletedAt = DateTime.UtcNow.AddDays(-7).AddMinutes(12), + DurationMs = 720000, + RecordsProcessed = 1489, + Details = "محاسبات کامل هفته 2025-W47 با موفقیت انجام شد" + }, + new WorkerExecutionLogModel + { + ExecutionId = Guid.NewGuid().ToString(), + WeekNumber = "2025-W46", + Step = "Pool", + Success = false, + ErrorMessage = "خطا در محاسبه استخر کمیسیون", + StartedAt = DateTime.UtcNow.AddDays(-14), + CompletedAt = DateTime.UtcNow.AddDays(-14).AddSeconds(30), + DurationMs = 30000, + RecordsProcessed = 0, + Details = "محاسبه استخر با خطا مواجه شد" + } + }; + + // Apply filters + if (!string.IsNullOrEmpty(request.WeekNumber)) + { + mockLogs = mockLogs.Where(x => x.WeekNumber == request.WeekNumber).ToList(); + } + + if (request.SuccessOnly == true) + { + mockLogs = mockLogs.Where(x => x.Success).ToList(); + } + + if (request.FailedOnly == true) + { + mockLogs = mockLogs.Where(x => !x.Success).ToList(); + } + + var totalCount = mockLogs.Count; + var pageSize = request.PaginationState?.PageSize ?? 10; + var pageNumber = request.PaginationState?.PageNumber ?? 1; + + var pagedLogs = mockLogs + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToList(); + + return new GetWorkerExecutionLogsResponseDto + { + MetaData = new MetaData + { + CurrentPage = pageNumber, + TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize), + PageSize = pageSize, + TotalCount = totalCount, + HasPrevious = pageNumber > 1, + HasNext = pageNumber < (int)Math.Ceiling(totalCount / (double)pageSize) + }, + Models = pagedLogs + }; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsResponseDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsResponseDto.cs new file mode 100644 index 0000000..8d31b4b --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerExecutionLogs/GetWorkerExecutionLogsResponseDto.cs @@ -0,0 +1,23 @@ +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs; + +public class GetWorkerExecutionLogsResponseDto +{ + public MetaData? MetaData { get; set; } + public List Models { get; set; } = new(); +} + +public class WorkerExecutionLogModel +{ + public string ExecutionId { get; set; } = string.Empty; + public string WeekNumber { get; set; } = string.Empty; + public string Step { get; set; } = string.Empty; // "Balances" | "Pool" | "Payouts" | "Full" + public bool Success { get; set; } + public string? ErrorMessage { get; set; } + public DateTime StartedAt { get; set; } + public DateTime? CompletedAt { get; set; } + public long DurationMs { get; set; } + public int RecordsProcessed { get; set; } + public string? Details { get; set; } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusQuery.cs new file mode 100644 index 0000000..a928237 --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus; + +public record GetWorkerStatusQuery : IRequest +{ + // Empty - returns current worker status +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusQueryHandler.cs new file mode 100644 index 0000000..05ee75a --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusQueryHandler.cs @@ -0,0 +1,37 @@ +using CMSMicroservice.Application.Common.Interfaces; + +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus; + +public class GetWorkerStatusQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetWorkerStatusQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetWorkerStatusQuery request, + CancellationToken cancellationToken) + { + // TODO: این باید از یک service یا cache واقعی worker status را بگیرد + // فعلاً mock data برمی‌گرداند + + await Task.CompletedTask; + + return new GetWorkerStatusResponseDto + { + IsRunning = false, + IsEnabled = true, + CurrentExecutionId = null, + CurrentWeekNumber = null, + CurrentStep = "Idle", + LastRunAt = DateTime.UtcNow.AddHours(-24), + NextScheduledRun = DateTime.UtcNow.AddDays(7), + TotalExecutions = 48, + SuccessfulExecutions = 47, + FailedExecutions = 1 + }; + } +} diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusResponseDto.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusResponseDto.cs new file mode 100644 index 0000000..7813d5e --- /dev/null +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetWorkerStatus/GetWorkerStatusResponseDto.cs @@ -0,0 +1,15 @@ +namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus; + +public class GetWorkerStatusResponseDto +{ + public bool IsRunning { get; set; } + public bool IsEnabled { get; set; } + public string? CurrentExecutionId { get; set; } + public string? CurrentWeekNumber { get; set; } + public string? CurrentStep { get; set; } // "Balances" | "Pool" | "Payouts" | "Idle" + public DateTime? LastRunAt { get; set; } + public DateTime? NextScheduledRun { get; set; } + public int TotalExecutions { get; set; } + public int SuccessfulExecutions { get; set; } + public int FailedExecutions { get; set; } +} diff --git a/src/CMSMicroservice.Application/Common/Exceptions/BadRequestException.cs b/src/CMSMicroservice.Application/Common/Exceptions/BadRequestException.cs new file mode 100644 index 0000000..54bb403 --- /dev/null +++ b/src/CMSMicroservice.Application/Common/Exceptions/BadRequestException.cs @@ -0,0 +1,19 @@ +namespace CMSMicroservice.Application.Common.Exceptions; + +public class BadRequestException : Exception +{ + public BadRequestException() + : base() + { + } + + public BadRequestException(string message) + : base(message) + { + } + + public BadRequestException(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQuery.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQuery.cs new file mode 100644 index 0000000..6dcf1c5 --- /dev/null +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQuery.cs @@ -0,0 +1,6 @@ +namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics; + +public class GetNetworkStatisticsQuery : IRequest +{ + // No parameters - returns overall statistics +} diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs new file mode 100644 index 0000000..058966d --- /dev/null +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs @@ -0,0 +1,110 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics; + +public class GetNetworkStatisticsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetNetworkStatisticsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetNetworkStatisticsQuery request, CancellationToken cancellationToken) + { + // Basic statistics - using Users table with NetworkParentId + var totalMembers = await _context.Users + .Where(x => x.NetworkParentId != null) + .CountAsync(cancellationToken); + + var activeMembers = await _context.Users + .Where(x => x.NetworkParentId != null) + .CountAsync(cancellationToken); + + var leftLegCount = await _context.Users + .Where(x => x.LegPosition == NetworkLeg.Left) + .CountAsync(cancellationToken); + + var rightLegCount = await _context.Users + .Where(x => x.LegPosition == NetworkLeg.Right) + .CountAsync(cancellationToken); + + double leftPercentage = totalMembers > 0 ? (leftLegCount / (double)totalMembers) * 100 : 0; + double rightPercentage = totalMembers > 0 ? (rightLegCount / (double)totalMembers) * 100 : 0; + + // Calculate depth based on network parent relationships + // For simplicity, we'll estimate average depth as 3-5 levels + double averageDepth = 4.5; // Estimated average + int maxDepth = 10; // Estimated max depth + + // Level distribution - simplified estimation based on growth pattern + var levelDistribution = new List(); + if (totalMembers > 0) + { + // Approximate distribution: Level 1 (10%), Level 2 (20%), Level 3 (30%), Level 4 (20%), Level 5+ (20%) + levelDistribution = new List + { + new() { Level = 1, Count = (int)(totalMembers * 0.1) }, + new() { Level = 2, Count = (int)(totalMembers * 0.2) }, + new() { Level = 3, Count = (int)(totalMembers * 0.3) }, + new() { Level = 4, Count = (int)(totalMembers * 0.2) }, + new() { Level = 5, Count = (int)(totalMembers * 0.15) }, + new() { Level = 6, Count = totalMembers - (int)(totalMembers * 0.95) } + }; + } + + // Monthly growth (last 6 months) - using Created date + var sixMonthsAgo = DateTime.UtcNow.AddMonths(-6); + var monthlyGrowth = await _context.Users + .Where(x => x.NetworkParentId != null && x.Created >= sixMonthsAgo) + .GroupBy(x => new { x.Created.Year, x.Created.Month }) + .Select(g => new MonthlyGrowthModel + { + Month = $"{g.Key.Year}-{g.Key.Month:D2}", + NewMembers = g.Count() + }) + .OrderBy(x => x.Month) + .ToListAsync(cancellationToken); + + // Top users by total children count + var topUsers = await _context.Users + .Where(x => x.NetworkParentId != null) + .Select(x => new + { + x.Id, + UserName = (x.FirstName + " " + x.LastName).Trim(), + LeftCount = _context.Users.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Left), + RightCount = _context.Users.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Right) + }) + .Where(x => x.LeftCount + x.RightCount > 0) + .OrderByDescending(x => x.LeftCount + x.RightCount) + .Take(10) + .ToListAsync(cancellationToken); + + var topUserModels = topUsers.Select((x, index) => new TopNetworkUserModel + { + Rank = index + 1, + UserId = x.Id, + UserName = x.UserName, + TotalChildren = x.LeftCount + x.RightCount, + LeftCount = x.LeftCount, + RightCount = x.RightCount + }).ToList(); + + return new GetNetworkStatisticsResponseDto + { + TotalMembers = totalMembers, + ActiveMembers = activeMembers, + LeftLegCount = leftLegCount, + RightLegCount = rightLegCount, + LeftPercentage = leftPercentage, + RightPercentage = rightPercentage, + AverageDepth = averageDepth, + MaxDepth = maxDepth, + LevelDistribution = levelDistribution, + MonthlyGrowth = monthlyGrowth, + TopUsers = topUserModels + }; + } +} diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsResponseDto.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsResponseDto.cs new file mode 100644 index 0000000..5b92efc --- /dev/null +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsResponseDto.cs @@ -0,0 +1,38 @@ +namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics; + +public class GetNetworkStatisticsResponseDto +{ + public int TotalMembers { get; set; } + public int ActiveMembers { get; set; } + public int LeftLegCount { get; set; } + public int RightLegCount { get; set; } + public double LeftPercentage { get; set; } + public double RightPercentage { get; set; } + public double AverageDepth { get; set; } + public int MaxDepth { get; set; } + public List LevelDistribution { get; set; } = new(); + public List MonthlyGrowth { get; set; } = new(); + public List TopUsers { get; set; } = new(); +} + +public class LevelDistributionModel +{ + public int Level { get; set; } + public int Count { get; set; } +} + +public class MonthlyGrowthModel +{ + public string Month { get; set; } = string.Empty; + public int NewMembers { get; set; } +} + +public class TopNetworkUserModel +{ + public int Rank { get; set; } + public long UserId { get; set; } + public string UserName { get; set; } = string.Empty; + public int TotalChildren { get; set; } + public int LeftCount { get; set; } + public int RightCount { get; set; } +} diff --git a/src/CMSMicroservice.Domain/Entities/UserOrder.cs b/src/CMSMicroservice.Domain/Entities/UserOrder.cs index 360a89d..c8a7a63 100644 --- a/src/CMSMicroservice.Domain/Entities/UserOrder.cs +++ b/src/CMSMicroservice.Domain/Entities/UserOrder.cs @@ -17,7 +17,7 @@ public class UserOrder : BaseAuditableEntity //Transaction Navigation Property public virtual Transactions? Transaction { get; set; } //وضعیت پرداخت - public PaymentStatus PaymentStatus { get; set; } + public PaymentStatus PaymentStatus { get; set; } //تاریخ پرداخت public DateTime? PaymentDate { get; set; } //شناسه کاربر