namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWeeklyCommissionPool; public class GetWeeklyCommissionPoolQueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IWeekDefinitionRepository _weekDefinitionRepository; public GetWeeklyCommissionPoolQueryHandler( IApplicationDbContext context, IWeekDefinitionRepository weekDefinitionRepository) { _context = context; _weekDefinitionRepository = weekDefinitionRepository; } public async Task Handle(GetWeeklyCommissionPoolQuery request, CancellationToken cancellationToken) { var query = _context.WeeklyCommissionPools .Include(x => x.WeekDefinition) .Include(x => x.Package) .AsNoTracking() .Where(x => x.WeekDefinitionId == request.WeekDefinitionId); if (request.PackageId.HasValue) query = query.Where(x => x.PackageId == request.PackageId.Value); var pool = await query .Select(x => new WeeklyCommissionPoolDto { Id = x.Id, WeekDefinitionId = x.WeekDefinitionId, WeekDisplayName = x.WeekDefinition.DisplayName, TotalPoolAmount = x.TotalPoolAmount, TotalBalances = x.TotalBalances, ValuePerBalance = x.ValuePerBalance, IsCalculated = x.IsCalculated, CalculatedAt = x.CalculatedAt, Created = x.Created, PackageId = x.PackageId, PackageTitle = x.Package != null ? x.Package.Title : string.Empty }) .FirstOrDefaultAsync(cancellationToken); pool ??= new WeeklyCommissionPoolDto(); // ── Q29: اعتبارسنجی TotalPoolAmount در برابر فعال‌سازی‌های واقعی ── await ValidatePoolAmountAsync(pool, request, cancellationToken); return pool; } /// /// TotalPoolAmount استخر را با تعداد فعال‌سازی‌های ثبت‌شده (AcceptContract + ActivateClubMembership) مقایسه می‌کند. /// نتیجه در فیلدهای Validation... ذخیره می‌شود. /// private async Task ValidatePoolAmountAsync(WeeklyCommissionPoolDto pool, GetWeeklyCommissionPoolQuery request, CancellationToken cancellationToken) { var packageId = pool.PackageId > 0 ? pool.PackageId : request.PackageId; if (packageId is null or 0) return; var weekDef = _weekDefinitionRepository.GetWeekById(request.WeekDefinitionId); if (weekDef is null) return; var activationFee = await _context.Packages .AsNoTracking() .Where(p => p.Id == packageId.Value) .Select(p => p.ActivationFee) .FirstOrDefaultAsync(cancellationToken); if (activationFee <= 0) return; // شمارش Cycle‌هایی که Pool آن‌ها واقعاً شارژ شده: // - عضو جدید: Pool توسط AcceptContract شارژ می‌شود → UserContract وجود دارد // - خرید مجدد: Pool در ActivateClubMembership شارژ می‌شود → IsCurrentCycle نبوده (قبلاً active بوده) // - نباید از IsActive استفاده شود: وضعیت تاریخی نیست (عضو ممکن است بعداً غیرفعال شده باشد) // // قانون: Cycle شارژ شده = یا قرارداد دارد (عضو جدید) یا IsActive قبلاً true بوده (خرید مجدد) // ساده‌ترین معادل: Cycle هایی که ClubMembership.ActivatedAt در بازه هفته است (زمان واقعی شارژ Pool) var totalActivations = await _context.ClubMembershipCycles .AsNoTracking() .Where(c => c.PackageId == packageId.Value && c.PackagePurchasedAt >= weekDef.StartDate && c.PackagePurchasedAt < weekDef.EndDate && _context.UserContracts.Any(uc => uc.UserId == c.UserId && _context.Contracts.Any(con => con.Id == uc.ContractId && con.Type == ContractType.ClubMembership))) .CountAsync(cancellationToken); var expectedAmount = (long)totalActivations * activationFee; pool.ValidationActivationCount = totalActivations; pool.ValidationExpectedAmount = expectedAmount; pool.ValidationDiscrepancy = pool.TotalPoolAmount - expectedAmount; pool.ValidationIsValid = pool.TotalPoolAmount == expectedAmount; } }