feat: Implement Approve and Reject Withdrawal commands with handlers
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics;
|
||||||
|
|
||||||
|
public class GetClubStatisticsQuery : IRequest<GetClubStatisticsResponseDto>
|
||||||
|
{
|
||||||
|
// No parameters - returns overall statistics
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics;
|
||||||
|
|
||||||
|
public class GetClubStatisticsQueryHandler : IRequestHandler<GetClubStatisticsQuery, GetClubStatisticsResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetClubStatisticsQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetClubStatisticsResponseDto> 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<PackageLevelDistributionModel>();
|
||||||
|
|
||||||
|
// 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<MonthlyMembershipTrendModel>();
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<PackageLevelDistributionModel> PackageDistribution { get; set; } = new();
|
||||||
|
public List<MonthlyMembershipTrendModel> 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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Commands.ApproveWithdrawal;
|
||||||
|
|
||||||
|
public class ApproveWithdrawalCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
public long PayoutId { get; set; }
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Commands.ApproveWithdrawal;
|
||||||
|
|
||||||
|
public class ApproveWithdrawalCommandHandler : IRequestHandler<ApproveWithdrawalCommand, Unit>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public ApproveWithdrawalCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Commands.RejectWithdrawal;
|
||||||
|
|
||||||
|
public class RejectWithdrawalCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
public long PayoutId { get; set; }
|
||||||
|
public string Reason { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Commands.RejectWithdrawal;
|
||||||
|
|
||||||
|
public class RejectWithdrawalCommandHandler : IRequestHandler<RejectWithdrawalCommand, Unit>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public RejectWithdrawalCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Commands.TriggerWeeklyCalculation;
|
||||||
|
|
||||||
|
public record TriggerWeeklyCalculationCommand : IRequest<TriggerWeeklyCalculationResponseDto>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// شماره هفته (فرمت: "YYYY-Www")
|
||||||
|
/// </summary>
|
||||||
|
public string WeekNumber { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// اگر true باشد، محاسبات قبلی را حذف و دوباره محاسبه میکند
|
||||||
|
/// </summary>
|
||||||
|
public bool ForceRecalculate { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skip balance calculation
|
||||||
|
/// </summary>
|
||||||
|
public bool SkipBalances { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skip pool calculation
|
||||||
|
/// </summary>
|
||||||
|
public bool SkipPool { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skip payout processing
|
||||||
|
/// </summary>
|
||||||
|
public bool SkipPayouts { get; init; }
|
||||||
|
}
|
||||||
@@ -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<TriggerWeeklyCalculationCommand, TriggerWeeklyCalculationResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
|
public TriggerWeeklyCalculationCommandHandler(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
IMediator mediator)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TriggerWeeklyCalculationResponseDto> 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<string>();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query برای دریافت لیست تمام استخرهای کمیسیون هفتگی
|
||||||
|
/// </summary>
|
||||||
|
public record GetAllWeeklyPoolsQuery : IRequest<GetAllWeeklyPoolsResponseDto>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// از هفته (فیلتر اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public string? FromWeek { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تا هفته (فیلتر اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public string? ToWeek { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فقط Pool های محاسبه شده
|
||||||
|
/// </summary>
|
||||||
|
public bool? OnlyCalculated { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شماره صفحه
|
||||||
|
/// </summary>
|
||||||
|
public int PageIndex { get; init; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد در صفحه
|
||||||
|
/// </summary>
|
||||||
|
public int PageSize { get; init; } = 10;
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
|
||||||
|
|
||||||
|
public class GetAllWeeklyPoolsQueryHandler : IRequestHandler<GetAllWeeklyPoolsQuery, GetAllWeeklyPoolsResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetAllWeeklyPoolsQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetAllWeeklyPoolsResponseDto> 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
|
||||||
|
|
||||||
|
public record GetAllWeeklyPoolsResponseDto
|
||||||
|
{
|
||||||
|
public MetaDataDto MetaData { get; init; } = new();
|
||||||
|
public List<WeeklyCommissionPoolDto> 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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
|
||||||
|
|
||||||
|
public class GetWithdrawalRequestsQuery : IRequest<GetWithdrawalRequestsResponseDto>
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
|
||||||
|
|
||||||
|
public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRequestsQuery, GetWithdrawalRequestsResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetWithdrawalRequestsQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetWithdrawalRequestsResponseDto> 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
|
||||||
|
|
||||||
|
public class GetWithdrawalRequestsResponseDto
|
||||||
|
{
|
||||||
|
public MetaData? MetaData { get; set; }
|
||||||
|
public List<WithdrawalRequestModel> 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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;
|
||||||
|
|
||||||
|
public record GetWorkerExecutionLogsQuery : IRequest<GetWorkerExecutionLogsResponseDto>
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;
|
||||||
|
|
||||||
|
public class GetWorkerExecutionLogsQueryHandler : IRequestHandler<GetWorkerExecutionLogsQuery, GetWorkerExecutionLogsResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetWorkerExecutionLogsQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetWorkerExecutionLogsResponseDto> Handle(
|
||||||
|
GetWorkerExecutionLogsQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// TODO: این باید از یک entity واقعی لاگها را بگیرد
|
||||||
|
// فعلاً mock data برمیگرداند
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
var mockLogs = new List<WorkerExecutionLogModel>
|
||||||
|
{
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorkerExecutionLogModel> 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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
|
||||||
|
|
||||||
|
public record GetWorkerStatusQuery : IRequest<GetWorkerStatusResponseDto>
|
||||||
|
{
|
||||||
|
// Empty - returns current worker status
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
|
||||||
|
|
||||||
|
public class GetWorkerStatusQueryHandler : IRequestHandler<GetWorkerStatusQuery, GetWorkerStatusResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetWorkerStatusQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetWorkerStatusResponseDto> 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics;
|
||||||
|
|
||||||
|
public class GetNetworkStatisticsQuery : IRequest<GetNetworkStatisticsResponseDto>
|
||||||
|
{
|
||||||
|
// No parameters - returns overall statistics
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics;
|
||||||
|
|
||||||
|
public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatisticsQuery, GetNetworkStatisticsResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetNetworkStatisticsQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetNetworkStatisticsResponseDto> 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<LevelDistributionModel>();
|
||||||
|
if (totalMembers > 0)
|
||||||
|
{
|
||||||
|
// Approximate distribution: Level 1 (10%), Level 2 (20%), Level 3 (30%), Level 4 (20%), Level 5+ (20%)
|
||||||
|
levelDistribution = new List<LevelDistributionModel>
|
||||||
|
{
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LevelDistributionModel> LevelDistribution { get; set; } = new();
|
||||||
|
public List<MonthlyGrowthModel> MonthlyGrowth { get; set; } = new();
|
||||||
|
public List<TopNetworkUserModel> 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; }
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ public class UserOrder : BaseAuditableEntity
|
|||||||
//Transaction Navigation Property
|
//Transaction Navigation Property
|
||||||
public virtual Transactions? Transaction { get; set; }
|
public virtual Transactions? Transaction { get; set; }
|
||||||
//وضعیت پرداخت
|
//وضعیت پرداخت
|
||||||
public PaymentStatus PaymentStatus { get; set; }
|
public PaymentStatus PaymentStatus { get; set; }
|
||||||
//تاریخ پرداخت
|
//تاریخ پرداخت
|
||||||
public DateTime? PaymentDate { get; set; }
|
public DateTime? PaymentDate { get; set; }
|
||||||
//شناسه کاربر
|
//شناسه کاربر
|
||||||
|
|||||||
Reference in New Issue
Block a user