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:
+48
-1
@@ -221,7 +221,54 @@ public class AcceptClubMembershipContractCommandHandler
|
||||
_context.ClubMembershipHistories.Add(history);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 9. اضافه کردن مبلغ به Pool هفته جاری
|
||||
// 8.5. آپدیت PackagePurchasedAt سایکل جاری به زمان امضای قرارداد
|
||||
// دلیل: Pool در همین لحظه شارژ میشود — Validation باید به همین هفته اشاره کند
|
||||
// سناریوی cross-week: کاربر هفته ۱۸ پکیج خریده، هفته ۱۹ قرارداد امضا کرده
|
||||
// بدون این Fix: Cycle.PackagePurchasedAt = هفته ۱۸ ولی Pool هفته ۱۹ → اختلاف
|
||||
// با این Fix: Cycle.PackagePurchasedAt = هفته ۱۹ = Pool هفته ۱۹ → درست
|
||||
var currentCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, cancellationToken);
|
||||
|
||||
if (currentCycle != null)
|
||||
{
|
||||
currentCycle.PackagePurchasedAt = activationDate;
|
||||
_context.ClubMembershipCycles.Update(currentCycle);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Updated Cycle #{CycleNumber} PackagePurchasedAt to contract-signing time for UserId {UserId}",
|
||||
currentCycle.CycleNumber,
|
||||
userId
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// در صورتی که Cycle وجود ندارد (legacy یا مسیر مستقیم AcceptContract)
|
||||
// Cycle را همینجا بساز تا Validation درست کار کند
|
||||
_logger.LogWarning(
|
||||
"No IsCurrentCycle found for UserId {UserId} at AcceptContract — creating Cycle now",
|
||||
userId
|
||||
);
|
||||
|
||||
var maxCycleNumber = await _context.ClubMembershipCycles
|
||||
.Where(c => c.ClubMembershipId == clubMembership.Id)
|
||||
.MaxAsync(c => (int?)c.CycleNumber, cancellationToken) ?? 0;
|
||||
|
||||
var newCycle = new ClubMembershipCycle
|
||||
{
|
||||
UserId = userId,
|
||||
ClubMembershipId = clubMembership.Id,
|
||||
CycleNumber = maxCycleNumber + 1,
|
||||
PackagePurchasedAt = activationDate,
|
||||
PurchaseMethod = user.PackagePurchaseMethod,
|
||||
PackageAmount = package.Price,
|
||||
PackageId = package.Id,
|
||||
IsCurrentCycle = true
|
||||
};
|
||||
|
||||
_context.ClubMembershipCycles.Add(newCycle);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
|
||||
var weeklyPool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
|
||||
|
||||
+6
@@ -17,4 +17,10 @@ public record ActivateClubMembershipCommand : IRequest<bool>
|
||||
/// فعالسازی اجباری توسط ادمین — بدون بررسی سفارش و کیفپول
|
||||
/// </summary>
|
||||
public bool ForceActivation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پکیج — فقط در ForceActivation استفاده میشود.
|
||||
/// اگر 0 باشد، پکیج پایه (IsBasePackage) استفاده میشود.
|
||||
/// </summary>
|
||||
public long PackageId { get; init; }
|
||||
}
|
||||
|
||||
+61
-39
@@ -170,10 +170,19 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
// بارگذاری پکیج پایه برای فعالسازی اجباری
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException("پکیج پایه یافت نشد");
|
||||
// بارگذاری پکیج — اگر PackageId مشخص شده همان را لود کن، وگرنه پکیج پایه
|
||||
if (request.PackageId > 0)
|
||||
{
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.Id == request.PackageId && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException($"پکیج {request.PackageId} یافت نشد");
|
||||
}
|
||||
else
|
||||
{
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException("پکیج پایه یافت نشد");
|
||||
}
|
||||
}
|
||||
|
||||
// 6. بررسی عضویت فعلی
|
||||
@@ -226,10 +235,11 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
var hasCurrentCycle = await _context.ClubMembershipCycles
|
||||
.AnyAsync(c => c.UserId == user.Id && c.IsCurrentCycle, cancellationToken);
|
||||
|
||||
if (existingMembership.IsActive && hasCurrentCycle)
|
||||
// اگر چرخه فعال دارد — یعنی Activate قبلاً برای این خرید اجرا شده؛ جلوگیری از ثبت Cycle تکراری
|
||||
if (hasCurrentCycle)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"User {UserId} is already an active club member with a running cycle",
|
||||
"User {UserId} already has a running cycle — skipping duplicate activation",
|
||||
user.Id
|
||||
);
|
||||
return true;
|
||||
@@ -307,48 +317,60 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری
|
||||
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
|
||||
var weeklyPool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
|
||||
|
||||
if (weeklyPool == null)
|
||||
// عضو جدید (isNewMembership=true): Pool توسط AcceptClubMembershipContract شارژ میشود
|
||||
// چون کاربر قبل از امضای قرارداد IsActive=false است و قرارداد فقط یکبار امضا میشود.
|
||||
// خرید مجدد (isNewMembership=false): قرارداد مجدد امضا نمیشود — Pool همینجا شارژ میشود.
|
||||
if (!isNewMembership)
|
||||
{
|
||||
// ایجاد Pool جدید برای این هفته × این پکیج
|
||||
weeklyPool = new WeeklyCommissionPool
|
||||
{
|
||||
WeekDefinitionId = currentWeekDefinitionId,
|
||||
PackageId = package.Id,
|
||||
TotalPoolAmount = activationFeeValue,
|
||||
TotalBalances = 0, // در CalculateWeeklyBalances محاسبه میشه
|
||||
ValuePerBalance = 0, // در CalculateWeeklyCommissionPool محاسبه میشه
|
||||
IsCalculated = false,
|
||||
CalculatedAt = null
|
||||
};
|
||||
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
|
||||
var weeklyPool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
|
||||
|
||||
await _context.WeeklyCommissionPools.AddAsync(weeklyPool, cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created new WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId} with initial amount: {Amount}",
|
||||
currentWeekDefinitionId,
|
||||
giftValue
|
||||
);
|
||||
if (weeklyPool == null)
|
||||
{
|
||||
// ایجاد Pool جدید برای این هفته × این پکیج
|
||||
weeklyPool = new WeeklyCommissionPool
|
||||
{
|
||||
WeekDefinitionId = currentWeekDefinitionId,
|
||||
PackageId = package.Id,
|
||||
TotalPoolAmount = activationFeeValue,
|
||||
TotalBalances = 0,
|
||||
ValuePerBalance = 0,
|
||||
IsCalculated = false,
|
||||
CalculatedAt = null
|
||||
};
|
||||
|
||||
await _context.WeeklyCommissionPools.AddAsync(weeklyPool, cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created new WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId} with initial amount: {Amount}",
|
||||
currentWeekDefinitionId,
|
||||
activationFeeValue
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
weeklyPool.TotalPoolAmount += activationFeeValue;
|
||||
_context.WeeklyCommissionPools.Update(weeklyPool);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Added {Amount} to existing WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId}. New total: {NewTotal}",
|
||||
activationFeeValue,
|
||||
currentWeekDefinitionId,
|
||||
weeklyPool.TotalPoolAmount
|
||||
);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// اضافه کردن به Pool موجود
|
||||
weeklyPool.TotalPoolAmount += activationFeeValue;
|
||||
_context.WeeklyCommissionPools.Update(weeklyPool);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Added {Amount} to existing WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId}. New total: {NewTotal}",
|
||||
activationFeeValue,
|
||||
currentWeekDefinitionId,
|
||||
weeklyPool.TotalPoolAmount
|
||||
"New member — Pool charge deferred to AcceptClubMembershipContract for UserId {UserId}",
|
||||
user.Id
|
||||
);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 9. اعمال ویژگیهای پکیج — DIFF/تفاضل (Q20)
|
||||
await ApplyFeatureDiffAsync(user.Id, entity.Id, package.Id, isNewMembership, activationDate, cancellationToken);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user