feat: Enhance network membership and withdrawal processing with user tracking and logging
This commit is contained in:
@@ -6,10 +6,14 @@ namespace CMSMicroservice.Application.CommissionCQ.Commands.ApproveWithdrawal;
|
||||
public class ApproveWithdrawalCommandHandler : IRequestHandler<ApproveWithdrawalCommand, Unit>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
|
||||
public ApproveWithdrawalCommandHandler(IApplicationDbContext context)
|
||||
public ApproveWithdrawalCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
_context = context;
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(ApproveWithdrawalCommand request, CancellationToken cancellationToken)
|
||||
@@ -30,6 +34,8 @@ public class ApproveWithdrawalCommandHandler : IRequestHandler<ApproveWithdrawal
|
||||
// Update status to Withdrawn (approved)
|
||||
payout.Status = CommissionPayoutStatus.Withdrawn;
|
||||
payout.WithdrawnAt = DateTime.UtcNow;
|
||||
payout.ProcessedBy = _currentUser.GetPerformedBy();
|
||||
payout.ProcessedAt = DateTime.UtcNow;
|
||||
payout.LastModified = DateTime.UtcNow;
|
||||
|
||||
// TODO: Add PayoutHistory record
|
||||
|
||||
@@ -34,30 +34,95 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
|
||||
.Select(x => new { x.Id })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// دریافت باقیماندههای هفته قبل
|
||||
var previousWeekNumber = GetPreviousWeekNumber(request.WeekNumber);
|
||||
var previousWeekCarryovers = await _context.NetworkWeeklyBalances
|
||||
.Where(x => x.WeekNumber == previousWeekNumber)
|
||||
.Select(x => new
|
||||
{
|
||||
x.UserId,
|
||||
x.LeftLegRemainder,
|
||||
x.RightLegRemainder
|
||||
})
|
||||
.ToDictionaryAsync(x => x.UserId, cancellationToken);
|
||||
|
||||
var balancesList = new List<NetworkWeeklyBalance>();
|
||||
var calculatedAt = DateTime.UtcNow;
|
||||
|
||||
// خواندن یکباره Configuration ها (بهینهسازی - به جای N query)
|
||||
var configs = await _context.SystemConfigurations
|
||||
.Where(x => x.IsActive && (
|
||||
x.Key == "Club.ActivationFee" ||
|
||||
x.Key == "Commission.WeeklyPoolContributionPercent" ||
|
||||
x.Key == "Commission.MaxWeeklyBalancesPerUser"))
|
||||
.ToDictionaryAsync(x => x.Key, x => x.Value, cancellationToken);
|
||||
|
||||
var activationFee = long.Parse(configs.GetValueOrDefault("Club.ActivationFee", "25000000"));
|
||||
var poolPercent = decimal.Parse(configs.GetValueOrDefault("Commission.WeeklyPoolContributionPercent", "20")) / 100m;
|
||||
var maxWeeklyBalances = int.Parse(configs.GetValueOrDefault("Commission.MaxWeeklyBalancesPerUser", "300"));
|
||||
|
||||
foreach (var user in usersInNetwork)
|
||||
{
|
||||
// محاسبه تعادل پای چپ (Left Leg)
|
||||
var leftLegBalances = (int)await CalculateLegBalances(user.Id, NetworkLeg.Left, cancellationToken);
|
||||
// دریافت باقیمانده هفته قبل
|
||||
var leftCarryover = 0;
|
||||
var rightCarryover = 0;
|
||||
if (previousWeekCarryovers.ContainsKey(user.Id))
|
||||
{
|
||||
leftCarryover = previousWeekCarryovers[user.Id].LeftLegRemainder;
|
||||
rightCarryover = previousWeekCarryovers[user.Id].RightLegRemainder;
|
||||
}
|
||||
|
||||
// محاسبه تعادل پای راست (Right Leg)
|
||||
var rightLegBalances = (int)await CalculateLegBalances(user.Id, NetworkLeg.Right, cancellationToken);
|
||||
// محاسبه تعداد اعضای جدید در این هفته (فقط فرزندان مستقیم که در این هفته فعال شدند)
|
||||
var leftNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Left, request.WeekNumber, cancellationToken);
|
||||
var rightNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Right, request.WeekNumber, cancellationToken);
|
||||
|
||||
// محاسبه Total Balances (کمترین مقدار دو پا)
|
||||
var totalBalances = Math.Min(leftLegBalances, rightLegBalances);
|
||||
// محاسبه مجموع هر پا (جدید + باقیمانده)
|
||||
var leftTotal = leftNewMembers + leftCarryover;
|
||||
var rightTotal = rightNewMembers + rightCarryover;
|
||||
|
||||
// محاسبه سهم استخر (10% از Total Balances)
|
||||
var weeklyPoolContribution = (long)(totalBalances * 0.10m);
|
||||
// محاسبه تعادل (کمترین مقدار)
|
||||
var totalBalances = Math.Min(leftTotal, rightTotal);
|
||||
|
||||
// اعمال محدودیت سقف تعادل هفتگی (مثلاً 300)
|
||||
var cappedBalances = Math.Min(totalBalances, maxWeeklyBalances);
|
||||
|
||||
// محاسبه باقیمانده برای هفته بعد
|
||||
// اگر تعادل بیش از سقف بود، مازاد هم به remainder اضافه میشود
|
||||
var excessBalances = totalBalances - cappedBalances;
|
||||
var leftRemainder = (leftTotal - totalBalances) + (leftTotal >= rightTotal ? excessBalances : 0);
|
||||
var rightRemainder = (rightTotal - totalBalances) + (rightTotal >= leftTotal ? excessBalances : 0);
|
||||
|
||||
// محاسبه سهم استخر (20% از مجموع فعالسازیهای جدید کل شبکه)
|
||||
// طبق گفته دکتر: کل افراد جدید در شبکه × هزینه فعالسازی × 20%
|
||||
var totalNewMembers = leftNewMembers + rightNewMembers;
|
||||
var weeklyPoolContribution = (long)(totalNewMembers * activationFee * poolPercent);
|
||||
|
||||
var balance = new NetworkWeeklyBalance
|
||||
{
|
||||
UserId = user.Id,
|
||||
WeekNumber = request.WeekNumber,
|
||||
LeftLegBalances = leftLegBalances,
|
||||
RightLegBalances = rightLegBalances,
|
||||
TotalBalances = totalBalances,
|
||||
|
||||
// اطلاعات جدید
|
||||
LeftLegNewMembers = leftNewMembers,
|
||||
RightLegNewMembers = rightNewMembers,
|
||||
LeftLegCarryover = leftCarryover,
|
||||
RightLegCarryover = rightCarryover,
|
||||
|
||||
// مجموع
|
||||
LeftLegTotal = leftTotal,
|
||||
RightLegTotal = rightTotal,
|
||||
TotalBalances = cappedBalances, // تعادل واقعی بعد از اعمال سقف
|
||||
|
||||
// باقیمانده برای هفته بعد
|
||||
LeftLegRemainder = leftRemainder,
|
||||
RightLegRemainder = rightRemainder,
|
||||
|
||||
// فیلدهای قدیمی (deprecated) - برای سازگاری با کدهای قبلی
|
||||
#pragma warning disable CS0618
|
||||
LeftLegBalances = leftTotal,
|
||||
RightLegBalances = rightTotal,
|
||||
#pragma warning restore CS0618
|
||||
|
||||
WeeklyPoolContribution = weeklyPoolContribution,
|
||||
CalculatedAt = calculatedAt,
|
||||
IsExpired = false
|
||||
@@ -73,23 +138,89 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// محاسبه تعادل یک پا (Left یا Right) به صورت بازگشتی
|
||||
/// شماره هفته قبل را محاسبه میکند
|
||||
/// </summary>
|
||||
private async Task<long> CalculateLegBalances(long userId, NetworkLeg leg, CancellationToken cancellationToken)
|
||||
private string GetPreviousWeekNumber(string currentWeekNumber)
|
||||
{
|
||||
// پیدا کردن فرزند در پای مورد نظر
|
||||
// مثال: "2025-W48" -> "2025-W47"
|
||||
var parts = currentWeekNumber.Split('-');
|
||||
var year = int.Parse(parts[0]);
|
||||
var week = int.Parse(parts[1].Replace("W", ""));
|
||||
|
||||
week--;
|
||||
if (week < 1)
|
||||
{
|
||||
year--;
|
||||
week = 52; // یا 53 بسته به سال
|
||||
}
|
||||
|
||||
return $"{year}-W{week:D2}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// شمارش اعضای جدیدی که در این هفته به یک پا اضافه شدند
|
||||
/// فقط فرزندان مستقیم که ActivatedAt آنها در این هفته است
|
||||
/// </summary>
|
||||
private async Task<int> CountNewMembersInLeg(long userId, NetworkLeg leg, string weekNumber, CancellationToken cancellationToken)
|
||||
{
|
||||
// تبدیل WeekNumber به بازه تاریخی
|
||||
var (startDate, endDate) = GetWeekDateRange(weekNumber);
|
||||
|
||||
// شمارش تمام اعضای زیرمجموعه که در این هفته فعال شدند
|
||||
var count = await CountNewMembersRecursive(userId, leg, startDate, endDate, cancellationToken);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// شمارش بازگشتی اعضای جدید در یک پا
|
||||
/// </summary>
|
||||
private async Task<int> CountNewMembersRecursive(long userId, NetworkLeg leg, DateTime startDate, DateTime endDate, CancellationToken cancellationToken)
|
||||
{
|
||||
// پیدا کردن فرزند مستقیم در پای مورد نظر
|
||||
var child = await _context.Users
|
||||
.FirstOrDefaultAsync(x => x.NetworkParentId == userId && x.LegPosition == leg, cancellationToken);
|
||||
|
||||
if (child == null)
|
||||
{
|
||||
return 0; // اگر فرزندی نداشته باشد، تعادل صفر است
|
||||
return 0;
|
||||
}
|
||||
|
||||
// محاسبه بازگشتی: مجموع تعادل فرزند چپ + راست + 1 (خود فرزند)
|
||||
var childLeftLeg = await CalculateLegBalances(child.Id, NetworkLeg.Left, cancellationToken);
|
||||
var childRightLeg = await CalculateLegBalances(child.Id, NetworkLeg.Right, cancellationToken);
|
||||
var count = 0;
|
||||
|
||||
return 1 + childLeftLeg + childRightLeg;
|
||||
// اگر فرزند در این هفته فعال شده، 1 امتیاز
|
||||
var membership = await _context.ClubMemberships
|
||||
.FirstOrDefaultAsync(x => x.UserId == child.Id && x.IsActive, cancellationToken);
|
||||
|
||||
if (membership?.ActivatedAt >= startDate && membership?.ActivatedAt <= endDate)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
// جمع کردن اعضای جدید از پای چپ و راست فرزند
|
||||
var childLeft = await CountNewMembersRecursive(child.Id, NetworkLeg.Left, startDate, endDate, cancellationToken);
|
||||
var childRight = await CountNewMembersRecursive(child.Id, NetworkLeg.Right, startDate, endDate, cancellationToken);
|
||||
|
||||
return count + childLeft + childRight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// تبدیل شماره هفته به بازه تاریخی
|
||||
/// </summary>
|
||||
private (DateTime startDate, DateTime endDate) GetWeekDateRange(string weekNumber)
|
||||
{
|
||||
// مثال: "2025-W48"
|
||||
var parts = weekNumber.Split('-');
|
||||
var year = int.Parse(parts[0]);
|
||||
var week = int.Parse(parts[1].Replace("W", ""));
|
||||
|
||||
// محاسبه اولین روز هفته (شنبه)
|
||||
var jan1 = new DateTime(year, 1, 1);
|
||||
var daysOffset = DayOfWeek.Saturday - jan1.DayOfWeek;
|
||||
var firstSaturday = jan1.AddDays(daysOffset);
|
||||
var weekStart = firstSaturday.AddDays((week - 1) * 7);
|
||||
var weekEnd = weekStart.AddDays(6).AddHours(23).AddMinutes(59).AddSeconds(59);
|
||||
|
||||
return (weekStart, weekEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,14 @@ namespace CMSMicroservice.Application.CommissionCQ.Commands.ProcessWithdrawal;
|
||||
public class ProcessWithdrawalCommandHandler : IRequestHandler<ProcessWithdrawalCommand, Unit>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
|
||||
public ProcessWithdrawalCommandHandler(IApplicationDbContext context)
|
||||
public ProcessWithdrawalCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
_context = context;
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(ProcessWithdrawalCommand request, CancellationToken cancellationToken)
|
||||
|
||||
@@ -6,10 +6,14 @@ namespace CMSMicroservice.Application.CommissionCQ.Commands.RejectWithdrawal;
|
||||
public class RejectWithdrawalCommandHandler : IRequestHandler<RejectWithdrawalCommand, Unit>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
|
||||
public RejectWithdrawalCommandHandler(IApplicationDbContext context)
|
||||
public RejectWithdrawalCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
_context = context;
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(RejectWithdrawalCommand request, CancellationToken cancellationToken)
|
||||
@@ -29,6 +33,9 @@ public class RejectWithdrawalCommandHandler : IRequestHandler<RejectWithdrawalCo
|
||||
|
||||
// Update status to Cancelled (rejected)
|
||||
payout.Status = CommissionPayoutStatus.Cancelled;
|
||||
payout.ProcessedBy = _currentUser.GetPerformedBy();
|
||||
payout.ProcessedAt = DateTime.UtcNow;
|
||||
payout.RejectionReason = request.Reason;
|
||||
payout.LastModified = DateTime.UtcNow;
|
||||
|
||||
// TODO: Add PayoutHistory record with rejection reason
|
||||
|
||||
@@ -3,10 +3,14 @@ namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal;
|
||||
public class RequestWithdrawalCommandHandler : IRequestHandler<RequestWithdrawalCommand, Unit>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
|
||||
public RequestWithdrawalCommandHandler(IApplicationDbContext context)
|
||||
public RequestWithdrawalCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
_context = context;
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(RequestWithdrawalCommand request, CancellationToken cancellationToken)
|
||||
|
||||
Reference in New Issue
Block a user