feat: Enhance club membership activation and commission pool validation
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m9s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m9s
- Updated AcceptClubMembershipContractCommandHandler to set PackagePurchasedAt to the contract-signing time, ensuring accurate pool charging. - Modified ActivateClubMembershipCommand to allow specifying a PackageId for forced activation, defaulting to the base package if not provided. - Improved ActivateClubMembershipCommandHandler to handle new and existing memberships differently regarding pool charging. - Added validation logic in GetWeeklyCommissionPoolQueryHandler to compare TotalPoolAmount against actual activations. - Introduced validation fields in WeeklyCommissionPoolDto for tracking discrepancies and expected amounts. - Updated stored procedures to ensure accurate calculations based on package purchases and to handle empty pools. - Enhanced protobuf definitions to include validation fields for commission pools and network tree nodes. - Added mapping for new fields in CommissionProfile and NetworkMembershipProfile. - Created DiscountProductProfile for mapping discount product requests and responses.
This commit is contained in:
+9
-5
@@ -266,12 +266,16 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
|
||||
|
||||
var count = 0;
|
||||
|
||||
// اگر فرزند در این هفته پکیج خریده (Cycle جدید ساخته شده)، 1 امتیاز
|
||||
// ⚠️ از ClubMembershipCycle.PackagePurchasedAt استفاده میکنیم (نه ActivatedAt که overwrite میشد)
|
||||
var currentCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(x => x.UserId == child.Id && x.IsCurrentCycle, cancellationToken);
|
||||
// ⭐ بر اساس بازه تاریخی هفته، نه IsCurrentCycle
|
||||
// IsCurrentCycle فقط آخرین خرید را نشان میدهد — برای تاریخچهای (هفتههای گذشته)
|
||||
// باید از PackagePurchasedAt استفاده کرد که تاریخ دقیق خرید را نگه میدارد
|
||||
var hadCycleInWeek = await _context.ClubMembershipCycles
|
||||
.AnyAsync(x => x.UserId == child.Id
|
||||
&& x.PackagePurchasedAt >= startDate
|
||||
&& x.PackagePurchasedAt <= endDate,
|
||||
cancellationToken);
|
||||
|
||||
if (currentCycle?.PackagePurchasedAt >= startDate && currentCycle?.PackagePurchasedAt <= endDate)
|
||||
if (hadCycleInWeek)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
+56
-1
@@ -41,6 +41,61 @@ public class GetWeeklyCommissionPoolQueryHandler : IRequestHandler<GetWeeklyComm
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return pool ?? new WeeklyCommissionPoolDto();
|
||||
pool ??= new WeeklyCommissionPoolDto();
|
||||
|
||||
// ── Q29: اعتبارسنجی TotalPoolAmount در برابر فعالسازیهای واقعی ──
|
||||
await ValidatePoolAmountAsync(pool, request, cancellationToken);
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TotalPoolAmount استخر را با تعداد فعالسازیهای ثبتشده (AcceptContract + ActivateClubMembership) مقایسه میکند.
|
||||
/// نتیجه در فیلدهای Validation... ذخیره میشود.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -16,4 +16,14 @@ public class WeeklyCommissionPoolDto
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public long PackageId { get; set; }
|
||||
public string PackageTitle { get; set; } = string.Empty;
|
||||
|
||||
// ── Validation Fields (Q29) ──
|
||||
/// <summary>آیا TotalPoolAmount با تعداد فعالسازیهای ثبتشده در دیتابیس مطابقت دارد؟</summary>
|
||||
public bool ValidationIsValid { get; set; } = true;
|
||||
/// <summary>مبلغ مورد انتظار بر اساس شمارش فعالسازیها × ActivationFee</summary>
|
||||
public long ValidationExpectedAmount { get; set; }
|
||||
/// <summary>اختلاف: TotalPoolAmount - ValidationExpectedAmount</summary>
|
||||
public long ValidationDiscrepancy { get; set; }
|
||||
/// <summary>تعداد فعالسازیهای شناساییشده برای این هفته و پکیج</summary>
|
||||
public int ValidationActivationCount { get; set; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user