Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dcd11351a3 | |||
| aaaf7fc1ca | |||
| 7554d7054a | |||
| ce8e248843 | |||
| 8446e0e3b5 | |||
| 161f796cd4 | |||
| 469d97bb60 | |||
| d19c569aae | |||
| 7176fe44ee | |||
| 607f791b65 | |||
| 0002a5a6f2 | |||
| ccb938e9ba | |||
| 8e5c7c5205 | |||
| a9cd2fd67a | |||
| ae92ab8697 | |||
| fe3edd178d | |||
| 13dd0f552f | |||
| 8b9c317de6 |
+25
-6
@@ -135,13 +135,25 @@ public class AcceptClubMembershipContractCommandHandler
|
||||
};
|
||||
await _context.UserContracts.AddAsync(userContract, cancellationToken);
|
||||
|
||||
// 6. دریافت مقادیر از SystemConstants (استاتیک)
|
||||
long giftValue = SystemConstants.ClubMembershipGiftValue;
|
||||
long activationFeeValue = SystemConstants.ClubActivationFee;
|
||||
// 6. بارگذاری پکیج از آخرین خرید کاربر یا پکیج پایه
|
||||
var latestPurchase = await _context.UserPackagePurchases
|
||||
.Where(p => p.UserId == userId)
|
||||
.OrderByDescending(p => p.PurchasedAt)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
var package = latestPurchase != null
|
||||
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == latestPurchase.PackageId && !p.IsDeleted, cancellationToken)
|
||||
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken);
|
||||
|
||||
if (package == null)
|
||||
throw new NotFoundException("پکیج یافت نشد");
|
||||
|
||||
long giftValue = package.ActivationFee;
|
||||
long activationFeeValue = package.ActivationFee;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Using Club.MembershipGiftValue: {GiftValue}, Club.ActivationFee: {ActivationFee}",
|
||||
giftValue, activationFeeValue
|
||||
"Using Package {PackageId} ({PackageName}): GiftValue={GiftValue}, ActivationFee={ActivationFee}",
|
||||
package.Id, package.Title, giftValue, activationFeeValue
|
||||
);
|
||||
|
||||
// 7. فعالسازی باشگاه مشتریان
|
||||
@@ -156,6 +168,10 @@ public class AcceptClubMembershipContractCommandHandler
|
||||
UserId = user.Id,
|
||||
IsActive = true,
|
||||
ActivatedAt = activationDate,
|
||||
FirstActivationDate = activationDate,
|
||||
FirstPackageId = package.Id,
|
||||
LastActivationDate = activationDate,
|
||||
LastPackageId = package.Id,
|
||||
InitialContribution = activationFeeValue,
|
||||
GiftValue = giftValue,
|
||||
TotalEarned = 0,
|
||||
@@ -175,6 +191,8 @@ public class AcceptClubMembershipContractCommandHandler
|
||||
clubMembership = user.ClubMembership!;
|
||||
clubMembership.IsActive = true;
|
||||
clubMembership.ActivatedAt = activationDate;
|
||||
clubMembership.LastActivationDate = activationDate;
|
||||
clubMembership.LastPackageId = package.Id;
|
||||
clubMembership.PurchaseMethod = user.PackagePurchaseMethod;
|
||||
_context.ClubMemberships.Update(clubMembership);
|
||||
|
||||
@@ -206,13 +224,14 @@ public class AcceptClubMembershipContractCommandHandler
|
||||
// 9. اضافه کردن مبلغ به Pool هفته جاری
|
||||
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
|
||||
var weeklyPool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId, cancellationToken);
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
|
||||
|
||||
if (weeklyPool == null)
|
||||
{
|
||||
weeklyPool = new WeeklyCommissionPool
|
||||
{
|
||||
WeekDefinitionId = currentWeekDefinitionId,
|
||||
PackageId = package.Id,
|
||||
TotalPoolAmount = activationFeeValue,
|
||||
TotalBalances = 0,
|
||||
ValuePerBalance = 0,
|
||||
|
||||
+46
-26
@@ -54,6 +54,9 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
throw new NotFoundException(nameof(User), request.UserId);
|
||||
}
|
||||
|
||||
// متغیر پکیج — در هر دو مسیر (عادی و Force) مقداردهی میشود
|
||||
Package package;
|
||||
|
||||
// 2-5: بررسیهای مالی — در حالت ForceActivation (ادمین) رد میشود
|
||||
if (!request.ForceActivation)
|
||||
{
|
||||
@@ -61,11 +64,11 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} has not purchased golden package yet",
|
||||
"User {UserId} has not purchased any package yet",
|
||||
request.UserId
|
||||
);
|
||||
throw new BadRequestException(
|
||||
"برای فعالسازی باشگاه مشتریان ابتدا باید پکیج طلایی خریداری کنید"
|
||||
"برای فعالسازی باشگاه مشتریان ابتدا باید یک پکیج خریداری کنید"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,17 +82,8 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
throw new NotFoundException("کیف پول کاربر یافت نشد");
|
||||
}
|
||||
|
||||
if (wallet.Balance < SystemConstants.BasePackageAmount)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} has insufficient balance: {Balance}",
|
||||
request.UserId,
|
||||
wallet.Balance
|
||||
);
|
||||
throw new BadRequestException(
|
||||
$"برای فعالسازی باشگاه مشتریان باید حداقل {SystemConstants.BasePackageAmount:N0} ریال موجودی اصلی داشته باشید"
|
||||
);
|
||||
}
|
||||
// NOTE: balance check uses package.Price — loaded after finding order (step 4)
|
||||
// Moved to after package loading
|
||||
|
||||
// 3.5. بررسی وضعیت کیفپول جادویی — اگر در حالت Magic است، فعالسازی مجاز نیست
|
||||
if (wallet.WalletMode == WalletMode.Magic)
|
||||
@@ -119,7 +113,7 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
"No successful package order found for UserId: {UserId}",
|
||||
request.UserId
|
||||
);
|
||||
throw new NotFoundException("سفارش پکیج طلایی یافت نشد");
|
||||
throw new NotFoundException("سفارش پکیج یافت نشد");
|
||||
}
|
||||
|
||||
// 5. بررسی Transaction
|
||||
@@ -146,6 +140,19 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
"تراکنش معتبر برای فعالسازی باشگاه یافت نشد"
|
||||
);
|
||||
}
|
||||
|
||||
// 5.5. بارگذاری پکیج از سفارش
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.Id == packageOrder.PackageId && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException("پکیج یافت نشد");
|
||||
|
||||
// بررسی موجودی با مبلغ پکیج واقعی
|
||||
if (wallet.Balance < package.Price)
|
||||
{
|
||||
throw new BadRequestException(
|
||||
$"برای فعالسازی باشگاه مشتریان باید حداقل {package.Price:N0} ریال موجودی اصلی داشته باشید"
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -161,19 +168,24 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
_context.Users.Update(user);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
// بارگذاری پکیج پایه برای فعالسازی اجباری
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException("پکیج پایه یافت نشد");
|
||||
}
|
||||
|
||||
// 6. بررسی عضویت فعلی
|
||||
var existingMembership = await _context.ClubMemberships
|
||||
.FirstOrDefaultAsync(c => c.UserId == user.Id, cancellationToken);
|
||||
|
||||
// 6.1. دریافت مبلغ هدیه و هزینه فعالسازی از SystemConstants
|
||||
long giftValue = SystemConstants.ClubMembershipGiftValue;
|
||||
long activationFeeValue = SystemConstants.ClubActivationFee;
|
||||
// 6.1. دریافت مبلغ هدیه و هزینه فعالسازی از پکیج
|
||||
long giftValue = package.ActivationFee;
|
||||
long activationFeeValue = package.ActivationFee;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Using Club.MembershipGiftValue: {GiftValue}, Club.ActivationFee: {ActivationFee}",
|
||||
giftValue, activationFeeValue
|
||||
"Using Package {PackageId} ({PackageName}): GiftValue={GiftValue}, ActivationFee={ActivationFee}, Price={Price}",
|
||||
package.Id, package.Title, giftValue, activationFeeValue, package.Price
|
||||
);
|
||||
|
||||
ClubMembership entity;
|
||||
@@ -188,8 +200,12 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
UserId = user.Id,
|
||||
IsActive = true,
|
||||
ActivatedAt = activationDate,
|
||||
InitialContribution =activationFeeValue,
|
||||
GiftValue = giftValue, // مقدار از تنظیمات
|
||||
FirstActivationDate = activationDate,
|
||||
FirstPackageId = package.Id,
|
||||
LastActivationDate = activationDate,
|
||||
LastPackageId = package.Id,
|
||||
InitialContribution = activationFeeValue,
|
||||
GiftValue = giftValue,
|
||||
TotalEarned = 0,
|
||||
PurchaseMethod = user.PackagePurchaseMethod
|
||||
};
|
||||
@@ -214,9 +230,11 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
return true;
|
||||
}
|
||||
|
||||
// فعالسازی مجدد — ActivatedAt حفظ میشه (overwrite نمیشه)
|
||||
// فعالسازی مجدد — FirstActivation حفظ میشه (Q21), Last بروزرسانی میشه
|
||||
entity = existingMembership;
|
||||
entity.IsActive = true;
|
||||
entity.LastActivationDate = activationDate;
|
||||
entity.LastPackageId = package.Id;
|
||||
entity.PurchaseMethod = user.PackagePurchaseMethod;
|
||||
|
||||
_context.ClubMemberships.Update(entity);
|
||||
@@ -250,7 +268,8 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
CycleNumber = maxCycleNumber + 1,
|
||||
PackagePurchasedAt = activationDate,
|
||||
PurchaseMethod = user.PackagePurchaseMethod,
|
||||
PackageAmount = SystemConstants.BasePackageAmount,
|
||||
PackageAmount = package.Price,
|
||||
PackageId = package.Id,
|
||||
IsCurrentCycle = true
|
||||
};
|
||||
|
||||
@@ -284,15 +303,16 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
// ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری
|
||||
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
|
||||
var weeklyPool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId, cancellationToken);
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
|
||||
|
||||
if (weeklyPool == null)
|
||||
{
|
||||
// ایجاد Pool جدید برای این هفته
|
||||
// ایجاد Pool جدید برای این هفته × این پکیج
|
||||
weeklyPool = new WeeklyCommissionPool
|
||||
{
|
||||
WeekDefinitionId = currentWeekDefinitionId,
|
||||
TotalPoolAmount = activationFeeValue, // مبلغ هدیه به Pool اضافه میشه
|
||||
PackageId = package.Id,
|
||||
TotalPoolAmount = activationFeeValue,
|
||||
TotalBalances = 0, // در CalculateWeeklyBalances محاسبه میشه
|
||||
ValuePerBalance = 0, // در CalculateWeeklyCommissionPool محاسبه میشه
|
||||
IsCalculated = false,
|
||||
|
||||
+9
-5
@@ -82,11 +82,14 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
|
||||
var balancesList = new List<NetworkWeeklyBalance>();
|
||||
var calculatedAt = DateTime.Now;
|
||||
|
||||
// استفاده از SystemConstants (استاتیک - بدون کوئری به دیتابیس)
|
||||
// سقف تعادل هفتگی برای هر دست (نه کل) - 300 برای چپ + 300 برای راست = حداکثر 600 تعادل
|
||||
var maxBalancesPerLeg = SystemConstants.CommissionMaxWeeklyBalancesPerLeg;
|
||||
// حداکثر عمق شبکه برای شمارش اعضا (15 لول)
|
||||
var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel;
|
||||
// بارگذاری پکیج پایه برای خواندن سقفها
|
||||
var package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new InvalidOperationException("پکیج پایه یافت نشد");
|
||||
|
||||
// خواندن سقفها از پکیج (قبلاً از SystemConstants بود)
|
||||
var maxBalancesPerLeg = package.MaxBalancesPerLeg;
|
||||
var maxNetworkLevel = package.MaxNetworkLevel;
|
||||
|
||||
foreach (var user in usersInNetwork.OrderBy(o=>o.Id))
|
||||
{
|
||||
@@ -140,6 +143,7 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
|
||||
{
|
||||
UserId = user.Id,
|
||||
WeekDefinitionId = request.WeekDefinitionId,
|
||||
PackageId = package.Id,
|
||||
|
||||
// اطلاعات جدید
|
||||
LeftLegNewMembers = leftNewMembers,
|
||||
|
||||
+6
-2
@@ -49,8 +49,11 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayou
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
// ⭐ خواندن MaxNetworkLevel از SystemConstants (استاتیک)
|
||||
var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel;
|
||||
// بارگذاری پکیج پایه برای خواندن سقفها
|
||||
var package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new InvalidOperationException("پکیج پایه یافت نشد");
|
||||
var maxNetworkLevel = package.MaxNetworkLevel;
|
||||
|
||||
// دریافت همه تعادلهای هفتگی (شامل صفرها هم برای محاسبه زیرمجموعه)
|
||||
var allWeeklyBalances = await _context.NetworkWeeklyBalances
|
||||
@@ -113,6 +116,7 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayou
|
||||
UserId = userId,
|
||||
WeekDefinitionId = request.WeekDefinitionId,
|
||||
WeeklyPoolId = pool.Id,
|
||||
PackageId = package.Id,
|
||||
BalancesEarned = totalBalancesWithSubordinates, // ⭐ شامل زیرمجموعه
|
||||
ValuePerBalance = pool.ValuePerBalance,
|
||||
TotalAmount = totalAmount,
|
||||
|
||||
+5
@@ -18,6 +18,11 @@ public record GetMyCommissionPayoutsQuery : IRequest<GetMyCommissionPayoutsRespo
|
||||
/// </summary>
|
||||
public long? WeekDefinitionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس پکیج (اختیاری)
|
||||
/// </summary>
|
||||
public long? PackageId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Pagination
|
||||
/// </summary>
|
||||
|
||||
+10
-1
@@ -28,6 +28,7 @@ public class GetMyCommissionPayoutsQueryHandler : IRequestHandler<GetMyCommissio
|
||||
|
||||
var query = _context.UserCommissionPayouts
|
||||
.Include(x => x.WeekDefinition)
|
||||
.Include(x => x.Package)
|
||||
.Where(x => x.UserId == userId)
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
@@ -43,6 +44,12 @@ public class GetMyCommissionPayoutsQueryHandler : IRequestHandler<GetMyCommissio
|
||||
query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId.Value);
|
||||
}
|
||||
|
||||
// فیلتر بر اساس پکیج
|
||||
if (request.PackageId.HasValue && request.PackageId.Value > 0)
|
||||
{
|
||||
query = query.Where(x => x.PackageId == request.PackageId.Value);
|
||||
}
|
||||
|
||||
// مرتبسازی: جدیدترین اول
|
||||
query = query.OrderByDescending(x => x.Created);
|
||||
|
||||
@@ -60,7 +67,9 @@ public class GetMyCommissionPayoutsQueryHandler : IRequestHandler<GetMyCommissio
|
||||
AmountFormatted = x.TotalAmount.ToString("N0") + " تومان",
|
||||
Status = x.Status,
|
||||
CalculatedDate = x.PaidAt ?? (DateTime?)x.Created,
|
||||
DatePersian = ""
|
||||
DatePersian = "",
|
||||
PackageId = x.PackageId,
|
||||
PackageTitle = x.Package != null ? x.Package.Title : ""
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
|
||||
+4
@@ -20,4 +20,8 @@ public class GetMyCommissionPayoutsResponseModel
|
||||
public CommissionPayoutStatus Status { get; set; }
|
||||
public DateTime? CalculatedDate { get; set; }
|
||||
public string DatePersian { get; set; } = string.Empty;
|
||||
|
||||
// Package info
|
||||
public long PackageId { get; set; }
|
||||
public string PackageTitle { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
+5
@@ -17,6 +17,11 @@ public record GetMyWeeklyBalancesQuery : IRequest<GetUserWeeklyBalancesResponseD
|
||||
/// </summary>
|
||||
public bool OnlyActive { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس پکیج (اختیاری)
|
||||
/// </summary>
|
||||
public long? PackageId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Pagination
|
||||
/// </summary>
|
||||
|
||||
+1
@@ -38,6 +38,7 @@ public class GetMyWeeklyBalancesQueryHandler : IRequestHandler<GetMyWeeklyBalanc
|
||||
{
|
||||
UserId = userId,
|
||||
WeekDefinitionId = request.WeekDefinitionId,
|
||||
PackageId = request.PackageId,
|
||||
OnlyActive = request.OnlyActive,
|
||||
PaginationState = request.PaginationState
|
||||
};
|
||||
|
||||
+5
@@ -20,6 +20,11 @@ public record GetUserCommissionPayoutsQuery : IRequest<GetUserCommissionPayoutsR
|
||||
/// </summary>
|
||||
public long? WeekDefinitionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس پکیج (اختیاری)
|
||||
/// </summary>
|
||||
public long? PackageId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// مرتبسازی
|
||||
/// </summary>
|
||||
|
||||
+10
-1
@@ -18,6 +18,7 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommi
|
||||
var query = _context.UserCommissionPayouts
|
||||
.Include(x => x.WeekDefinition)
|
||||
.Include(x => x.User)
|
||||
.Include(x => x.Package)
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
|
||||
@@ -41,6 +42,12 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommi
|
||||
query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
|
||||
}
|
||||
|
||||
// فیلتر بر اساس پکیج
|
||||
if (request.PackageId.HasValue && request.PackageId.Value > 0)
|
||||
{
|
||||
query = query.Where(x => x.PackageId == request.PackageId.Value);
|
||||
}
|
||||
|
||||
query = query.ApplyOrder(sortBy: request.SortBy ?? "Created");
|
||||
|
||||
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
|
||||
@@ -64,7 +71,9 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommi
|
||||
WithdrawalMethod = x.WithdrawalMethod,
|
||||
IbanNumber = x.IbanNumber,
|
||||
WithdrawnAt = x.WithdrawnAt,
|
||||
Created = x.Created
|
||||
Created = x.Created,
|
||||
PackageId = x.PackageId,
|
||||
PackageTitle = x.Package != null ? x.Package.Title : ""
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
|
||||
+4
@@ -24,4 +24,8 @@ public class GetUserCommissionPayoutsResponseModel
|
||||
public string? IbanNumber { get; set; }
|
||||
public DateTime? WithdrawnAt { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
// Package info
|
||||
public long PackageId { get; set; }
|
||||
public string PackageTitle { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
+4
@@ -15,6 +15,10 @@ public record GetUserWeeklyBalancesQuery : IRequest<GetUserWeeklyBalancesRespons
|
||||
/// </summary>
|
||||
public long? WeekDefinitionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس پکیج (اختیاری)
|
||||
/// </summary>
|
||||
public long? PackageId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// فقط موارد Expired نشده؟
|
||||
|
||||
+10
-1
@@ -18,6 +18,7 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
|
||||
var query = _context.NetworkWeeklyBalances
|
||||
.Include(x => x.WeekDefinition)
|
||||
.Include(x => x.User)
|
||||
.Include(x => x.Package)
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
|
||||
@@ -43,6 +44,12 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
|
||||
query = query.Where(x => !x.IsExpired);
|
||||
}
|
||||
|
||||
// فیلتر بر اساس پکیج
|
||||
if (request.PackageId.HasValue && request.PackageId.Value > 0)
|
||||
{
|
||||
query = query.Where(x => x.PackageId == request.PackageId.Value);
|
||||
}
|
||||
|
||||
// مرتبسازی بر اساس WeekDefinitionId (نزولی = جدیدترین اول)
|
||||
query = query.ApplyOrder(sortBy: request.SortBy ?? "-WeekDefinitionId");
|
||||
|
||||
@@ -67,7 +74,9 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
|
||||
WeeklyPoolContribution = x.WeeklyPoolContribution,
|
||||
CalculatedAt = x.CalculatedAt,
|
||||
IsExpired = x.IsExpired,
|
||||
Created = x.Created
|
||||
Created = x.Created,
|
||||
PackageId = x.PackageId,
|
||||
PackageTitle = x.Package != null ? x.Package.Title : ""
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
|
||||
+4
@@ -29,4 +29,8 @@ public class GetUserWeeklyBalancesResponseModel
|
||||
public DateTime? CalculatedAt { get; set; }
|
||||
public bool IsExpired { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
// Package info
|
||||
public long PackageId { get; set; }
|
||||
public string PackageTitle { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public interface IApplicationDbContext
|
||||
DbSet<ClubMembership> ClubMemberships { get; }
|
||||
DbSet<ClubMembershipHistory> ClubMembershipHistories { get; }
|
||||
DbSet<ClubMembershipCycle> ClubMembershipCycles { get; }
|
||||
DbSet<PackageFeature> PackageFeatures { get; }
|
||||
DbSet<ClubFeature> ClubFeatures { get; }
|
||||
DbSet<UserClubFeature> UserClubFeatures { get; }
|
||||
DbSet<NetworkWeeklyBalance> NetworkWeeklyBalances { get; }
|
||||
|
||||
+21
-25
@@ -12,7 +12,7 @@ namespace CMSMicroservice.Application.DayaLoanCQ.Commands.CheckAndProcessDayaLoa
|
||||
/// 1. استعلام از API دایا
|
||||
/// 2. ذخیره/بهروزرسانی DayaLoanContract
|
||||
/// 3. شارژ کیف پول برای وامهای تأیید شده
|
||||
/// 4. ثبت Order پکیج طلایی
|
||||
/// 4. ثبت Order پکیج
|
||||
/// 5. ارسال SMS
|
||||
/// </summary>
|
||||
public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndProcessDayaLoansCommand, CheckAndProcessDayaLoansResponseDto>
|
||||
@@ -186,10 +186,15 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
|
||||
string contractNumber,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// بارگذاری پکیج پایه
|
||||
var package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new InvalidOperationException("پکیج پایه یافت نشد");
|
||||
|
||||
// 1. ایجاد تراکنش
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Amount = SystemConstants.DayaLoanAmount,
|
||||
Amount = package.Price,
|
||||
Description = $"دریافت اعتبار دایا - قرارداد {contractNumber}",
|
||||
PaymentStatus = PaymentStatus.Success,
|
||||
PaymentDate = DateTime.Now,
|
||||
@@ -216,13 +221,13 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
|
||||
}
|
||||
|
||||
// 3. شارژ کیف پول عادی
|
||||
wallet.Balance += SystemConstants.DayaLoanAmount;
|
||||
wallet.Balance += package.Price;
|
||||
|
||||
var mainLog = new UserWalletChangeLog
|
||||
{
|
||||
WalletId = wallet.Id,
|
||||
CurrentBalance = 0,
|
||||
ChangeValue = SystemConstants.DayaLoanAmount,
|
||||
ChangeValue = package.Price,
|
||||
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||
ChangeNerworkValue = 0,
|
||||
IsIncrease = true,
|
||||
@@ -230,8 +235,8 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
|
||||
};
|
||||
await _context.UserWalletChangeLogs.AddAsync(mainLog, cancellationToken);
|
||||
|
||||
// 4. شارژ کیف پول تخفیف (دو برابر)
|
||||
var discountAmount = SystemConstants.DayaLoanAmount * 2;
|
||||
// 4. شارژ کیف پول تخفیف
|
||||
var discountAmount = (long)(package.Price * package.DiscountMultiplier);
|
||||
wallet.DiscountBalance += discountAmount;
|
||||
|
||||
var discountLog = new UserWalletChangeLog
|
||||
@@ -253,27 +258,18 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
|
||||
user.DayaCreditReceivedAt = DateTime.Now;
|
||||
user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan;
|
||||
|
||||
// 6. ثبت Order پکیج پایه
|
||||
var goldenPackage = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.Id == 4, cancellationToken);
|
||||
|
||||
if (goldenPackage != null)
|
||||
// 6. ثبت UserPackagePurchase برای پکیج پایه
|
||||
var packagePurchase = new UserPackagePurchase
|
||||
{
|
||||
// 6. ثبت UserPackagePurchase برای پکیج پایه
|
||||
var goldenPackageId =goldenPackage.Id;
|
||||
var packagePurchase = new UserPackagePurchase
|
||||
{
|
||||
UserId = user.Id,
|
||||
PackageId = goldenPackageId,
|
||||
PurchaseMethod = PackagePurchaseMethod.DayaLoan,
|
||||
PurchasedAt = DateTime.Now,
|
||||
Amount = SystemConstants.DayaLoanAmount,
|
||||
TransactionId = transaction.Id
|
||||
};
|
||||
UserId = user.Id,
|
||||
PackageId = package.Id,
|
||||
PurchaseMethod = PackagePurchaseMethod.DayaLoan,
|
||||
PurchasedAt = DateTime.Now,
|
||||
Amount = package.Price,
|
||||
TransactionId = transaction.Id
|
||||
};
|
||||
|
||||
await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken);
|
||||
|
||||
}
|
||||
await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken);
|
||||
|
||||
// 7. Domain Event
|
||||
user.AddDomainEvent(new DayaLoanApprovedEvent(user, transaction, contractNumber));
|
||||
|
||||
+9
-5
@@ -70,9 +70,13 @@ public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPay
|
||||
throw new NotFoundException($"کیف پول کاربر {request.UserId} یافت نشد");
|
||||
}
|
||||
|
||||
// 4. محاسبه مبالغ
|
||||
var balanceAmount = SystemConstants.BasePackageAmount; // 56M
|
||||
var discountBalanceAmount = SystemConstants.BasePackageAmount * 2; // 112M
|
||||
// 4. بارگذاری پکیج پایه و محاسبه مبالغ
|
||||
var package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException("پکیج پایه یافت نشد");
|
||||
|
||||
var balanceAmount = package.Price;
|
||||
var discountBalanceAmount = (long)(package.Price * package.DiscountMultiplier);
|
||||
|
||||
|
||||
// 5. ثبت تراکنش
|
||||
@@ -112,8 +116,8 @@ public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPay
|
||||
var oldBalance = wallet.Balance;
|
||||
var oldDiscountBalance = wallet.DiscountBalance;
|
||||
|
||||
wallet.Balance += balanceAmount; // +56M
|
||||
wallet.DiscountBalance += discountBalanceAmount; // +112M
|
||||
wallet.Balance += balanceAmount;
|
||||
wallet.DiscountBalance += discountBalanceAmount;
|
||||
|
||||
// 8. ثبت لاگ کیف پول
|
||||
var walletLog = new UserWalletChangeLog
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ public class GetUserNetworkPositionQueryHandler : IRequestHandler<GetUserNetwork
|
||||
HasReceivedDayaCredit = user.HasReceivedDayaCredit,
|
||||
DayaCreditReceivedAt = user.DayaCreditReceivedAt,
|
||||
PackagePurchaseMethod = user.PackagePurchaseMethod,
|
||||
HasPurchasedGoldenPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None,
|
||||
HasPurchasedPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None,
|
||||
|
||||
// آمار مالی
|
||||
TotalEarnedCommission = commissionStats?.TotalAmount ?? 0,
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public class UserNetworkPositionDto
|
||||
public bool HasReceivedDayaCredit { get; set; }
|
||||
public DateTime? DayaCreditReceivedAt { get; set; }
|
||||
public PackagePurchaseMethod PackagePurchaseMethod { get; set; }
|
||||
public bool HasPurchasedGoldenPackage { get; set; }
|
||||
public bool HasPurchasedPackage { get; set; }
|
||||
|
||||
// آمار مالی
|
||||
public decimal TotalEarnedCommission { get; set; }
|
||||
|
||||
+14
-1
@@ -9,5 +9,18 @@ public record CreateNewPackageCommand : IRequest<CreateNewPackageResponseDto>
|
||||
public string ImagePath { get; init; }
|
||||
//قیمت
|
||||
public long Price { get; init; }
|
||||
|
||||
// فیلدهای جدید پکیج
|
||||
public int SortOrder { get; init; }
|
||||
public bool IsActive { get; init; } = true;
|
||||
public bool IsBasePackage { get; init; }
|
||||
public bool SupportsDayaPurchase { get; init; } = true;
|
||||
public bool SupportsDirectPurchase { get; init; } = true;
|
||||
public long ActivationFee { get; init; }
|
||||
public double DiscountMultiplier { get; init; } = 2.0;
|
||||
public double MagicWalletMultiplier { get; init; } = 2.5;
|
||||
public int MaxBalancesPerLeg { get; init; } = 300;
|
||||
public int MaxNetworkLevel { get; init; } = 15;
|
||||
public long MagicWalletMaxDeposit { get; init; } = 1_000_000_000;
|
||||
public long MagicWalletMaxCredit { get; init; } = 2_500_000_000;
|
||||
public List<long> FeatureIds { get; init; } = new();
|
||||
}
|
||||
+16
@@ -16,6 +16,22 @@ public class CreateNewPackageCommandHandler : IRequestHandler<CreateNewPackageCo
|
||||
await _context.Packages.AddAsync(entity, cancellationToken);
|
||||
entity.AddDomainEvent(new CreateNewPackageEvent(entity));
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Sync PackageFeatures
|
||||
if (request.FeatureIds.Any())
|
||||
{
|
||||
foreach (var featureId in request.FeatureIds)
|
||||
{
|
||||
_context.PackageFeatures.Add(new PackageFeature
|
||||
{
|
||||
PackageId = entity.Id,
|
||||
ClubFeatureId = featureId,
|
||||
IsIncluded = true
|
||||
});
|
||||
}
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return entity.Adapt<CreateNewPackageResponseDto>();
|
||||
}
|
||||
}
|
||||
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment;
|
||||
|
||||
/// <summary>
|
||||
/// ثبت اولیه تراکنش و سفارش پکیج پایه (56 میلیون تومان)
|
||||
/// این Command توسط BFF فراخوانی میشود قبل از ارسال کاربر به درگاه
|
||||
/// </summary>
|
||||
public record InitiateBasePackagePaymentCommand : IRequest<InitiateBasePackagePaymentResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه کاربر
|
||||
/// </summary>
|
||||
public long UserId { get; init; }
|
||||
}
|
||||
|
||||
public class InitiateBasePackagePaymentResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// موفقیت عملیات
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// پیام
|
||||
/// </summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// شناسه سفارش
|
||||
/// </summary>
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه تراکنش CMS
|
||||
/// </summary>
|
||||
public long TransactionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// مبلغ پکیج
|
||||
/// </summary>
|
||||
public long Amount { get; set; }
|
||||
}
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ValidationException = FluentValidation.ValidationException;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment;
|
||||
|
||||
/// <summary>
|
||||
/// Handler برای ثبت اولیه تراکنش و سفارش پکیج پایه
|
||||
/// این Handler فقط Transaction و Order را با وضعیت Pending ایجاد میکند
|
||||
/// BFF سپس کاربر را به درگاه PYMS هدایت میکند
|
||||
/// </summary>
|
||||
public class InitiateBasePackagePaymentCommandHandler : IRequestHandler<InitiateBasePackagePaymentCommand, InitiateBasePackagePaymentResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ILogger<InitiateBasePackagePaymentCommandHandler> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پکیج پایه در دیتابیس
|
||||
/// </summary>
|
||||
private const long BasePackageId = 4;
|
||||
|
||||
public InitiateBasePackagePaymentCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
ILogger<InitiateBasePackagePaymentCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<InitiateBasePackagePaymentResponseDto> Handle(
|
||||
InitiateBasePackagePaymentCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Initiating base package payment for UserId: {UserId}",
|
||||
request.UserId);
|
||||
|
||||
// 1. پیدا کردن کاربر
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found. UserId: {UserId}", request.UserId);
|
||||
throw new NotFoundException(nameof(User), request.UserId);
|
||||
}
|
||||
|
||||
// 2. بررسی عدم خرید قبلی پکیج پایه
|
||||
if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} has already purchased base package via {Method}",
|
||||
request.UserId,
|
||||
user.PackagePurchaseMethod);
|
||||
|
||||
return new InitiateBasePackagePaymentResponseDto
|
||||
{
|
||||
Success = false,
|
||||
Message = "شما قبلاً پکیج پایه را خریداری کردهاید."
|
||||
};
|
||||
}
|
||||
|
||||
// 3. بررسی عدم وجود سفارش Pending
|
||||
var pendingOrder = await _context.UserOrders
|
||||
.FirstOrDefaultAsync(o =>
|
||||
o.UserId == request.UserId &&
|
||||
o.PaymentStatus == PaymentStatus.Pending &&
|
||||
o.PackageId == BasePackageId,
|
||||
cancellationToken);
|
||||
|
||||
if (pendingOrder != null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} has pending order {OrderId}",
|
||||
request.UserId,
|
||||
pendingOrder.Id);
|
||||
|
||||
// برگرداندن سفارش موجود برای ادامه پرداخت
|
||||
var existingTransaction = await _context.Transactions
|
||||
.FirstOrDefaultAsync(t => t.Id == pendingOrder.TransactionId, cancellationToken);
|
||||
|
||||
return new InitiateBasePackagePaymentResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "سفارش قبلی در انتظار پرداخت یافت شد.",
|
||||
OrderId = pendingOrder.Id,
|
||||
TransactionId = existingTransaction?.Id ?? 0,
|
||||
Amount = SystemConstants.BasePackageAmount
|
||||
};
|
||||
}
|
||||
|
||||
// 4. پیدا کردن آدرس پیشفرض کاربر
|
||||
var defaultAddress = await _context.UserAddresses
|
||||
.Where(a => a.UserId == request.UserId)
|
||||
.OrderByDescending(a => a.Created)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (defaultAddress == null)
|
||||
{
|
||||
_logger.LogWarning("No address found for user {UserId}", request.UserId);
|
||||
throw new ValidationException("لطفاً ابتدا یک آدرس برای خود ثبت کنید.");
|
||||
}
|
||||
|
||||
// 5. ایجاد Transaction با وضعیت Pending
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Amount = SystemConstants.BasePackageAmount,
|
||||
Description = $"خرید پکیج پایه ۵۶ میلیونی - کاربر #{user.Id}",
|
||||
PaymentStatus = PaymentStatus.Pending,
|
||||
Type = TransactionType.DepositIpg
|
||||
};
|
||||
|
||||
_context.Transactions.Add(transaction);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created Transaction {TransactionId} with Pending status for UserId {UserId}",
|
||||
transaction.Id,
|
||||
request.UserId);
|
||||
|
||||
// 6. ایجاد UserOrder با وضعیت Pending
|
||||
var order = new UserOrder
|
||||
{
|
||||
UserId = user.Id,
|
||||
PackageId = BasePackageId,
|
||||
Amount = SystemConstants.BasePackageAmount,
|
||||
PaymentStatus = PaymentStatus.Pending,
|
||||
DeliveryStatus = DeliveryStatus.None,
|
||||
UserAddressId = defaultAddress.Id,
|
||||
PaymentMethod = PaymentMethod.IPG,
|
||||
TransactionId = transaction.Id
|
||||
};
|
||||
|
||||
_context.UserOrders.Add(order);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created UserOrder {OrderId} for UserId {UserId}, TransactionId: {TransactionId}",
|
||||
order.Id,
|
||||
request.UserId,
|
||||
transaction.Id);
|
||||
|
||||
return new InitiateBasePackagePaymentResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "تراکنش و سفارش با موفقیت ثبت شد.",
|
||||
OrderId = order.Id,
|
||||
TransactionId = transaction.Id,
|
||||
Amount = SystemConstants.BasePackageAmount
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error in InitiateBasePackagePaymentCommand for UserId: {UserId}",
|
||||
request.UserId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment;
|
||||
|
||||
public class InitiateBasePackagePaymentCommandValidator : AbstractValidator<InitiateBasePackagePaymentCommand>
|
||||
{
|
||||
public InitiateBasePackagePaymentCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر معتبر نیست.");
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
|
||||
/// <summary>
|
||||
/// خرید پکیج طلایی (شروع فرآیند پرداخت)
|
||||
/// </summary>
|
||||
public record PurchaseGoldenPackageCommand : IRequest<PurchaseGoldenPackageResponseDto>
|
||||
{
|
||||
public long UserId { get; init; }
|
||||
public long PackageId { get; init; }
|
||||
public string ReturnUrl { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public class PurchaseGoldenPackageResponseDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public long OrderId { get; set; }
|
||||
public string PaymentGatewayUrl { get; set; } = string.Empty;
|
||||
public string TrackingCode { get; set; } = string.Empty;
|
||||
}
|
||||
-161
@@ -1,161 +0,0 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ValidationException = FluentValidation.ValidationException;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
|
||||
public class PurchaseGoldenPackageCommandHandler : IRequestHandler<PurchaseGoldenPackageCommand, PurchaseGoldenPackageResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly ILogger<PurchaseGoldenPackageCommandHandler> _logger;
|
||||
|
||||
public PurchaseGoldenPackageCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
ILogger<PurchaseGoldenPackageCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<PurchaseGoldenPackageResponseDto> Handle(PurchaseGoldenPackageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Starting golden package purchase for UserId: {UserId}, PackageId: {PackageId}",
|
||||
request.UserId,
|
||||
request.PackageId);
|
||||
|
||||
// 1. پیدا کردن کاربر
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found for golden package purchase. UserId: {UserId}", request.UserId);
|
||||
throw new NotFoundException(nameof(User), request.UserId);
|
||||
}
|
||||
|
||||
// 2. جلوگیری از خرید مجدد پکیج طلایی
|
||||
if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} has already purchased golden package via {Method}",
|
||||
request.UserId,
|
||||
user.PackagePurchaseMethod);
|
||||
|
||||
throw new ValidationException("شما قبلاً پکیج طلایی را خریداری کردهاید.");
|
||||
}
|
||||
|
||||
// 3. پیدا کردن پکیج
|
||||
var package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.Id == request.PackageId, cancellationToken);
|
||||
|
||||
if (package == null)
|
||||
{
|
||||
_logger.LogWarning("Golden package not found. PackageId: {PackageId}", request.PackageId);
|
||||
throw new NotFoundException(nameof(Package), request.PackageId);
|
||||
}
|
||||
|
||||
// اطمینان از اینکه این همان پکیج طلایی است
|
||||
if (!package.Title.Contains("طلایی", StringComparison.OrdinalIgnoreCase) &&
|
||||
!package.Title.Contains("golden", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"PackageId {PackageId} is not a golden package. Title: {Title}",
|
||||
request.PackageId,
|
||||
package.Title);
|
||||
|
||||
throw new ValidationException("فقط پکیج طلایی قابل خرید است.");
|
||||
}
|
||||
|
||||
// 4. پیدا کردن آدرس پیشفرض کاربر (الزامی برای UserOrder)
|
||||
var defaultAddress = await _context.UserAddresses
|
||||
.Where(a => a.UserId == request.UserId)
|
||||
.OrderByDescending(a => a.Created)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (defaultAddress == null)
|
||||
{
|
||||
_logger.LogWarning("No address found for user {UserId} in golden package purchase", request.UserId);
|
||||
throw new ValidationException("لطفاً ابتدا یک آدرس برای خود ثبت کنید.");
|
||||
}
|
||||
|
||||
// 5. ایجاد سفارش
|
||||
var order = new UserOrder
|
||||
{
|
||||
UserId = user.Id,
|
||||
PackageId = package.Id,
|
||||
Amount = package.Price,
|
||||
PaymentStatus = PaymentStatus.Pending,
|
||||
DeliveryStatus = DeliveryStatus.None,
|
||||
UserAddressId = defaultAddress.Id,
|
||||
PaymentMethod = PaymentMethod.IPG
|
||||
};
|
||||
|
||||
_context.UserOrders.Add(order);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created golden package UserOrder {OrderId} for UserId {UserId}, Amount: {Amount}",
|
||||
order.Id,
|
||||
request.UserId,
|
||||
order.Amount);
|
||||
|
||||
// 6. شروع پرداخت با درگاه
|
||||
var paymentRequest = new PaymentRequest
|
||||
{
|
||||
Amount = order.Amount,
|
||||
UserId = user.Id,
|
||||
Mobile = user.Mobile ?? string.Empty,
|
||||
CallbackUrl = request.ReturnUrl,
|
||||
Description = $"خرید پکیج طلایی - سفارش #{order.Id}"
|
||||
};
|
||||
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest, cancellationToken);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
{
|
||||
_logger.LogError(
|
||||
"Payment gateway initiation failed for golden package. OrderId {OrderId}: {ErrorMessage}",
|
||||
order.Id,
|
||||
paymentResult.ErrorMessage);
|
||||
|
||||
order.PaymentStatus = PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
throw new Exception($"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}");
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Golden package payment initiated successfully. OrderId: {OrderId}, RefId: {RefId}",
|
||||
order.Id,
|
||||
paymentResult.RefId);
|
||||
|
||||
return new PurchaseGoldenPackageResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "لطفاً به درگاه پرداخت منتقل شوید.",
|
||||
OrderId = order.Id,
|
||||
PaymentGatewayUrl = paymentResult.GatewayUrl ?? string.Empty,
|
||||
TrackingCode = paymentResult.RefId ?? string.Empty
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error in PurchaseGoldenPackageCommand for UserId: {UserId}",
|
||||
request.UserId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
|
||||
public class PurchaseGoldenPackageCommandValidator : AbstractValidator<PurchaseGoldenPackageCommand>
|
||||
{
|
||||
public PurchaseGoldenPackageCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر باید بزرگتر از 0 باشد");
|
||||
|
||||
RuleFor(x => x.PackageId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه پکیج باید بزرگتر از 0 باشد");
|
||||
|
||||
RuleFor(x => x.ReturnUrl)
|
||||
.NotEmpty()
|
||||
.WithMessage("آدرس بازگشت الزامی است")
|
||||
.Must(url => Uri.TryCreate(url, UriKind.Absolute, out _))
|
||||
.WithMessage("آدرس بازگشت معتبر نیست");
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Models;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchasePackage;
|
||||
|
||||
/// <summary>
|
||||
/// دستور خرید پکیج از طریق درگاه بانکی
|
||||
/// </summary>
|
||||
public class PurchasePackageCommand : IRequest<PaymentInitiateResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه کاربر
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
}
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Models;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ValidationException = FluentValidation.ValidationException;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchasePackage;
|
||||
|
||||
public class PurchasePackageCommandHandler
|
||||
: IRequestHandler<PurchasePackageCommand, PaymentInitiateResult>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<PurchasePackageCommandHandler> _logger;
|
||||
|
||||
public PurchasePackageCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
IConfiguration configuration,
|
||||
ILogger<PurchasePackageCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<PaymentInitiateResult> Handle(
|
||||
PurchasePackageCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Starting package purchase for UserId: {UserId}",
|
||||
request.UserId
|
||||
);
|
||||
|
||||
// 1. بررسی وجود کاربر
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found: {UserId}", request.UserId);
|
||||
throw new NotFoundException(nameof(User), request.UserId);
|
||||
}
|
||||
|
||||
// 2. بررسی اینکه قبلاً پکیج نخریده باشد
|
||||
if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} has already purchased package via {Method}",
|
||||
request.UserId,
|
||||
user.PackagePurchaseMethod
|
||||
);
|
||||
throw new ValidationException(
|
||||
"شما قبلاً پکیج را خریداری کردهاید"
|
||||
);
|
||||
}
|
||||
|
||||
// 3. پیدا کردن پکیج (فعلاً پکیج طلایی)
|
||||
var goldenPackage = await _context.Packages
|
||||
.FirstOrDefaultAsync(
|
||||
p => p.Title.Contains("طلایی") || p.Title.Contains("Golden"),
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
if (goldenPackage == null)
|
||||
{
|
||||
_logger.LogError("Package not found in database");
|
||||
throw new NotFoundException("پکیج یافت نشد");
|
||||
}
|
||||
|
||||
// 4. پیدا کردن آدرس پیشفرض کاربر (برای فیلد اجباری)
|
||||
var defaultAddress = await _context.UserAddresses
|
||||
.Where(a => a.UserId == request.UserId)
|
||||
.OrderByDescending(a => a.Created)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (defaultAddress == null)
|
||||
{
|
||||
_logger.LogWarning("No address found for user {UserId}", request.UserId);
|
||||
throw new ValidationException(
|
||||
"لطفاً ابتدا یک آدرس برای خود ثبت کنید"
|
||||
);
|
||||
}
|
||||
|
||||
// 5. ایجاد سفارش
|
||||
var order = new UserOrder
|
||||
{
|
||||
UserId = user.Id,
|
||||
PackageId = goldenPackage.Id,
|
||||
Amount = goldenPackage.Price, // 56,000,000 تومان
|
||||
PaymentStatus = PaymentStatus.Pending,
|
||||
DeliveryStatus = DeliveryStatus.None,
|
||||
UserAddressId = defaultAddress.Id,
|
||||
PaymentMethod = PaymentMethod.IPG
|
||||
};
|
||||
|
||||
_context.UserOrders.Add(order);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created UserOrder {OrderId} for UserId {UserId}, Amount: {Amount}",
|
||||
order.Id,
|
||||
request.UserId,
|
||||
order.Amount
|
||||
);
|
||||
|
||||
// 6. ایجاد درخواست پرداخت از درگاه
|
||||
var paymentRequest = new PaymentRequest
|
||||
{
|
||||
Amount = order.Amount,
|
||||
UserId = user.Id,
|
||||
Mobile = user.Mobile ?? "",
|
||||
CallbackUrl = $"{_configuration["CmsBaseUrl"] ?? "https://localhost:32846"}/api/package/verify-package",
|
||||
Description = $"خرید پکیج - سفارش #{order.Id}"
|
||||
};
|
||||
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
{
|
||||
_logger.LogError(
|
||||
"Payment gateway failed for OrderId {OrderId}: {ErrorMessage}",
|
||||
order.Id,
|
||||
paymentResult.ErrorMessage
|
||||
);
|
||||
|
||||
// بهروزرسانی وضعیت سفارش
|
||||
order.PaymentStatus = PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
throw new Exception(
|
||||
$"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}"
|
||||
);
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Payment initiated successfully. OrderId: {OrderId}, RefId: {RefId}",
|
||||
order.Id,
|
||||
paymentResult.RefId
|
||||
);
|
||||
|
||||
return paymentResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error in PurchasePackageCommand for UserId: {UserId}",
|
||||
request.UserId
|
||||
);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchasePackage;
|
||||
|
||||
public class PurchasePackageCommandValidator : AbstractValidator<PurchasePackageCommand>
|
||||
{
|
||||
public PurchasePackageCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
|
||||
}
|
||||
}
|
||||
+14
-1
@@ -11,5 +11,18 @@ public record UpdatePackageCommand : IRequest<Unit>
|
||||
public string ImagePath { get; init; }
|
||||
//قیمت
|
||||
public long Price { get; init; }
|
||||
|
||||
// فیلدهای جدید پکیج
|
||||
public int SortOrder { get; init; }
|
||||
public bool IsActive { get; init; }
|
||||
public bool IsBasePackage { get; init; }
|
||||
public bool SupportsDayaPurchase { get; init; }
|
||||
public bool SupportsDirectPurchase { get; init; }
|
||||
public long ActivationFee { get; init; }
|
||||
public double DiscountMultiplier { get; init; }
|
||||
public double MagicWalletMultiplier { get; init; }
|
||||
public int MaxBalancesPerLeg { get; init; }
|
||||
public int MaxNetworkLevel { get; init; }
|
||||
public long MagicWalletMaxDeposit { get; init; }
|
||||
public long MagicWalletMaxCredit { get; init; }
|
||||
public List<long> FeatureIds { get; init; } = new();
|
||||
}
|
||||
+17
@@ -16,6 +16,23 @@ public class UpdatePackageCommandHandler : IRequestHandler<UpdatePackageCommand,
|
||||
request.Adapt(entity);
|
||||
_context.Packages.Update(entity);
|
||||
entity.AddDomainEvent(new UpdatePackageEvent(entity));
|
||||
|
||||
// Sync PackageFeatures — remove old, add new
|
||||
var existingFeatures = await _context.PackageFeatures
|
||||
.Where(pf => pf.PackageId == request.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
_context.PackageFeatures.RemoveRange(existingFeatures);
|
||||
|
||||
foreach (var featureId in request.FeatureIds)
|
||||
{
|
||||
_context.PackageFeatures.Add(new PackageFeature
|
||||
{
|
||||
PackageId = request.Id,
|
||||
ClubFeatureId = featureId,
|
||||
IsIncluded = true
|
||||
});
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return Unit.Value;
|
||||
}
|
||||
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment;
|
||||
|
||||
/// <summary>
|
||||
/// تکمیل یا رد پرداخت پکیج پایه (فراخوانی توسط BFF بعد از Callback)
|
||||
/// </summary>
|
||||
public record VerifyBasePackagePaymentCommand : IRequest<VerifyBasePackagePaymentResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه سفارش CMS
|
||||
/// </summary>
|
||||
public long OrderId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه تراکنش CMS
|
||||
/// </summary>
|
||||
public long TransactionId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// آیا پرداخت موفق بوده؟ (نتیجه Verify از BFF)
|
||||
/// </summary>
|
||||
public bool PaymentSuccess { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// کد پیگیری بانکی (در صورت موفقیت)
|
||||
/// </summary>
|
||||
public string? RefId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// پیام از درگاه
|
||||
/// </summary>
|
||||
public string? Message { get; init; }
|
||||
}
|
||||
|
||||
public class VerifyBasePackagePaymentResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// موفقیت عملیات
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// پیام
|
||||
/// </summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// شناسه سفارش
|
||||
/// </summary>
|
||||
public long OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه تراکنش
|
||||
/// </summary>
|
||||
public long TransactionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// کد پیگیری بانکی
|
||||
/// </summary>
|
||||
public string? ReferenceCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// موجودی کیف پول بعد از شارژ
|
||||
/// </summary>
|
||||
public long WalletBalance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// موجودی تخفیف بعد از شارژ
|
||||
/// </summary>
|
||||
public long DiscountBalance { get; set; }
|
||||
}
|
||||
-205
@@ -1,205 +0,0 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment;
|
||||
|
||||
/// <summary>
|
||||
/// Handler برای تکمیل یا رد پرداخت پکیج پایه
|
||||
/// فراخوانی توسط BFF بعد از اینکه نتیجه Verify از PYMS مشخص شد
|
||||
///
|
||||
/// اگر PaymentSuccess = true:
|
||||
/// - شارژ کیف پول (Balance + DiscountBalance)
|
||||
/// - ثبت لاگ تغییر کیف پول
|
||||
/// - بهروزرسانی سفارش و کاربر
|
||||
///
|
||||
/// اگر PaymentSuccess = false:
|
||||
/// - فقط آپدیت وضعیت تراکنش و سفارش به Reject
|
||||
/// </summary>
|
||||
public class VerifyBasePackagePaymentCommandHandler : IRequestHandler<VerifyBasePackagePaymentCommand, VerifyBasePackagePaymentResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ILogger<VerifyBasePackagePaymentCommandHandler> _logger;
|
||||
|
||||
public VerifyBasePackagePaymentCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
ILogger<VerifyBasePackagePaymentCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<VerifyBasePackagePaymentResponseDto> Handle(
|
||||
VerifyBasePackagePaymentCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Processing base package payment result. OrderId: {OrderId}, TransactionId: {TransactionId}, PaymentSuccess: {PaymentSuccess}",
|
||||
request.OrderId,
|
||||
request.TransactionId,
|
||||
request.PaymentSuccess);
|
||||
|
||||
// 1. پیدا کردن سفارش با کاربر
|
||||
var order = await _context.UserOrders
|
||||
.Include(o => o.User)
|
||||
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||
|
||||
if (order == null)
|
||||
{
|
||||
_logger.LogWarning("Order not found. OrderId: {OrderId}", request.OrderId);
|
||||
throw new NotFoundException(nameof(UserOrder), request.OrderId);
|
||||
}
|
||||
|
||||
// 2. پیدا کردن تراکنش
|
||||
var transaction = await _context.Transactions
|
||||
.FirstOrDefaultAsync(t => t.Id == request.TransactionId, cancellationToken);
|
||||
|
||||
if (transaction == null)
|
||||
{
|
||||
_logger.LogWarning("Transaction not found. TransactionId: {TransactionId}", request.TransactionId);
|
||||
throw new NotFoundException(nameof(Transaction), request.TransactionId);
|
||||
}
|
||||
|
||||
// 3. بررسی Idempotency - اگر قبلاً تایید شده باشد
|
||||
if (order.PaymentStatus == PaymentStatus.Success)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Order {OrderId} already verified successfully",
|
||||
request.OrderId);
|
||||
|
||||
var existingWallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
|
||||
|
||||
return new VerifyBasePackagePaymentResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "پرداخت قبلاً با موفقیت تایید شده است.",
|
||||
OrderId = order.Id,
|
||||
TransactionId = transaction.Id,
|
||||
ReferenceCode = transaction.RefId,
|
||||
WalletBalance = existingWallet?.Balance ?? 0,
|
||||
DiscountBalance = existingWallet?.DiscountBalance ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
// 4. اگر پرداخت ناموفق بود - فقط وضعیت را Reject میکنیم
|
||||
if (!request.PaymentSuccess)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Payment failed. OrderId: {OrderId}, Message: {Message}",
|
||||
request.OrderId,
|
||||
request.Message);
|
||||
|
||||
transaction.PaymentStatus = PaymentStatus.Reject;
|
||||
order.PaymentStatus = PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new VerifyBasePackagePaymentResponseDto
|
||||
{
|
||||
Success = false,
|
||||
Message = request.Message ?? "پرداخت ناموفق بود.",
|
||||
OrderId = order.Id,
|
||||
TransactionId = transaction.Id
|
||||
};
|
||||
}
|
||||
|
||||
// 5. پرداخت موفق - پیدا کردن یا ایجاد کیف پول
|
||||
var userWallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
|
||||
|
||||
if (userWallet == null)
|
||||
{
|
||||
_logger.LogInformation("Creating new wallet for UserId: {UserId}", order.UserId);
|
||||
userWallet = new UserWallet
|
||||
{
|
||||
UserId = order.UserId,
|
||||
Balance = 0,
|
||||
DiscountBalance = 0,
|
||||
NetworkBalance = 0
|
||||
};
|
||||
_context.UserWallets.Add(userWallet);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
// 6. شارژ کیف پول (هم Balance و هم DiscountBalance)
|
||||
var oldBalance = userWallet.Balance;
|
||||
var oldDiscountBalance = userWallet.DiscountBalance;
|
||||
|
||||
userWallet.Balance += SystemConstants.BasePackageAmount;
|
||||
userWallet.DiscountBalance += SystemConstants.BasePackageAmount * 2;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Charging wallet for user {UserId}. Balance: {OldBalance} -> {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}",
|
||||
order.UserId,
|
||||
oldBalance,
|
||||
userWallet.Balance,
|
||||
oldDiscountBalance,
|
||||
userWallet.DiscountBalance);
|
||||
|
||||
// 7. بهروزرسانی Transaction
|
||||
transaction.PaymentStatus = PaymentStatus.Success;
|
||||
transaction.PaymentDate = DateTime.Now;
|
||||
transaction.RefId = request.RefId;
|
||||
|
||||
// 8. ثبت لاگ تغییر کیف پول
|
||||
var changeLog = new UserWalletChangeLog
|
||||
{
|
||||
WalletId = userWallet.Id,
|
||||
CurrentBalance = userWallet.Balance,
|
||||
ChangeValue = SystemConstants.BasePackageAmount,
|
||||
CurrentNetworkBalance = userWallet.NetworkBalance,
|
||||
ChangeNerworkValue = 0,
|
||||
CurrentDiscountBalance = userWallet.DiscountBalance,
|
||||
ChangeDiscountValue = SystemConstants.BasePackageAmount * 2,
|
||||
IsIncrease = true,
|
||||
RefrenceId = transaction.Id
|
||||
};
|
||||
|
||||
await _context.UserWalletChangeLogs.AddAsync(changeLog, cancellationToken);
|
||||
|
||||
// 9. بهروزرسانی Order
|
||||
order.TransactionId = transaction.Id;
|
||||
order.PaymentStatus = PaymentStatus.Success;
|
||||
order.PaymentDate = DateTime.Now;
|
||||
order.PaymentMethod = PaymentMethod.IPG;
|
||||
|
||||
// 10. بهروزرسانی User
|
||||
order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Base package payment completed successfully. OrderId: {OrderId}, UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}",
|
||||
order.Id,
|
||||
order.UserId,
|
||||
transaction.Id,
|
||||
request.RefId);
|
||||
|
||||
return new VerifyBasePackagePaymentResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "پرداخت با موفقیت تایید شد. کیف پول شما شارژ گردید.",
|
||||
OrderId = order.Id,
|
||||
TransactionId = transaction.Id,
|
||||
ReferenceCode = request.RefId,
|
||||
WalletBalance = userWallet.Balance,
|
||||
DiscountBalance = userWallet.DiscountBalance
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error in VerifyBasePackagePaymentCommand. OrderId: {OrderId}",
|
||||
request.OrderId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment;
|
||||
|
||||
public class VerifyBasePackagePaymentCommandValidator : AbstractValidator<VerifyBasePackagePaymentCommand>
|
||||
{
|
||||
public VerifyBasePackagePaymentCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.OrderId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه سفارش معتبر نیست.");
|
||||
|
||||
RuleFor(x => x.TransactionId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه تراکنش معتبر نیست.");
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
|
||||
/// <summary>
|
||||
/// تایید پرداخت پکیج طلایی (پس از بازگشت از درگاه)
|
||||
/// </summary>
|
||||
public record VerifyGoldenPackagePurchaseCommand : IRequest<VerifyGoldenPackagePurchaseResponseDto>
|
||||
{
|
||||
public long OrderId { get; init; }
|
||||
public string Authority { get; init; } = string.Empty;
|
||||
public string Status { get; init; } = string.Empty; // OK یا NOK
|
||||
}
|
||||
|
||||
public class VerifyGoldenPackagePurchaseResponseDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public long OrderId { get; set; }
|
||||
public long TransactionId { get; set; }
|
||||
public string ReferenceCode { get; set; } = string.Empty;
|
||||
public long WalletBalance { get; set; }
|
||||
}
|
||||
-187
@@ -1,187 +0,0 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ValidationException = FluentValidation.ValidationException;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
|
||||
public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler<VerifyGoldenPackagePurchaseCommand, VerifyGoldenPackagePurchaseResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly ILogger<VerifyGoldenPackagePurchaseCommandHandler> _logger;
|
||||
|
||||
public VerifyGoldenPackagePurchaseCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
ILogger<VerifyGoldenPackagePurchaseCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<VerifyGoldenPackagePurchaseResponseDto> Handle(VerifyGoldenPackagePurchaseCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Verifying golden package purchase. OrderId: {OrderId}, Authority: {Authority}, Status: {Status}",
|
||||
request.OrderId,
|
||||
request.Authority,
|
||||
request.Status);
|
||||
|
||||
// 1. اگر پرداخت از سمت درگاه موفق گزارش نشده باشد
|
||||
if (!string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var pendingOrder = await _context.UserOrders
|
||||
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||
|
||||
if (pendingOrder != null && pendingOrder.PaymentStatus == PaymentStatus.Pending)
|
||||
{
|
||||
pendingOrder.PaymentStatus = PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
throw new ValidationException("پرداخت توسط کاربر لغو شد.");
|
||||
}
|
||||
|
||||
// 2. پیدا کردن سفارش به همراه کاربر
|
||||
var order = await _context.UserOrders
|
||||
.Include(o => o.User)
|
||||
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||
|
||||
if (order == null)
|
||||
{
|
||||
_logger.LogWarning("Golden package order not found. OrderId: {OrderId}", request.OrderId);
|
||||
throw new NotFoundException(nameof(UserOrder), request.OrderId);
|
||||
}
|
||||
|
||||
// اگر قبلاً با موفقیت پرداخت شده، پاسخ idempotent برگردانیم
|
||||
if (order.PaymentStatus == PaymentStatus.Success && order.TransactionId.HasValue)
|
||||
{
|
||||
var existingWallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
|
||||
|
||||
var existingTransaction = await _context.Transactions
|
||||
.FirstOrDefaultAsync(t => t.Id == order.TransactionId.Value, cancellationToken);
|
||||
|
||||
return new VerifyGoldenPackagePurchaseResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "پرداخت قبلاً با موفقیت تایید شده است.",
|
||||
OrderId = order.Id,
|
||||
TransactionId = existingTransaction?.Id ?? order.TransactionId.Value,
|
||||
ReferenceCode = existingTransaction?.RefId ?? string.Empty,
|
||||
WalletBalance = existingWallet?.Balance ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Verify با درگاه پرداخت
|
||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
request.Authority,
|
||||
request.Authority,
|
||||
cancellationToken);
|
||||
|
||||
if (!verifyResult.IsSuccess)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Golden package payment verification failed. OrderId: {OrderId}, Message: {Message}",
|
||||
request.OrderId,
|
||||
verifyResult.Message);
|
||||
|
||||
order.PaymentStatus = PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
throw new ValidationException($"تراکنش ناموفق: {verifyResult.Message}");
|
||||
}
|
||||
|
||||
// 4. شارژ کیف پول (Balance فقط طبق سناریوی پکیج)
|
||||
var wallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
|
||||
|
||||
if (wallet == null)
|
||||
{
|
||||
_logger.LogError("Wallet not found for UserId: {UserId}", order.UserId);
|
||||
throw new NotFoundException($"کیف پول کاربر با شناسه {order.UserId} یافت نشد");
|
||||
}
|
||||
|
||||
var oldBalance = wallet.Balance;
|
||||
wallet.Balance += order.Amount;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Charging wallet Balance for user {UserId} from {OldBalance} to {NewBalance}",
|
||||
order.UserId,
|
||||
oldBalance,
|
||||
wallet.Balance);
|
||||
|
||||
// 5. ثبت Transaction
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Amount = order.Amount,
|
||||
Description = $"خرید پکیج طلایی از درگاه - سفارش #{order.Id}",
|
||||
PaymentStatus = PaymentStatus.Success,
|
||||
PaymentDate = DateTime.Now,
|
||||
RefId = verifyResult.RefId,
|
||||
Type = TransactionType.DepositIpg
|
||||
};
|
||||
|
||||
_context.Transactions.Add(transaction);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 6. ثبت لاگ تغییر کیف پول
|
||||
var changeLog = new UserWalletChangeLog
|
||||
{
|
||||
WalletId = wallet.Id,
|
||||
CurrentBalance = wallet.Balance,
|
||||
ChangeValue = order.Amount,
|
||||
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||
ChangeNerworkValue = 0,
|
||||
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||
ChangeDiscountValue = 0,
|
||||
IsIncrease = true,
|
||||
RefrenceId = transaction.Id
|
||||
};
|
||||
|
||||
await _context.UserWalletChangeLogs.AddAsync(changeLog, cancellationToken);
|
||||
|
||||
// 7. بهروزرسانی سفارش و کاربر
|
||||
order.TransactionId = transaction.Id;
|
||||
order.PaymentStatus = PaymentStatus.Success;
|
||||
order.PaymentDate = DateTime.Now;
|
||||
order.PaymentMethod = PaymentMethod.IPG;
|
||||
order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Golden package purchase verified successfully. OrderId: {OrderId}, UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}",
|
||||
order.Id,
|
||||
order.UserId,
|
||||
transaction.Id,
|
||||
verifyResult.RefId);
|
||||
|
||||
return new VerifyGoldenPackagePurchaseResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "پرداخت با موفقیت تایید شد. کیف پول شما شارژ گردید.",
|
||||
OrderId = order.Id,
|
||||
TransactionId = transaction.Id,
|
||||
ReferenceCode = verifyResult.RefId,
|
||||
WalletBalance = wallet.Balance
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error in VerifyGoldenPackagePurchaseCommand. OrderId: {OrderId}",
|
||||
request.OrderId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
|
||||
public class VerifyGoldenPackagePurchaseCommandValidator : AbstractValidator<VerifyGoldenPackagePurchaseCommand>
|
||||
{
|
||||
public VerifyGoldenPackagePurchaseCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.OrderId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
|
||||
|
||||
RuleFor(x => x.Authority)
|
||||
.NotEmpty()
|
||||
.WithMessage("کد Authority الزامی است");
|
||||
|
||||
RuleFor(x => x.Status)
|
||||
.NotEmpty()
|
||||
.WithMessage("وضعیت پرداخت الزامی است")
|
||||
.Must(s => s == "OK" || s == "NOK")
|
||||
.WithMessage("وضعیت باید OK یا NOK باشد");
|
||||
}
|
||||
}
|
||||
+28
-6
@@ -99,9 +99,11 @@ public class VerifyPackagePurchaseCommandHandler
|
||||
wallet.Balance
|
||||
);
|
||||
|
||||
// شارژ DiscountBalance (موجودی تخفیف) — دو برابر مبلغ سفارش
|
||||
// شارژ DiscountBalance (موجودی تخفیف) — ضریب تخفیف از پکیج
|
||||
var oldDiscountBalance = wallet.DiscountBalance;
|
||||
wallet.DiscountBalance += order.Amount * 2;
|
||||
var discountMultiplier = order.Package?.DiscountMultiplier ?? 2.0m;
|
||||
var discountAmount = (long)(order.Amount * discountMultiplier);
|
||||
wallet.DiscountBalance += discountAmount;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Charging DiscountBalance for UserId {UserId}: {OldBalance} -> {NewBalance}",
|
||||
@@ -132,7 +134,7 @@ public class VerifyPackagePurchaseCommandHandler
|
||||
ChangeValue = order.Amount,
|
||||
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||
ChangeNerworkValue = 0,
|
||||
CurrentDiscountBalance = wallet.DiscountBalance - (order.Amount * 2), // قبل از شارژ DiscountBalance
|
||||
CurrentDiscountBalance = wallet.DiscountBalance - discountAmount, // قبل از شارژ DiscountBalance
|
||||
ChangeDiscountValue = 0,
|
||||
IsIncrease = true,
|
||||
RefrenceId = transaction.Id
|
||||
@@ -148,19 +150,39 @@ public class VerifyPackagePurchaseCommandHandler
|
||||
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||
ChangeNerworkValue = 0,
|
||||
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||
ChangeDiscountValue = order.Amount * 2,
|
||||
ChangeDiscountValue = discountAmount,
|
||||
IsIncrease = true,
|
||||
RefrenceId = transaction.Id
|
||||
};
|
||||
await _context.UserWalletChangeLogs.AddAsync(discountLog, cancellationToken);
|
||||
|
||||
// 8. بهروزرسانی Order
|
||||
// 8. ثبت UserPackagePurchase
|
||||
if (order.PackageId.HasValue)
|
||||
{
|
||||
var packagePurchase = new UserPackagePurchase
|
||||
{
|
||||
UserId = order.UserId,
|
||||
PackageId = order.PackageId.Value,
|
||||
PurchaseMethod = PackagePurchaseMethod.DirectPurchase,
|
||||
PurchasedAt = DateTime.Now,
|
||||
Amount = order.Amount,
|
||||
OrderId = order.Id,
|
||||
TransactionId = transaction.Id
|
||||
};
|
||||
await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created UserPackagePurchase for UserId {UserId}, PackageId {PackageId}",
|
||||
order.UserId, order.PackageId.Value);
|
||||
}
|
||||
|
||||
// 9. بهروزرسانی Order
|
||||
order.TransactionId = transaction.Id;
|
||||
order.PaymentStatus = PaymentStatus.Success;
|
||||
order.PaymentDate = DateTime.Now;
|
||||
order.PaymentMethod = PaymentMethod.IPG;
|
||||
|
||||
// 9. تغییر User.PackagePurchaseMethod
|
||||
// 10. تغییر User.PackagePurchaseMethod
|
||||
order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
+17
-2
@@ -11,8 +11,10 @@ public class GetAllPackageByFilterQueryHandler : IRequestHandler<GetAllPackageBy
|
||||
public async Task<GetAllPackageByFilterResponseDto> Handle(GetAllPackageByFilterQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.Packages
|
||||
.Where(x => !x.IsDeleted)
|
||||
.ApplyOrder(sortBy: request.SortBy)
|
||||
.AsNoTracking()
|
||||
.Include(x => x.PackageFeatures)
|
||||
.AsQueryable();
|
||||
if (request.Filter is not null)
|
||||
{
|
||||
@@ -24,11 +26,24 @@ public class GetAllPackageByFilterQueryHandler : IRequestHandler<GetAllPackageBy
|
||||
.Where(x => request.Filter.Price == null || x.Price == request.Filter.Price)
|
||||
;
|
||||
}
|
||||
var packages = await query
|
||||
.PaginatedListAsync(paginationState: request.PaginationState)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var models = packages.Select(p =>
|
||||
{
|
||||
var model = p.Adapt<GetAllPackageByFilterResponseModel>();
|
||||
model.FeatureIds = p.PackageFeatures?
|
||||
.Where(pf => pf.IsIncluded)
|
||||
.Select(pf => pf.ClubFeatureId)
|
||||
.ToList() ?? new();
|
||||
return model;
|
||||
}).ToList();
|
||||
|
||||
return new GetAllPackageByFilterResponseDto
|
||||
{
|
||||
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
|
||||
Models = await query.PaginatedListAsync(paginationState: request.PaginationState)
|
||||
.ProjectToType<GetAllPackageByFilterResponseModel>().ToListAsync(cancellationToken)
|
||||
Models = models
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -18,4 +18,18 @@ public class GetAllPackageByFilterResponseDto
|
||||
public string ImagePath { get; set; }
|
||||
//قیمت
|
||||
public long Price { get; set; }
|
||||
// فیلدهای جدید پکیج
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsBasePackage { get; set; }
|
||||
public bool SupportsDayaPurchase { get; set; }
|
||||
public bool SupportsDirectPurchase { get; set; }
|
||||
public long ActivationFee { get; set; }
|
||||
public decimal DiscountMultiplier { get; set; }
|
||||
public decimal MagicWalletMultiplier { get; set; }
|
||||
public int MaxBalancesPerLeg { get; set; }
|
||||
public int MaxNetworkLevel { get; set; }
|
||||
public long MagicWalletMaxDeposit { get; set; }
|
||||
public long MagicWalletMaxCredit { get; set; }
|
||||
public List<long> FeatureIds { get; set; } = new();
|
||||
}
|
||||
|
||||
+46
-30
@@ -18,44 +18,60 @@ public class GetCustomerPackageDetailsQueryHandler : IRequestHandler<GetCustomer
|
||||
{
|
||||
var package = await _context.Packages
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Id == request.PackageId)
|
||||
.ProjectToType<GetCustomerPackageDetailsResponseDto>()
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
.Include(p => p.PackageFeatures)
|
||||
.ThenInclude(pf => pf.ClubFeature)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.PackageId && !x.IsDeleted, cancellationToken);
|
||||
|
||||
if (package == null)
|
||||
throw new NotFoundException(nameof(Package), request.PackageId);
|
||||
|
||||
// Add features based on package (this could be stored in DB in future)
|
||||
package.Features = new List<PackageFeatureDto>
|
||||
var response = new GetCustomerPackageDetailsResponseDto
|
||||
{
|
||||
new PackageFeatureDto
|
||||
{
|
||||
Title = "درآمد کمیسیون",
|
||||
Description = "دریافت کمیسیون از فروش محصولات",
|
||||
Icon = "commission",
|
||||
IsHighlighted = true
|
||||
},
|
||||
new PackageFeatureDto
|
||||
{
|
||||
Title = "پشتیبانی 24/7",
|
||||
Description = "دسترسی به پشتیبانی در تمام ساعات شبانه روز",
|
||||
Icon = "support",
|
||||
IsHighlighted = false
|
||||
},
|
||||
new PackageFeatureDto
|
||||
{
|
||||
Title = "آموزشهای تخصصی",
|
||||
Description = "دسترسی به دورههای آموزشی و وبینارها",
|
||||
Icon = "education",
|
||||
IsHighlighted = true
|
||||
}
|
||||
Id = package.Id,
|
||||
Title = package.Title,
|
||||
Description = package.Description,
|
||||
Price = package.Price,
|
||||
ImagePath = package.ImagePath,
|
||||
ActivationFee = package.ActivationFee,
|
||||
DiscountMultiplier = (double)package.DiscountMultiplier,
|
||||
MagicWalletMultiplier = (double)package.MagicWalletMultiplier,
|
||||
MagicWalletMaxDeposit = package.MagicWalletMaxDeposit,
|
||||
MagicWalletMaxCredit = package.MagicWalletMaxCredit,
|
||||
IsBasePackage = package.IsBasePackage,
|
||||
SupportsDayaPurchase = package.SupportsDayaPurchase,
|
||||
SupportsDirectPurchase = package.SupportsDirectPurchase
|
||||
};
|
||||
|
||||
// Set purchase requirements
|
||||
package.Requirements = new PurchaseRequirementsDto
|
||||
// بارگذاری ویژگیها از DB (PackageFeatures → ClubFeature)
|
||||
if (package.PackageFeatures?.Any() == true)
|
||||
{
|
||||
response.Features = package.PackageFeatures
|
||||
.Where(pf => pf.ClubFeature != null)
|
||||
.Select(pf => new PackageFeatureDto
|
||||
{
|
||||
Title = pf.ClubFeature.Title ?? string.Empty,
|
||||
Description = pf.ClubFeature.Description ?? string.Empty,
|
||||
Icon = "feature",
|
||||
IsHighlighted = pf.ClubFeature.IsActive
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// فالبک به ویژگیهای پیشفرض
|
||||
response.Features = new List<PackageFeatureDto>
|
||||
{
|
||||
new() { Title = "درآمد کمیسیون", Description = "دریافت کمیسیون از فروش محصولات", Icon = "commission", IsHighlighted = true },
|
||||
new() { Title = "پشتیبانی 24/7", Description = "دسترسی به پشتیبانی در تمام ساعات شبانه روز", Icon = "support", IsHighlighted = false },
|
||||
new() { Title = "آموزشهای تخصصی", Description = "دسترسی به دورههای آموزشی و وبینارها", Icon = "education", IsHighlighted = true }
|
||||
};
|
||||
}
|
||||
|
||||
// شرایط خرید
|
||||
response.Requirements = new PurchaseRequirementsDto
|
||||
{
|
||||
RequiresMembership = false,
|
||||
MinimumWalletBalance = package.Price / 10, // 10% minimum
|
||||
MinimumWalletBalance = package.Price / 10,
|
||||
Restrictions = new List<string>
|
||||
{
|
||||
"باید حداقل 18 سال سن داشته باشید",
|
||||
@@ -63,6 +79,6 @@ public class GetCustomerPackageDetailsQueryHandler : IRequestHandler<GetCustomer
|
||||
}
|
||||
};
|
||||
|
||||
return package;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -9,6 +9,15 @@ public class GetCustomerPackageDetailsResponseDto
|
||||
public string ImagePath { get; set; }
|
||||
public List<PackageFeatureDto> Features { get; set; } = new();
|
||||
public PurchaseRequirementsDto Requirements { get; set; }
|
||||
// New package-based fields
|
||||
public long ActivationFee { get; set; }
|
||||
public double DiscountMultiplier { get; set; }
|
||||
public double MagicWalletMultiplier { get; set; }
|
||||
public long MagicWalletMaxDeposit { get; set; }
|
||||
public long MagicWalletMaxCredit { get; set; }
|
||||
public bool IsBasePackage { get; set; }
|
||||
public bool SupportsDayaPurchase { get; set; }
|
||||
public bool SupportsDirectPurchase { get; set; }
|
||||
}
|
||||
|
||||
public class PackageFeatureDto
|
||||
|
||||
+35
-27
@@ -17,35 +17,43 @@ public class GetCustomerPackagesQueryHandler : IRequestHandler<GetCustomerPackag
|
||||
{
|
||||
var query = _context.Packages
|
||||
.AsNoTracking()
|
||||
.Where(p => !p.IsDeleted)
|
||||
.AsQueryable();
|
||||
|
||||
// Filter by PackageType if specified
|
||||
if (request.PackageTypeFilter.HasValue)
|
||||
// فیلتر IsActive
|
||||
if (!request.IncludeInactive)
|
||||
query = query.Where(p => p.IsActive);
|
||||
|
||||
// مرتبسازی بر اساس SortOrder
|
||||
query = query.OrderBy(p => p.SortOrder);
|
||||
|
||||
var packages = await query.ToListAsync(cancellationToken);
|
||||
|
||||
return packages.Select(p => new GetCustomerPackagesResponseDto
|
||||
{
|
||||
// Note: Package entity doesn't have PackageType enum, so we filter by convention
|
||||
// Assuming Title or Description contains the package type indicator
|
||||
// If Package entity needs PackageType field, it should be added to migration
|
||||
}
|
||||
|
||||
// Get all packages (assuming all are available unless marked otherwise)
|
||||
var packages = await query
|
||||
.ProjectToType<GetCustomerPackagesResponseDto>()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// Map additional fields
|
||||
foreach (var package in packages)
|
||||
{
|
||||
package.Name = package.Title;
|
||||
package.ImageUrl = package.ImagePath;
|
||||
package.Currency = "IRR";
|
||||
package.IsAvailable = true;
|
||||
package.ValidityDays = 365; // Default validity
|
||||
package.IsPopular = false;
|
||||
package.ShortDescription = package.Description?.Length > 100
|
||||
? package.Description.Substring(0, 100) + "..."
|
||||
: package.Description;
|
||||
}
|
||||
|
||||
return packages;
|
||||
Id = p.Id,
|
||||
Name = p.Title,
|
||||
Title = p.Title,
|
||||
Description = p.Description,
|
||||
Price = p.Price,
|
||||
ImageUrl = p.ImagePath,
|
||||
ImagePath = p.ImagePath,
|
||||
Currency = "IRR",
|
||||
IsAvailable = p.IsActive,
|
||||
ValidityDays = 365,
|
||||
IsPopular = p.IsBasePackage,
|
||||
ShortDescription = p.Description?.Length > 100
|
||||
? p.Description.Substring(0, 100) + "..."
|
||||
: p.Description,
|
||||
// New fields
|
||||
ActivationFee = p.ActivationFee,
|
||||
DiscountMultiplier = (double)p.DiscountMultiplier,
|
||||
MagicWalletMultiplier = (double)p.MagicWalletMultiplier,
|
||||
MagicWalletMaxDeposit = p.MagicWalletMaxDeposit,
|
||||
MagicWalletMaxCredit = p.MagicWalletMaxCredit,
|
||||
IsBasePackage = p.IsBasePackage,
|
||||
SupportsDayaPurchase = p.SupportsDayaPurchase,
|
||||
SupportsDirectPurchase = p.SupportsDirectPurchase
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -15,4 +15,13 @@ public class GetCustomerPackagesResponseDto
|
||||
public int ValidityDays { get; set; }
|
||||
public bool IsPopular { get; set; }
|
||||
public string ShortDescription { get; set; }
|
||||
// New package-based fields
|
||||
public long ActivationFee { get; set; }
|
||||
public double DiscountMultiplier { get; set; }
|
||||
public double MagicWalletMultiplier { get; set; }
|
||||
public long MagicWalletMaxDeposit { get; set; }
|
||||
public long MagicWalletMaxCredit { get; set; }
|
||||
public bool IsBasePackage { get; set; }
|
||||
public bool SupportsDayaPurchase { get; set; }
|
||||
public bool SupportsDirectPurchase { get; set; }
|
||||
}
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ public class GetCustomerPurchaseHistoryQueryHandler : IRequestHandler<GetCustome
|
||||
.AsNoTracking()
|
||||
.Where(x => x.UserId == userId && x.PackageId != null)
|
||||
.Include(x => x.Package)
|
||||
.Include(x => x.Transaction)
|
||||
.AsQueryable();
|
||||
|
||||
// Apply date filters if specified
|
||||
|
||||
+11
-4
@@ -11,12 +11,19 @@ public class GetPackageQueryHandler : IRequestHandler<GetPackageQuery, GetPackag
|
||||
public async Task<GetPackageResponseDto> Handle(GetPackageQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _context.Packages
|
||||
var package = await _context.Packages
|
||||
.AsNoTracking()
|
||||
.Include(x => x.PackageFeatures)
|
||||
.Where(x => x.Id == request.Id)
|
||||
.ProjectToType<GetPackageResponseDto>()
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
?? throw new NotFoundException(nameof(Package), request.Id);
|
||||
|
||||
return response ?? throw new NotFoundException(nameof(Package), request.Id);
|
||||
var response = package.Adapt<GetPackageResponseDto>();
|
||||
response.FeatureIds = package.PackageFeatures?
|
||||
.Where(pf => pf.IsIncluded)
|
||||
.Select(pf => pf.ClubFeatureId)
|
||||
.ToList() ?? new();
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -11,5 +11,18 @@ public class GetPackageResponseDto
|
||||
public string ImagePath { get; set; }
|
||||
//قیمت
|
||||
public long Price { get; set; }
|
||||
|
||||
// فیلدهای جدید پکیج
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsBasePackage { get; set; }
|
||||
public bool SupportsDayaPurchase { get; set; }
|
||||
public bool SupportsDirectPurchase { get; set; }
|
||||
public long ActivationFee { get; set; }
|
||||
public decimal DiscountMultiplier { get; set; }
|
||||
public decimal MagicWalletMultiplier { get; set; }
|
||||
public int MaxBalancesPerLeg { get; set; }
|
||||
public int MaxNetworkLevel { get; set; }
|
||||
public long MagicWalletMaxDeposit { get; set; }
|
||||
public long MagicWalletMaxCredit { get; set; }
|
||||
public List<long> FeatureIds { get; set; } = new();
|
||||
}
|
||||
+44
-41
@@ -1,3 +1,4 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
@@ -15,47 +16,49 @@ public class GetUserPackageStatusQueryHandler : IRequestHandler<GetUserPackageSt
|
||||
|
||||
public async Task<UserPackageStatusDto> Handle(GetUserPackageStatusQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: پیادهسازی دریافت وضعیت پکیج کاربر
|
||||
//
|
||||
// 1. دریافت اطلاعات کاربر:
|
||||
// - var user = await _context.Users
|
||||
// .Include(u => u.UserWallet)
|
||||
// .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken)
|
||||
// - if (user == null) throw new NotFoundException("کاربر یافت نشد")
|
||||
//
|
||||
// 2. دریافت عضویت باشگاه:
|
||||
// - var clubMembership = await _context.ClubMemberships
|
||||
// .FirstOrDefaultAsync(c => c.UserId == user.Id && c.IsActive, cancellationToken)
|
||||
//
|
||||
// 3. دریافت آخرین سفارش پکیج:
|
||||
// - var lastPackageOrder = await _context.UserOrders
|
||||
// .Where(o => o.UserId == user.Id && o.PackageId != null)
|
||||
// .OrderByDescending(o => o.Created)
|
||||
// .FirstOrDefaultAsync(cancellationToken)
|
||||
//
|
||||
// 4. بررسی شرایط فعالسازی باشگاه:
|
||||
// - var wallet = user.UserWallet
|
||||
// - bool canActivate =
|
||||
// user.PackagePurchaseMethod != PackagePurchaseMethod.None &&
|
||||
// clubMembership == null &&
|
||||
// wallet != null &&
|
||||
// wallet.Balance >= 56_000_000
|
||||
//
|
||||
// 5. برگشت DTO:
|
||||
// - return new UserPackageStatusDto {
|
||||
// UserId = user.Id,
|
||||
// PackagePurchaseMethod = user.PackagePurchaseMethod.ToString(),
|
||||
// HasPurchasedPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None,
|
||||
// IsClubMemberActive = clubMembership != null,
|
||||
// WalletBalance = wallet?.Balance ?? 0,
|
||||
// DiscountBalance = wallet?.DiscountBalance ?? 0,
|
||||
// CanActivateClubMembership = canActivate,
|
||||
// LastOrderNumber = lastPackageOrder?.OrderNumber,
|
||||
// LastPurchaseDate = lastPackageOrder?.Created
|
||||
// }
|
||||
//
|
||||
// نکته: این query برای UI مفید است تا وضعیت کاربر را نمایش دهد
|
||||
// 1. دریافت اطلاعات کاربر
|
||||
var user = await _context.Users
|
||||
.Include(u => u.UserWallets)
|
||||
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
|
||||
|
||||
throw new NotImplementedException("GetUserPackageStatus needs implementation");
|
||||
if (user == null)
|
||||
throw new NotFoundException(nameof(User), request.UserId);
|
||||
|
||||
// 2. دریافت عضویت باشگاه
|
||||
var clubMembership = await _context.ClubMemberships
|
||||
.FirstOrDefaultAsync(c => c.UserId == user.Id && c.IsActive, cancellationToken);
|
||||
|
||||
// 3. دریافت آخرین سفارش پکیج
|
||||
var lastPackageOrder = await _context.UserOrders
|
||||
.Where(o => o.UserId == user.Id && o.PackageId != null)
|
||||
.OrderByDescending(o => o.Created)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
// 4. بارگذاری پکیج پایه برای بررسی شرایط فعالسازی
|
||||
var basePackage = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken);
|
||||
|
||||
var wallet = user.UserWallets.FirstOrDefault();
|
||||
|
||||
// 5. بررسی شرایط فعالسازی باشگاه
|
||||
var canActivate =
|
||||
user.PackagePurchaseMethod != PackagePurchaseMethod.None &&
|
||||
clubMembership == null &&
|
||||
wallet != null &&
|
||||
basePackage != null &&
|
||||
wallet.Balance >= basePackage.Price;
|
||||
|
||||
return new UserPackageStatusDto
|
||||
{
|
||||
UserId = user.Id,
|
||||
PackagePurchaseMethod = user.PackagePurchaseMethod.ToString(),
|
||||
HasPurchasedPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None,
|
||||
IsClubMemberActive = clubMembership?.IsActive ?? false,
|
||||
WalletBalance = wallet?.Balance ?? 0,
|
||||
DiscountBalance = wallet?.DiscountBalance ?? 0,
|
||||
CanActivateClubMembership = canActivate,
|
||||
LastOrderNumber = lastPackageOrder?.Id.ToString(),
|
||||
LastPurchaseDate = lastPackageOrder?.Created
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -76,8 +76,17 @@ public class ChargeMagicWalletCommandHandler
|
||||
);
|
||||
}
|
||||
|
||||
// 3.5. بارگذاری پکیج کاربر برای سقف کیفپول جادویی
|
||||
var currentCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(c => c.UserId == request.UserId && c.IsCurrentCycle, cancellationToken);
|
||||
var package = currentCycle != null
|
||||
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, cancellationToken)
|
||||
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken);
|
||||
if (package == null)
|
||||
throw new NotFoundException("پکیج یافت نشد");
|
||||
|
||||
// 4. بررسی سقف واریزی (per-cycle)
|
||||
var remainingDeposit = SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited;
|
||||
var remainingDeposit = package.MagicWalletMaxDeposit - wallet.MagicTotalDeposited;
|
||||
|
||||
if (remainingDeposit <= 0)
|
||||
{
|
||||
|
||||
+14
-5
@@ -121,8 +121,17 @@ public class VerifyMagicWalletChargeCommandHandler
|
||||
throw new BadRequestException("کیفپول در حالت جادویی نیست");
|
||||
}
|
||||
|
||||
// 5. محاسبه اعتبار ×2.5
|
||||
var creditAmount = (long)(depositAmount * SystemConstants.MagicWalletMultiplier); // مبلغ × 2.5
|
||||
// 4.5. بارگذاری پکیج کاربر برای ضریب جادویی
|
||||
var currentCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, cancellationToken);
|
||||
var package = currentCycle != null
|
||||
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, cancellationToken)
|
||||
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken);
|
||||
if (package == null)
|
||||
throw new NotFoundException("پکیج یافت نشد");
|
||||
|
||||
// 5. محاسبه اعتبار
|
||||
var creditAmount = (long)(depositAmount * package.MagicWalletMultiplier); // مبلغ × 2.5
|
||||
var bonusAmount = creditAmount - depositAmount; // بونوس = مبلغ × 1.5
|
||||
|
||||
// 6. ثبت تراکنش واریز واقعی (MagicWalletDeposit)
|
||||
@@ -143,7 +152,7 @@ public class VerifyMagicWalletChargeCommandHandler
|
||||
var bonusTransaction = new Transaction
|
||||
{
|
||||
Amount = bonusAmount,
|
||||
Description = $"شارژ کیفپول جادویی - بونوس ×{SystemConstants.MagicWalletMultiplier - 1} - کاربر {userId}",
|
||||
Description = $"شارژ کیفپول جادویی - بونوس ×{package.MagicWalletMultiplier - 1} - کاربر {userId}",
|
||||
PaymentStatus = PaymentStatus.Success,
|
||||
PaymentDate = DateTime.UtcNow,
|
||||
RefId = $"MAGIC_BONUS_{depositTransaction.Id}",
|
||||
@@ -186,10 +195,10 @@ public class VerifyMagicWalletChargeCommandHandler
|
||||
userId,
|
||||
depositAmount,
|
||||
creditAmount,
|
||||
SystemConstants.MagicWalletMultiplier,
|
||||
package.MagicWalletMultiplier,
|
||||
bonusAmount,
|
||||
wallet.MagicTotalDeposited,
|
||||
SystemConstants.MagicWalletMaxDeposit,
|
||||
package.MagicWalletMaxDeposit,
|
||||
wallet.Balance
|
||||
);
|
||||
|
||||
|
||||
@@ -24,12 +24,16 @@ public static class SystemConstants
|
||||
|
||||
/// <summary>
|
||||
/// مبلغ هدیه حق عضویت باشگاه (ریال) - این مبلغ از کیف پول کم نمیشود
|
||||
/// [DEPRECATED] از Package.Price استفاده کنید
|
||||
/// </summary>
|
||||
[Obsolete("Moved to Package entity. Use package.Price or package.ActivationFee instead.")]
|
||||
public const long ClubMembershipGiftValue = 25_200_000;
|
||||
|
||||
/// <summary>
|
||||
/// هزینه فعالسازی عضویت باشگاه (ریال)
|
||||
/// [DEPRECATED] از Package.ActivationFee استفاده کنید
|
||||
/// </summary>
|
||||
[Obsolete("Moved to Package.ActivationFee. Read from Package entity instead.")]
|
||||
public const long ClubActivationFee = 25_200_000;
|
||||
|
||||
#endregion
|
||||
@@ -37,14 +41,17 @@ public static class SystemConstants
|
||||
#region Package Settings
|
||||
|
||||
/// <summary>
|
||||
/// مبلغ پکیج طلایی / پایه (ریال) - 56 میلیون تومان
|
||||
/// شامل: هدیه باشگاه + هزینه فعالسازی + مزایای دیگر
|
||||
/// مبلغ پکیج پایه (ریال)
|
||||
/// [DEPRECATED] از Package.Price استفاده کنید
|
||||
/// </summary>
|
||||
[Obsolete("Moved to Package.Price. Read from Package entity instead.")]
|
||||
public const long BasePackageAmount = 56_000_000;
|
||||
|
||||
/// <summary>
|
||||
/// مبلغ وام دایا (ریال) - همان مبلغ پکیج طلایی
|
||||
/// مبلغ وام دایا (ریال)
|
||||
/// [DEPRECATED] از Package.Price استفاده کنید
|
||||
/// </summary>
|
||||
[Obsolete("Moved to Package.Price. Read from Package entity with SupportsDayaPurchase=true.")]
|
||||
public const long DayaLoanAmount = 56_000_000;
|
||||
|
||||
#endregion
|
||||
@@ -63,12 +70,16 @@ public static class SystemConstants
|
||||
|
||||
/// <summary>
|
||||
/// سقف تعادل هفتگی برای هر دست (چپ یا راست) - حداکثر کل = 600
|
||||
/// [DEPRECATED] از Package.MaxBalancesPerLeg استفاده کنید
|
||||
/// </summary>
|
||||
[Obsolete("Moved to Package.MaxBalancesPerLeg. Read from Package entity instead.")]
|
||||
public const int CommissionMaxWeeklyBalancesPerLeg = 300;
|
||||
|
||||
/// <summary>
|
||||
/// حداکثر عمق شبکه برای محاسبه کمیسیون (تعداد لول زیرمجموعه)
|
||||
/// [DEPRECATED] از Package.MaxNetworkLevel استفاده کنید
|
||||
/// </summary>
|
||||
[Obsolete("Moved to Package.MaxNetworkLevel. Read from Package entity instead.")]
|
||||
public const int CommissionMaxNetworkLevel = 15;
|
||||
|
||||
/// <summary>
|
||||
@@ -96,17 +107,23 @@ public static class SystemConstants
|
||||
|
||||
/// <summary>
|
||||
/// ضریب شارژ کیفپول جادویی — واریز × 2.5 = اعتبار
|
||||
/// [DEPRECATED] از Package.MagicWalletMultiplier استفاده کنید
|
||||
/// </summary>
|
||||
[Obsolete("Moved to Package.MagicWalletMultiplier. Read from Package entity instead.")]
|
||||
public const decimal MagicWalletMultiplier = 2.5m;
|
||||
|
||||
/// <summary>
|
||||
/// سقف واریز در هر دور جادویی (ریال) — 100M تومان
|
||||
/// [DEPRECATED] از Package.MagicWalletMaxDeposit استفاده کنید
|
||||
/// </summary>
|
||||
[Obsolete("Moved to Package.MagicWalletMaxDeposit. Read from Package entity instead.")]
|
||||
public const long MagicWalletMaxDeposit = 1_000_000_000;
|
||||
|
||||
/// <summary>
|
||||
/// سقف اعتبار در هر دور جادویی (ریال) — 250M تومان
|
||||
/// [DEPRECATED] از Package.MagicWalletMaxCredit استفاده کنید
|
||||
/// </summary>
|
||||
[Obsolete("Moved to Package.MagicWalletMaxCredit. Read from Package entity instead.")]
|
||||
public const long MagicWalletMaxCredit = 2_500_000_000;
|
||||
|
||||
#endregion
|
||||
@@ -129,6 +146,8 @@ public static class SystemConstants
|
||||
|
||||
/// <summary>
|
||||
/// دریافت مقدار به صورت دیکشنری برای نمایش در Admin Panel
|
||||
/// Note: Per-package values (Price, MaxBalancesPerLeg, etc.) are now read from Package entity
|
||||
/// via ConfigurationService — only static/global constants remain here.
|
||||
/// </summary>
|
||||
public static Dictionary<string, object> GetAllAsDict()
|
||||
{
|
||||
@@ -138,26 +157,15 @@ public static class SystemConstants
|
||||
["Network.AllowOrphanNodes"] = NetworkAllowOrphanNodes,
|
||||
["Network.MaxChildrenPerLeg"] = NetworkMaxChildrenPerLeg,
|
||||
|
||||
// Club
|
||||
["Club.MembershipGiftValue"] = ClubMembershipGiftValue,
|
||||
["Club.ActivationFee"] = ClubActivationFee,
|
||||
|
||||
// Commission
|
||||
// Commission (global settings only — per-package limits are in Package entity)
|
||||
["Commission.CashWithdrawalEnabled"] = CommissionCashWithdrawalEnabled,
|
||||
["Commission.MinWithdrawalAmount"] = CommissionMinWithdrawalAmount,
|
||||
["Commission.MaxWeeklyBalancesPerLeg"] = CommissionMaxWeeklyBalancesPerLeg,
|
||||
["Commission.MaxNetworkLevel"] = CommissionMaxNetworkLevel,
|
||||
["Commission.CalculationStrategy"] = CommissionCalculationStrategy,
|
||||
|
||||
// System
|
||||
["System.MaintenanceMode"] = SystemMaintenanceMode,
|
||||
["System.EnableAuditLog"] = SystemEnableAuditLog,
|
||||
|
||||
// Magic Wallet
|
||||
["MagicWallet.Multiplier"] = MagicWalletMultiplier,
|
||||
["MagicWallet.MaxDeposit"] = MagicWalletMaxDeposit,
|
||||
["MagicWallet.MaxCredit"] = MagicWalletMaxCredit,
|
||||
|
||||
// Shop
|
||||
["Shop.VAT"] = ShopVAT,
|
||||
["Shop.VATEnabled"] = ShopVATEnabled
|
||||
@@ -166,6 +174,7 @@ public static class SystemConstants
|
||||
|
||||
/// <summary>
|
||||
/// دریافت لیست تنظیمات با توضیحات
|
||||
/// Note: Per-package values are served from Package entity via ConfigurationService.
|
||||
/// </summary>
|
||||
public static List<(string Key, object Value, string Description)> GetAllWithDescriptions()
|
||||
{
|
||||
@@ -175,26 +184,15 @@ public static class SystemConstants
|
||||
("Network.AllowOrphanNodes", NetworkAllowOrphanNodes, "اجازه حذف والدین که فرزند دارند"),
|
||||
("Network.MaxChildrenPerLeg", NetworkMaxChildrenPerLeg, "حداکثر تعداد فرزند مستقیم در هر پا"),
|
||||
|
||||
// Club
|
||||
("Club.MembershipGiftValue", ClubMembershipGiftValue, "مبلغ هدیه حق عضویت باشگاه (ریال)"),
|
||||
("Club.ActivationFee", ClubActivationFee, "هزینه فعالسازی عضویت باشگاه (ریال)"),
|
||||
|
||||
// Commission
|
||||
// Commission (global settings only)
|
||||
("Commission.CashWithdrawalEnabled", CommissionCashWithdrawalEnabled, "امکان برداشت نقدی فعال باشد"),
|
||||
("Commission.MinWithdrawalAmount", CommissionMinWithdrawalAmount, "حداقل مبلغ برداشت (ریال)"),
|
||||
("Commission.MaxWeeklyBalancesPerLeg", CommissionMaxWeeklyBalancesPerLeg, "سقف تعادل هفتگی برای هر دست"),
|
||||
("Commission.MaxNetworkLevel", CommissionMaxNetworkLevel, "حداکثر عمق شبکه برای محاسبه کمیسیون"),
|
||||
("Commission.CalculationStrategy", CommissionCalculationStrategy, "روش محاسبه (ORM/SP)"),
|
||||
|
||||
// System
|
||||
("System.MaintenanceMode", SystemMaintenanceMode, "حالت تعمیر و نگهداری سیستم"),
|
||||
("System.EnableAuditLog", SystemEnableAuditLog, "فعالسازی لاگ تغییرات"),
|
||||
|
||||
// Magic Wallet
|
||||
("MagicWallet.Multiplier", MagicWalletMultiplier, "ضریب شارژ کیفپول جادویی (×2.5)"),
|
||||
("MagicWallet.MaxDeposit", MagicWalletMaxDeposit, "سقف واریز هر دور جادویی (ریال)"),
|
||||
("MagicWallet.MaxCredit", MagicWalletMaxCredit, "سقف اعتبار هر دور جادویی (ریال)"),
|
||||
|
||||
// Shop
|
||||
("Shop.VAT", ShopVAT, "مالیات بر ارزش افزوده"),
|
||||
("Shop.VATEnabled", ShopVATEnabled, "مالیات فعال است؟")
|
||||
|
||||
@@ -20,9 +20,45 @@ public class ClubMembership : BaseAuditableEntity
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
// === v5: First/Last Activation Tracking (Q21) ===
|
||||
|
||||
/// <summary>
|
||||
/// تاریخ فعالسازی عضویت
|
||||
/// اولین فعالسازی — فقط یک بار ست میشود، هیچوقت overwrite نمیشود
|
||||
/// </summary>
|
||||
public DateTime? FirstActivationDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// اولین پکیج خریداریشده — فقط یک بار ست میشود
|
||||
/// </summary>
|
||||
public long? FirstPackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// FirstPackage Navigation Property
|
||||
/// </summary>
|
||||
public virtual Package? FirstPackage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// آخرین/جاری فعالسازی — هر خرید مجدد بروزرسانی میشود
|
||||
/// مبنای تشخیص "فعالشدگان این هفته" (Q22)
|
||||
/// </summary>
|
||||
public DateTime? LastActivationDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// آخرین/جاری پکیج — هر خرید مجدد بروزرسانی میشود
|
||||
/// مبنای carryover per-package (Q23)
|
||||
/// </summary>
|
||||
public long? LastPackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// LastPackage Navigation Property
|
||||
/// </summary>
|
||||
public virtual Package? LastPackage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// [DEPRECATED] تاریخ فعالسازی — جایگزین با FirstActivationDate + LastActivationDate
|
||||
/// حفظ موقت برای backward compatibility
|
||||
/// </summary>
|
||||
[Obsolete("Use FirstActivationDate / LastActivationDate instead")]
|
||||
public DateTime? ActivatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -57,6 +57,16 @@ public class ClubMembershipCycle : BaseAuditableEntity
|
||||
/// </summary>
|
||||
public long PackageAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پکیج خریداریشده در این دور
|
||||
/// </summary>
|
||||
public long PackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Package Navigation Property
|
||||
/// </summary>
|
||||
public virtual Package Package { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// آیا این دور فعلی است؟ فقط یک رکورد true میباشد
|
||||
/// </summary>
|
||||
|
||||
@@ -36,6 +36,16 @@ public class UserCommissionPayout : BaseAuditableEntity
|
||||
/// </summary>
|
||||
public virtual WeeklyCommissionPool WeeklyPool { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پکیج (کمیسیون هر پکیج جداگانه)
|
||||
/// </summary>
|
||||
public long PackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Package Navigation Property
|
||||
/// </summary>
|
||||
public virtual Package Package { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تعداد امتیازی که کاربر داشت
|
||||
/// </summary>
|
||||
|
||||
@@ -16,6 +16,16 @@ public class WeeklyCommissionPool : BaseAuditableEntity
|
||||
/// </summary>
|
||||
public virtual WeekDefinition WeekDefinition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پکیج (استخر هر پکیج جداگانه)
|
||||
/// </summary>
|
||||
public long PackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Package Navigation Property
|
||||
/// </summary>
|
||||
public virtual Package Package { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// مجموع مبلغ جمعشده در استخر (ریال)
|
||||
/// </summary>
|
||||
|
||||
@@ -25,6 +25,16 @@ public class NetworkWeeklyBalance : BaseAuditableEntity
|
||||
/// </summary>
|
||||
public virtual WeekDefinition WeekDefinition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پکیج (تعادل هر پکیج جداگانه)
|
||||
/// </summary>
|
||||
public long PackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Package Navigation Property
|
||||
/// </summary>
|
||||
public virtual Package Package { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تعداد اعضای جدید شاخه چپ در این هفته
|
||||
/// </summary>
|
||||
|
||||
@@ -1,15 +1,76 @@
|
||||
namespace CMSMicroservice.Domain.Entities;
|
||||
//پکیج
|
||||
|
||||
/// <summary>
|
||||
/// پکیج — هر پکیج قیمت، ویژگیها و تنظیمات مستقل دارد
|
||||
/// </summary>
|
||||
public class Package : BaseAuditableEntity
|
||||
{
|
||||
//عنوان
|
||||
// === فیلدهای فعلی (حفظ) ===
|
||||
|
||||
/// <summary>عنوان پکیج</summary>
|
||||
public string Title { get; set; }
|
||||
//توضیحات
|
||||
|
||||
/// <summary>توضیحات</summary>
|
||||
public string Description { get; set; }
|
||||
//آدرس تصویر
|
||||
|
||||
/// <summary>آدرس تصویر</summary>
|
||||
public string ImagePath { get; set; }
|
||||
//قیمت
|
||||
|
||||
/// <summary>قیمت پکیج (ریال)</summary>
|
||||
public long Price { get; set; }
|
||||
//UserOrder Collection Navigation Reference
|
||||
|
||||
// === فیلدهای جدید v3 ===
|
||||
|
||||
/// <summary>ترتیب نمایش</summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>فعال/غیرفعال</summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>پکیج پایه؟ (فقط یکی true)</summary>
|
||||
public bool IsBasePackage { get; set; }
|
||||
|
||||
/// <summary>پشتیبانی از خرید دایا</summary>
|
||||
public bool SupportsDayaPurchase { get; set; }
|
||||
|
||||
/// <summary>پشتیبانی از پرداخت مستقیم</summary>
|
||||
public bool SupportsDirectPurchase { get; set; } = true;
|
||||
|
||||
// === محاسبات مالی ===
|
||||
|
||||
/// <summary>سهم Commission Pool (ریال) — معمولاً Price × 0.45</summary>
|
||||
public long ActivationFee { get; set; }
|
||||
|
||||
/// <summary>ضریب شارژ DiscountBalance — فعلاً ×2 برای همه</summary>
|
||||
public decimal DiscountMultiplier { get; set; } = 2.0m;
|
||||
|
||||
/// <summary>ضریب کیفپول جادویی — واریز × این مقدار = اعتبار</summary>
|
||||
public decimal MagicWalletMultiplier { get; set; } = 2.5m;
|
||||
|
||||
// === تنظیمات پورسانت ===
|
||||
|
||||
/// <summary>سقف تعادل هر پا (نقرهای=۳۰، پایه=۳۰۰)</summary>
|
||||
public int MaxBalancesPerLeg { get; set; } = 300;
|
||||
|
||||
/// <summary>عمق شبکه برای محاسبه کمیسیون</summary>
|
||||
public int MaxNetworkLevel { get; set; } = 15;
|
||||
|
||||
// === تنظیمات کیفپول جادویی ===
|
||||
|
||||
/// <summary>سقف شارژ هر دور جادویی (ریال)</summary>
|
||||
public long MagicWalletMaxDeposit { get; set; } = 1_000_000_000;
|
||||
|
||||
/// <summary>سقف اعتبار هر دور جادویی (ریال)</summary>
|
||||
public long MagicWalletMaxCredit { get; set; } = 2_500_000_000;
|
||||
|
||||
// === Navigation Properties ===
|
||||
|
||||
/// <summary>فیچرهای این پکیج</summary>
|
||||
public virtual ICollection<PackageFeature>? PackageFeatures { get; set; }
|
||||
|
||||
/// <summary>سفارشات مرتبط</summary>
|
||||
public virtual ICollection<UserOrder> UserOrders { get; set; }
|
||||
|
||||
/// <summary>خریدهای پکیج</summary>
|
||||
public virtual ICollection<UserPackagePurchase>? Purchases { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using CMSMicroservice.Domain.Entities.Club;
|
||||
|
||||
namespace CMSMicroservice.Domain.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// ارتباط پکیج–فیچر — هر پکیج مجموعه فیچرهای خودش را دارد
|
||||
/// </summary>
|
||||
public class PackageFeature : BaseAuditableEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه پکیج
|
||||
/// </summary>
|
||||
public long PackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Package Navigation Property
|
||||
/// </summary>
|
||||
public virtual Package Package { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه فیچر باشگاه
|
||||
/// </summary>
|
||||
public long ClubFeatureId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ClubFeature Navigation Property
|
||||
/// </summary>
|
||||
public virtual ClubFeature ClubFeature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// آیا این فیچر در پکیج فعال است
|
||||
/// </summary>
|
||||
public bool IsIncluded { get; set; } = true;
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class User : BaseAuditableEntity
|
||||
public DateTime? DayaCreditReceivedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نحوه خرید پکیج طلایی (برای جلوگیری از خرید مجدد)
|
||||
/// نحوه خرید پکیج (برای جلوگیری از خرید مجدد)
|
||||
/// </summary>
|
||||
public PackagePurchaseMethod PackagePurchaseMethod { get; set; } = PackagePurchaseMethod.None;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ public class UserWallet : BaseAuditableEntity
|
||||
public long Balance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// موجودی شبکه/کارمزد (کیف پول طلایی) - قابل برداشت نقدی یا خرید الماس
|
||||
/// موجودی شبکه/کارمزد — قابل برداشت نقدی یا خرید الماس
|
||||
/// </summary>
|
||||
public long NetworkBalance { get; set; }
|
||||
|
||||
|
||||
@@ -22,4 +22,14 @@ public class UserWalletChangeLog : BaseAuditableEntity
|
||||
public bool IsIncrease { get; set; }
|
||||
//شناسه ارجاع
|
||||
public long? RefrenceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پکیج مرتبط (nullable — برای تغییرات کیف پول که مرتبط با پکیج هستند)
|
||||
/// </summary>
|
||||
public long? PackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Package Navigation Property
|
||||
/// </summary>
|
||||
public virtual Package? Package { get; set; }
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public enum CommissionPayoutStatus
|
||||
Pending = 0,
|
||||
|
||||
/// <summary>
|
||||
/// واریز شده به کیف پول طلایی
|
||||
/// واریز شده به کیف پول
|
||||
/// </summary>
|
||||
Paid = 1,
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace CMSMicroservice.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// نحوه خرید پکیج طلایی توسط کاربر
|
||||
/// نحوه خرید پکیج توسط کاربر
|
||||
/// </summary>
|
||||
public enum PackagePurchaseMethod
|
||||
{
|
||||
|
||||
@@ -110,6 +110,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext
|
||||
public DbSet<UserClubFeature> UserClubFeatures => Set<UserClubFeature>();
|
||||
public DbSet<ClubMembershipHistory> ClubMembershipHistories => Set<ClubMembershipHistory>();
|
||||
public DbSet<ClubMembershipCycle> ClubMembershipCycles => Set<ClubMembershipCycle>();
|
||||
public DbSet<PackageFeature> PackageFeatures => Set<PackageFeature>();
|
||||
|
||||
// Network
|
||||
public DbSet<NetworkWeeklyBalance> NetworkWeeklyBalances => Set<NetworkWeeklyBalance>();
|
||||
|
||||
+24
@@ -23,12 +23,32 @@ public class ClubMembershipConfiguration : IEntityTypeConfiguration<ClubMembersh
|
||||
builder.Property(entity => entity.GiftValue).IsRequired();
|
||||
builder.Property(entity => entity.TotalEarned).IsRequired();
|
||||
|
||||
// فیلدهای جدید First/Last Activation
|
||||
builder.Property(entity => entity.FirstActivationDate).IsRequired(false);
|
||||
builder.Property(entity => entity.FirstPackageId).IsRequired(false);
|
||||
builder.Property(entity => entity.LastActivationDate).IsRequired(false);
|
||||
builder.Property(entity => entity.LastPackageId).IsRequired(false);
|
||||
|
||||
// رابطه یکبهیک با User
|
||||
builder.HasOne(entity => entity.User)
|
||||
.WithOne(u => u.ClubMembership)
|
||||
.HasForeignKey<ClubMembership>(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// رابطه با Package — اولین پکیج خریداریشده
|
||||
builder.HasOne(entity => entity.FirstPackage)
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.FirstPackageId)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// رابطه با Package — آخرین پکیج خریداریشده
|
||||
builder.HasOne(entity => entity.LastPackage)
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.LastPackageId)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Index برای UserId (یونیک برای یکبهیک)
|
||||
builder.HasIndex(e => e.UserId)
|
||||
.IsUnique()
|
||||
@@ -37,5 +57,9 @@ public class ClubMembershipConfiguration : IEntityTypeConfiguration<ClubMembersh
|
||||
// Index برای IsActive
|
||||
builder.HasIndex(e => e.IsActive)
|
||||
.HasDatabaseName("IX_ClubMembership_IsActive");
|
||||
|
||||
// Index برای LastActivationDate (تشخیص فعالسازی هفتگی Q22)
|
||||
builder.HasIndex(e => e.LastActivationDate)
|
||||
.HasDatabaseName("IX_ClubMembership_LastActivationDate");
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -21,6 +21,7 @@ public class ClubMembershipCycleConfiguration : IEntityTypeConfiguration<ClubMem
|
||||
builder.Property(entity => entity.MagicCompletedAt).IsRequired(false);
|
||||
builder.Property(entity => entity.PurchaseMethod).IsRequired();
|
||||
builder.Property(entity => entity.PackageAmount).IsRequired();
|
||||
builder.Property(entity => entity.PackageId).IsRequired();
|
||||
builder.Property(entity => entity.IsCurrentCycle)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
@@ -36,6 +37,11 @@ public class ClubMembershipCycleConfiguration : IEntityTypeConfiguration<ClubMem
|
||||
.HasForeignKey(entity => entity.ClubMembershipId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(entity => entity.Package)
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.PackageId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Indexes
|
||||
builder.HasIndex(e => new { e.UserId, e.IsCurrentCycle })
|
||||
.HasDatabaseName("IX_ClubMembershipCycle_UserId_IsCurrentCycle");
|
||||
|
||||
+10
-3
@@ -18,6 +18,7 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration<Networ
|
||||
|
||||
builder.Property(entity => entity.UserId).IsRequired();
|
||||
builder.Property(entity => entity.WeekDefinitionId).IsRequired();
|
||||
builder.Property(entity => entity.PackageId).IsRequired();
|
||||
builder.Property(entity => entity.LeftLegBalances).IsRequired();
|
||||
builder.Property(entity => entity.RightLegBalances).IsRequired();
|
||||
builder.Property(entity => entity.TotalBalances).IsRequired();
|
||||
@@ -37,10 +38,16 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration<Networ
|
||||
.HasForeignKey(entity => entity.WeekDefinitionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Composite Index برای UserId و WeekDefinitionId
|
||||
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId })
|
||||
// رابطه با Package
|
||||
builder.HasOne(entity => entity.Package)
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.PackageId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Composite Index: هر کاربر × هر هفته × هر پکیج فقط یک رکورد
|
||||
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId, e.PackageId })
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId");
|
||||
.HasDatabaseName("IX_NetworkWeeklyBalance_User_WeekDef_Package");
|
||||
|
||||
// Index برای WeekDefinitionId
|
||||
builder.HasIndex(e => e.WeekDefinitionId)
|
||||
|
||||
@@ -16,5 +16,27 @@ public class PackageConfiguration : IEntityTypeConfiguration<Package>
|
||||
builder.Property(entity => entity.ImagePath).IsRequired(true);
|
||||
builder.Property(entity => entity.Price).IsRequired(true);
|
||||
|
||||
// فیلدهای جدید پکیج
|
||||
builder.Property(entity => entity.SortOrder).IsRequired().HasDefaultValue(0);
|
||||
builder.Property(entity => entity.IsActive).IsRequired().HasDefaultValue(true);
|
||||
builder.Property(entity => entity.IsBasePackage).IsRequired().HasDefaultValue(false);
|
||||
builder.Property(entity => entity.SupportsDayaPurchase).IsRequired().HasDefaultValue(false);
|
||||
builder.Property(entity => entity.SupportsDirectPurchase).IsRequired().HasDefaultValue(true);
|
||||
builder.Property(entity => entity.ActivationFee).IsRequired().HasDefaultValue(0L);
|
||||
builder.Property(entity => entity.DiscountMultiplier).IsRequired().HasDefaultValue(2.0m)
|
||||
.HasPrecision(18, 4);
|
||||
builder.Property(entity => entity.MagicWalletMultiplier).IsRequired().HasDefaultValue(2.5m)
|
||||
.HasPrecision(18, 4);
|
||||
builder.Property(entity => entity.MaxBalancesPerLeg).IsRequired().HasDefaultValue(300);
|
||||
builder.Property(entity => entity.MaxNetworkLevel).IsRequired().HasDefaultValue(15);
|
||||
builder.Property(entity => entity.MagicWalletMaxDeposit).IsRequired().HasDefaultValue(1_000_000_000L);
|
||||
builder.Property(entity => entity.MagicWalletMaxCredit).IsRequired().HasDefaultValue(2_500_000_000L);
|
||||
|
||||
// Indexes
|
||||
builder.HasIndex(e => e.IsActive)
|
||||
.HasDatabaseName("IX_Package_IsActive");
|
||||
builder.HasIndex(e => e.SortOrder)
|
||||
.HasDatabaseName("IX_Package_SortOrder");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Persistence.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// EF Configuration — ارتباط پکیج–فیچر
|
||||
/// </summary>
|
||||
public class PackageFeatureConfiguration : IEntityTypeConfiguration<PackageFeature>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PackageFeature> builder)
|
||||
{
|
||||
builder.HasQueryFilter(p => !p.IsDeleted);
|
||||
builder.Ignore(entity => entity.DomainEvents);
|
||||
|
||||
builder.HasKey(entity => entity.Id);
|
||||
builder.Property(entity => entity.Id).UseIdentityColumn();
|
||||
|
||||
builder.Property(entity => entity.PackageId).IsRequired();
|
||||
builder.Property(entity => entity.ClubFeatureId).IsRequired();
|
||||
builder.Property(entity => entity.IsIncluded).IsRequired().HasDefaultValue(true);
|
||||
|
||||
// رابطه با Package
|
||||
builder.HasOne(entity => entity.Package)
|
||||
.WithMany(p => p.PackageFeatures)
|
||||
.HasForeignKey(entity => entity.PackageId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// رابطه با ClubFeature
|
||||
builder.HasOne(entity => entity.ClubFeature)
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.ClubFeatureId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Unique: هر فیچر فقط یک بار در هر پکیج
|
||||
builder.HasIndex(e => new { e.PackageId, e.ClubFeatureId })
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_PackageFeature_PackageId_ClubFeatureId");
|
||||
}
|
||||
}
|
||||
+10
-3
@@ -19,6 +19,7 @@ public class UserCommissionPayoutConfiguration : IEntityTypeConfiguration<UserCo
|
||||
builder.Property(entity => entity.UserId).IsRequired();
|
||||
builder.Property(entity => entity.WeekDefinitionId).IsRequired();
|
||||
builder.Property(entity => entity.WeeklyPoolId).IsRequired();
|
||||
builder.Property(entity => entity.PackageId).IsRequired();
|
||||
builder.Property(entity => entity.BalancesEarned).IsRequired();
|
||||
builder.Property(entity => entity.ValuePerBalance).IsRequired();
|
||||
builder.Property(entity => entity.TotalAmount).IsRequired();
|
||||
@@ -50,10 +51,16 @@ public class UserCommissionPayoutConfiguration : IEntityTypeConfiguration<UserCo
|
||||
.IsRequired()
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Composite Index برای UserId و WeekDefinitionId
|
||||
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId })
|
||||
// رابطه با Package
|
||||
builder.HasOne(entity => entity.Package)
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.PackageId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Composite Index برای UserId، WeekDefinitionId و PackageId
|
||||
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId, e.PackageId })
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId");
|
||||
.HasDatabaseName("IX_UserCommissionPayout_User_WeekDef_Package");
|
||||
|
||||
// Index برای WeeklyPoolId
|
||||
builder.HasIndex(e => e.WeeklyPoolId)
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ public class UserPackagePurchaseConfiguration : IEntityTypeConfiguration<UserPac
|
||||
|
||||
// رابطه با Package
|
||||
builder.HasOne(entity => entity.Package)
|
||||
.WithMany()
|
||||
.WithMany(p => p.Purchases)
|
||||
.HasForeignKey(entity => entity.PackageId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
|
||||
+8
@@ -22,6 +22,14 @@ public class UserWalletChangeLogConfiguration : IEntityTypeConfiguration<UserWal
|
||||
builder.Property(entity => entity.ChangeNerworkValue).IsRequired(true);
|
||||
builder.Property(entity => entity.IsIncrease).IsRequired(true);
|
||||
builder.Property(entity => entity.RefrenceId).IsRequired(false);
|
||||
builder.Property(entity => entity.PackageId).IsRequired(false);
|
||||
|
||||
// رابطه با Package (nullable)
|
||||
builder.HasOne(entity => entity.Package)
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.PackageId)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -17,6 +17,7 @@ public class WeeklyCommissionPoolConfiguration : IEntityTypeConfiguration<Weekly
|
||||
builder.Property(entity => entity.Id).UseIdentityColumn();
|
||||
|
||||
builder.Property(entity => entity.WeekDefinitionId).IsRequired();
|
||||
builder.Property(entity => entity.PackageId).IsRequired();
|
||||
builder.Property(entity => entity.TotalPoolAmount).IsRequired();
|
||||
builder.Property(entity => entity.TotalBalances).IsRequired();
|
||||
builder.Property(entity => entity.ValuePerBalance).IsRequired();
|
||||
@@ -30,10 +31,16 @@ public class WeeklyCommissionPoolConfiguration : IEntityTypeConfiguration<Weekly
|
||||
.IsRequired()
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Index یونیک برای WeekDefinitionId
|
||||
builder.HasIndex(e => e.WeekDefinitionId)
|
||||
// رابطه با Package
|
||||
builder.HasOne(entity => entity.Package)
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.PackageId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Index یونیک ترکیبی: هر هفته × هر پکیج فقط یک استخر
|
||||
builder.HasIndex(e => new { e.WeekDefinitionId, e.PackageId })
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId");
|
||||
.HasDatabaseName("IX_WeeklyCommissionPool_WeekDef_Package");
|
||||
|
||||
// Index برای IsCalculated
|
||||
builder.HasIndex(e => e.IsCalculated)
|
||||
|
||||
+5013
File diff suppressed because it is too large
Load Diff
+683
@@ -0,0 +1,683 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPackageBasedSystem : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_WeeklyCommissionPool_WeekDefinitionId",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserCommissionPayout_UserId_WeekDefinitionId",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_NetworkWeeklyBalance_UserId_WeekDefinitionId",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserWalletChangeLogs",
|
||||
type: "bigint",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "ActivationFee",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "DiscountMultiplier",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "decimal(18,4)",
|
||||
precision: 18,
|
||||
scale: 4,
|
||||
nullable: false,
|
||||
defaultValue: 2.0m);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsActive",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsBasePackage",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "MagicWalletMaxCredit",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 2500000000L);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "MagicWalletMaxDeposit",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 1000000000L);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MagicWalletMultiplier",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "decimal(18,4)",
|
||||
precision: 18,
|
||||
scale: 4,
|
||||
nullable: false,
|
||||
defaultValue: 2.5m);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "MaxBalancesPerLeg",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 300);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "MaxNetworkLevel",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 15);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SortOrder",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "SupportsDayaPurchase",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "SupportsDirectPurchase",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "FirstActivationDate",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships",
|
||||
type: "datetime2",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "FirstPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships",
|
||||
type: "bigint",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastActivationDate",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships",
|
||||
type: "datetime2",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "LastPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships",
|
||||
type: "bigint",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMembershipCycles",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PackageFeatures",
|
||||
schema: "CMS",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
PackageId = table.Column<long>(type: "bigint", nullable: false),
|
||||
ClubFeatureId = table.Column<long>(type: "bigint", nullable: false),
|
||||
IsIncluded = table.Column<bool>(type: "bit", nullable: false, defaultValue: true),
|
||||
Created = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
LastModified = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifiedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PackageFeatures", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PackageFeatures_ClubFeatures_ClubFeatureId",
|
||||
column: x => x.ClubFeatureId,
|
||||
principalSchema: "CMS",
|
||||
principalTable: "ClubFeatures",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_PackageFeatures_Packages_PackageId",
|
||||
column: x => x.PackageId,
|
||||
principalSchema: "CMS",
|
||||
principalTable: "Packages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WeeklyCommissionPool_WeekDef_Package",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools",
|
||||
columns: new[] { "WeekDefinitionId", "PackageId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WeeklyCommissionPools_PackageId",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools",
|
||||
column: "PackageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserWalletChangeLogs_PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserWalletChangeLogs",
|
||||
column: "PackageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserCommissionPayout_User_WeekDef_Package",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts",
|
||||
columns: new[] { "UserId", "WeekDefinitionId", "PackageId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserCommissionPayouts_PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts",
|
||||
column: "PackageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Package_IsActive",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
column: "IsActive");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Package_SortOrder",
|
||||
schema: "CMS",
|
||||
table: "Packages",
|
||||
column: "SortOrder");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NetworkWeeklyBalance_User_WeekDef_Package",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances",
|
||||
columns: new[] { "UserId", "WeekDefinitionId", "PackageId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NetworkWeeklyBalances_PackageId",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances",
|
||||
column: "PackageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClubMembership_LastActivationDate",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships",
|
||||
column: "LastActivationDate");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClubMemberships_FirstPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships",
|
||||
column: "FirstPackageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClubMemberships_LastPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships",
|
||||
column: "LastPackageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClubMembershipCycles_PackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMembershipCycles",
|
||||
column: "PackageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PackageFeature_PackageId_ClubFeatureId",
|
||||
schema: "CMS",
|
||||
table: "PackageFeatures",
|
||||
columns: new[] { "PackageId", "ClubFeatureId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PackageFeatures_ClubFeatureId",
|
||||
schema: "CMS",
|
||||
table: "PackageFeatures",
|
||||
column: "ClubFeatureId");
|
||||
|
||||
// === DATA MIGRATION: Seed golden package and backfill existing records ===
|
||||
|
||||
// 1. Ensure golden package exists with known Id
|
||||
// Use IDENTITY_INSERT to guarantee Id=1 (if Package table uses identity)
|
||||
migrationBuilder.Sql(@"
|
||||
SET IDENTITY_INSERT [CMS].[Packages] ON;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM [CMS].[Packages] WHERE [Id] = 1)
|
||||
BEGIN
|
||||
INSERT INTO [CMS].[Packages]
|
||||
([Id], [Title], [Description], [ImagePath], [Price],
|
||||
[SortOrder], [IsActive], [IsBasePackage],
|
||||
[SupportsDayaPurchase], [SupportsDirectPurchase],
|
||||
[ActivationFee], [DiscountMultiplier], [MagicWalletMultiplier],
|
||||
[MaxBalancesPerLeg], [MaxNetworkLevel],
|
||||
[MagicWalletMaxDeposit], [MagicWalletMaxCredit],
|
||||
[Created], [IsDeleted])
|
||||
VALUES
|
||||
(1, N'پکیج طلایی', N'پکیج اصلی باشگاه مشتریان کارا بازار سلامت',
|
||||
N'/images/packages/golden.png', 56000000,
|
||||
1, 1, 1,
|
||||
1, 1,
|
||||
25200000, 2.0, 2.5,
|
||||
300, 15,
|
||||
1000000000, 2500000000,
|
||||
GETUTCDATE(), 0);
|
||||
END
|
||||
|
||||
SET IDENTITY_INSERT [CMS].[Packages] OFF;
|
||||
");
|
||||
|
||||
// 2. Backfill PackageId on existing records → point to golden package (Id=1)
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE [CMS].[WeeklyCommissionPools] SET [PackageId] = 1 WHERE [PackageId] = 0;
|
||||
UPDATE [CMS].[UserCommissionPayouts] SET [PackageId] = 1 WHERE [PackageId] = 0;
|
||||
UPDATE [CMS].[NetworkWeeklyBalances] SET [PackageId] = 1 WHERE [PackageId] = 0;
|
||||
UPDATE [CMS].[ClubMembershipCycles] SET [PackageId] = 1 WHERE [PackageId] = 0;
|
||||
");
|
||||
|
||||
// 3. Backfill ClubMembership First/Last fields from existing ActivatedAt
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE cm SET
|
||||
cm.[FirstActivationDate] = cm.[ActivatedAt],
|
||||
cm.[LastActivationDate] = cm.[ActivatedAt],
|
||||
cm.[FirstPackageId] = 1,
|
||||
cm.[LastPackageId] = 1
|
||||
FROM [CMS].[ClubMemberships] cm
|
||||
WHERE cm.[ActivatedAt] IS NOT NULL
|
||||
AND cm.[FirstActivationDate] IS NULL;
|
||||
");
|
||||
|
||||
// === END DATA MIGRATION ===
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ClubMembershipCycles_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMembershipCycles",
|
||||
column: "PackageId",
|
||||
principalSchema: "CMS",
|
||||
principalTable: "Packages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ClubMemberships_Packages_FirstPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships",
|
||||
column: "FirstPackageId",
|
||||
principalSchema: "CMS",
|
||||
principalTable: "Packages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ClubMemberships_Packages_LastPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships",
|
||||
column: "LastPackageId",
|
||||
principalSchema: "CMS",
|
||||
principalTable: "Packages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_NetworkWeeklyBalances_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances",
|
||||
column: "PackageId",
|
||||
principalSchema: "CMS",
|
||||
principalTable: "Packages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_UserCommissionPayouts_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts",
|
||||
column: "PackageId",
|
||||
principalSchema: "CMS",
|
||||
principalTable: "Packages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_UserWalletChangeLogs_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserWalletChangeLogs",
|
||||
column: "PackageId",
|
||||
principalSchema: "CMS",
|
||||
principalTable: "Packages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_WeeklyCommissionPools_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools",
|
||||
column: "PackageId",
|
||||
principalSchema: "CMS",
|
||||
principalTable: "Packages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ClubMembershipCycles_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMembershipCycles");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ClubMemberships_Packages_FirstPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ClubMemberships_Packages_LastPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_NetworkWeeklyBalances_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_UserCommissionPayouts_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_UserWalletChangeLogs_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserWalletChangeLogs");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_WeeklyCommissionPools_Packages_PackageId",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PackageFeatures",
|
||||
schema: "CMS");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_WeeklyCommissionPool_WeekDef_Package",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_WeeklyCommissionPools_PackageId",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserWalletChangeLogs_PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserWalletChangeLogs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserCommissionPayout_User_WeekDef_Package",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserCommissionPayouts_PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Package_IsActive",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Package_SortOrder",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_NetworkWeeklyBalance_User_WeekDef_Package",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_NetworkWeeklyBalances_PackageId",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ClubMembership_LastActivationDate",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ClubMemberships_FirstPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ClubMemberships_LastPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ClubMembershipCycles_PackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMembershipCycles");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserWalletChangeLogs");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ActivationFee",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DiscountMultiplier",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsActive",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsBasePackage",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MagicWalletMaxCredit",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MagicWalletMaxDeposit",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MagicWalletMultiplier",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxBalancesPerLeg",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxNetworkLevel",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SortOrder",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SupportsDayaPurchase",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SupportsDirectPurchase",
|
||||
schema: "CMS",
|
||||
table: "Packages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FirstActivationDate",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FirstPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastActivationDate",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastPackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMemberships");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PackageId",
|
||||
schema: "CMS",
|
||||
table: "ClubMembershipCycles");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WeeklyCommissionPool_WeekDefinitionId",
|
||||
schema: "CMS",
|
||||
table: "WeeklyCommissionPools",
|
||||
column: "WeekDefinitionId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserCommissionPayout_UserId_WeekDefinitionId",
|
||||
schema: "CMS",
|
||||
table: "UserCommissionPayouts",
|
||||
columns: new[] { "UserId", "WeekDefinitionId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NetworkWeeklyBalance_UserId_WeekDefinitionId",
|
||||
schema: "CMS",
|
||||
table: "NetworkWeeklyBalances",
|
||||
columns: new[] { "UserId", "WeekDefinitionId" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+240
-7
@@ -400,6 +400,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("FirstActivationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long?>("FirstPackageId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("GiftValue")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
@@ -412,12 +418,18 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastActivationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long?>("LastPackageId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("PurchaseMethod")
|
||||
.HasColumnType("int");
|
||||
|
||||
@@ -429,9 +441,16 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FirstPackageId");
|
||||
|
||||
b.HasIndex("IsActive")
|
||||
.HasDatabaseName("IX_ClubMembership_IsActive");
|
||||
|
||||
b.HasIndex("LastActivationDate")
|
||||
.HasDatabaseName("IX_ClubMembership_LastActivationDate");
|
||||
|
||||
b.HasIndex("LastPackageId");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_ClubMembership_UserId");
|
||||
@@ -482,6 +501,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Property<long>("PackageAmount")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("PackageId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime>("PackagePurchasedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
@@ -495,6 +517,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("ClubMembershipId");
|
||||
|
||||
b.HasIndex("PackageId");
|
||||
|
||||
b.HasIndex("PackagePurchasedAt")
|
||||
.HasDatabaseName("IX_ClubMembershipCycle_PackagePurchasedAt");
|
||||
|
||||
@@ -598,6 +622,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("PackageId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime?>("PaidAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
@@ -641,6 +668,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PackageId");
|
||||
|
||||
b.HasIndex("Status")
|
||||
.HasDatabaseName("IX_UserCommissionPayout_Status");
|
||||
|
||||
@@ -649,9 +678,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("WeeklyPoolId")
|
||||
.HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId");
|
||||
|
||||
b.HasIndex("UserId", "WeekDefinitionId")
|
||||
b.HasIndex("UserId", "WeekDefinitionId", "PackageId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId");
|
||||
.HasDatabaseName("IX_UserCommissionPayout_User_WeekDef_Package");
|
||||
|
||||
b.ToTable("UserCommissionPayouts", "CMS");
|
||||
});
|
||||
@@ -685,6 +714,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("PackageId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("TotalBalances")
|
||||
.HasColumnType("int");
|
||||
|
||||
@@ -702,9 +734,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("IsCalculated")
|
||||
.HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated");
|
||||
|
||||
b.HasIndex("WeekDefinitionId")
|
||||
b.HasIndex("PackageId");
|
||||
|
||||
b.HasIndex("WeekDefinitionId", "PackageId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId");
|
||||
.HasDatabaseName("IX_WeeklyCommissionPool_WeekDef_Package");
|
||||
|
||||
b.ToTable("WeeklyCommissionPools", "CMS");
|
||||
});
|
||||
@@ -2242,6 +2276,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Property<int>("LeftLegTotal")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("PackageId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("RightLegBalances")
|
||||
.HasColumnType("int");
|
||||
|
||||
@@ -2280,12 +2317,14 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("IsExpired")
|
||||
.HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired");
|
||||
|
||||
b.HasIndex("PackageId");
|
||||
|
||||
b.HasIndex("WeekDefinitionId")
|
||||
.HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId");
|
||||
|
||||
b.HasIndex("UserId", "WeekDefinitionId")
|
||||
b.HasIndex("UserId", "WeekDefinitionId", "PackageId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId");
|
||||
.HasDatabaseName("IX_NetworkWeeklyBalance_User_WeekDef_Package");
|
||||
|
||||
b.ToTable("NetworkWeeklyBalances", "CMS");
|
||||
});
|
||||
@@ -2412,6 +2451,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<long>("ActivationFee")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasDefaultValue(0L);
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
@@ -2422,10 +2466,26 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<decimal>("DiscountMultiplier")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)")
|
||||
.HasDefaultValue(2.0m);
|
||||
|
||||
b.Property<string>("ImagePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("IsBasePackage")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
@@ -2435,18 +2495,110 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("MagicWalletMaxCredit")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasDefaultValue(2500000000L);
|
||||
|
||||
b.Property<long>("MagicWalletMaxDeposit")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasDefaultValue(1000000000L);
|
||||
|
||||
b.Property<decimal>("MagicWalletMultiplier")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)")
|
||||
.HasDefaultValue(2.5m);
|
||||
|
||||
b.Property<int>("MaxBalancesPerLeg")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(300);
|
||||
|
||||
b.Property<int>("MaxNetworkLevel")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(15);
|
||||
|
||||
b.Property<long>("Price")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<bool>("SupportsDayaPurchase")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("SupportsDirectPurchase")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IsActive")
|
||||
.HasDatabaseName("IX_Package_IsActive");
|
||||
|
||||
b.HasIndex("SortOrder")
|
||||
.HasDatabaseName("IX_Package_SortOrder");
|
||||
|
||||
b.ToTable("Packages", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<long>("ClubFeatureId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsIncluded")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("PackageId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClubFeatureId");
|
||||
|
||||
b.HasIndex("PackageId", "ClubFeatureId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_PackageFeature_PackageId_ClubFeatureId");
|
||||
|
||||
b.ToTable("PackageFeatures", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
@@ -3718,6 +3870,9 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long?>("PackageId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long?>("RefrenceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
@@ -3726,6 +3881,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PackageId");
|
||||
|
||||
b.HasIndex("WalletId");
|
||||
|
||||
b.ToTable("UserWalletChangeLogs", "CMS");
|
||||
@@ -3947,12 +4104,26 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "FirstPackage")
|
||||
.WithMany()
|
||||
.HasForeignKey("FirstPackageId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "LastPackage")
|
||||
.WithMany()
|
||||
.HasForeignKey("LastPackageId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.User", "User")
|
||||
.WithOne("ClubMembership")
|
||||
.HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("FirstPackage");
|
||||
|
||||
b.Navigation("LastPackage");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
@@ -3964,6 +4135,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package")
|
||||
.WithMany()
|
||||
.HasForeignKey("PackageId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
@@ -3972,6 +4149,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.Navigation("ClubMembership");
|
||||
|
||||
b.Navigation("Package");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
@@ -4004,6 +4183,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package")
|
||||
.WithMany()
|
||||
.HasForeignKey("PackageId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.User", "User")
|
||||
.WithMany("CommissionPayouts")
|
||||
.HasForeignKey("UserId")
|
||||
@@ -4022,6 +4207,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Package");
|
||||
|
||||
b.Navigation("User");
|
||||
|
||||
b.Navigation("WeekDefinition");
|
||||
@@ -4031,12 +4218,20 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package")
|
||||
.WithMany()
|
||||
.HasForeignKey("PackageId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition")
|
||||
.WithMany("WeeklyCommissionPools")
|
||||
.HasForeignKey("WeekDefinitionId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Package");
|
||||
|
||||
b.Navigation("WeekDefinition");
|
||||
});
|
||||
|
||||
@@ -4292,6 +4487,12 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package")
|
||||
.WithMany()
|
||||
.HasForeignKey("PackageId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.User", "User")
|
||||
.WithMany("NetworkWeeklyBalances")
|
||||
.HasForeignKey("UserId")
|
||||
@@ -4304,6 +4505,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Package");
|
||||
|
||||
b.Navigation("User");
|
||||
|
||||
b.Navigation("WeekDefinition");
|
||||
@@ -4320,6 +4523,25 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Navigation("Order");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.PackageFeature", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClubFeatureId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package")
|
||||
.WithMany("PackageFeatures")
|
||||
.HasForeignKey("PackageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ClubFeature");
|
||||
|
||||
b.Navigation("Package");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction")
|
||||
@@ -4504,7 +4726,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package")
|
||||
.WithMany()
|
||||
.WithMany("Purchases")
|
||||
.HasForeignKey("PackageId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
@@ -4561,12 +4783,19 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package")
|
||||
.WithMany()
|
||||
.HasForeignKey("PackageId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.UserWallet", "Wallet")
|
||||
.WithMany("UserWalletChangeLogs")
|
||||
.HasForeignKey("WalletId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Package");
|
||||
|
||||
b.Navigation("Wallet");
|
||||
});
|
||||
|
||||
@@ -4670,6 +4899,10 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b =>
|
||||
{
|
||||
b.Navigation("PackageFeatures");
|
||||
|
||||
b.Navigation("Purchases");
|
||||
|
||||
b.Navigation("UserOrders");
|
||||
});
|
||||
|
||||
|
||||
+37
-8
@@ -8,6 +8,9 @@
|
||||
CREATE OR ALTER PROCEDURE [CMS].[sp_CalculateWeeklyBalances]
|
||||
@WeekDefinitionId BIGINT,
|
||||
@ForceRecalculate BIT = 0,
|
||||
@PackageId BIGINT = NULL, -- پکیج خاص (اگر NULL از پکیج پایه استفاده میکند)
|
||||
@InputMaxBalancesPerLeg INT = NULL, -- سقف تعادل هر پا (از Package entity)
|
||||
@InputMaxNetworkLevel INT = NULL, -- حداکثر عمق شبکه (از Package entity)
|
||||
@RowCount INT OUTPUT
|
||||
AS
|
||||
BEGIN
|
||||
@@ -18,10 +21,33 @@ BEGIN
|
||||
DECLARE @StartDate DATETIME;
|
||||
DECLARE @EndDate DATETIME;
|
||||
DECLARE @PreviousWeekDefinitionId BIGINT;
|
||||
DECLARE @MaxBalancesPerLeg INT = 300;
|
||||
DECLARE @MaxNetworkLevel INT = 15;
|
||||
DECLARE @MaxBalancesPerLeg INT;
|
||||
DECLARE @MaxNetworkLevel INT;
|
||||
DECLARE @CalculatedAt DATETIME = GETDATE();
|
||||
|
||||
-- تعیین PackageId پیشفرض (پکیج پایه)
|
||||
IF @PackageId IS NULL
|
||||
BEGIN
|
||||
SELECT TOP 1 @PackageId = Id
|
||||
FROM CMS.Packages
|
||||
WHERE IsBasePackage = 1 AND IsDeleted = 0;
|
||||
END
|
||||
|
||||
-- خواندن تنظیمات از Package entity اگر پارامتر داده نشده
|
||||
IF @InputMaxBalancesPerLeg IS NOT NULL
|
||||
SET @MaxBalancesPerLeg = @InputMaxBalancesPerLeg;
|
||||
ELSE
|
||||
SELECT @MaxBalancesPerLeg = ISNULL(MaxBalancesPerLeg, 300) FROM CMS.Packages WHERE Id = @PackageId;
|
||||
|
||||
IF @InputMaxNetworkLevel IS NOT NULL
|
||||
SET @MaxNetworkLevel = @InputMaxNetworkLevel;
|
||||
ELSE
|
||||
SELECT @MaxNetworkLevel = ISNULL(MaxNetworkLevel, 15) FROM CMS.Packages WHERE Id = @PackageId;
|
||||
|
||||
-- fallbackهای امن
|
||||
SET @MaxBalancesPerLeg = ISNULL(@MaxBalancesPerLeg, 300);
|
||||
SET @MaxNetworkLevel = ISNULL(@MaxNetworkLevel, 15);
|
||||
|
||||
BEGIN TRY
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
@@ -65,11 +91,9 @@ BEGIN
|
||||
AND IsActive = 1;
|
||||
|
||||
-- =============================================
|
||||
-- 4. مقادیر ثابت (Hardcoded - از SystemConstants)
|
||||
-- 4. تنظیمات پکیج (از پارامترها خوانده میشود)
|
||||
-- =============================================
|
||||
-- این مقادیر ثابت هستند و تغییر نمیکنند
|
||||
SET @MaxBalancesPerLeg = 300; -- سقف تعادل هر پا
|
||||
SET @MaxNetworkLevel = 15; -- حداکثر عمق شبکه
|
||||
-- @MaxBalancesPerLeg و @MaxNetworkLevel قبلاً مقداردهی شدهاند
|
||||
|
||||
-- =============================================
|
||||
-- 5. ایجاد جدول موقت برای نتایج
|
||||
@@ -96,7 +120,8 @@ BEGIN
|
||||
INSERT INTO #Balances (UserId)
|
||||
SELECT DISTINCT u.Id
|
||||
FROM CMS.Users u
|
||||
INNER JOIN CMS.ClubMemberships cm ON cm.UserId = u.Id AND cm.IsActive = 1;
|
||||
INNER JOIN CMS.ClubMemberships cm ON cm.UserId = u.Id AND cm.IsActive = 1
|
||||
WHERE cm.LastPackageId = @PackageId;
|
||||
|
||||
-- =============================================
|
||||
-- 7. دریافت باقیمانده هفته قبل
|
||||
@@ -107,7 +132,9 @@ BEGIN
|
||||
SET b.LeftLegCarryover = ISNULL(nb.LeftLegRemainder, 0),
|
||||
b.RightLegCarryover = ISNULL(nb.RightLegRemainder, 0)
|
||||
FROM #Balances b
|
||||
LEFT JOIN CMS.NetworkWeeklyBalances nb ON nb.UserId = b.UserId AND nb.WeekDefinitionId = @PreviousWeekDefinitionId;
|
||||
LEFT JOIN CMS.NetworkWeeklyBalances nb ON nb.UserId = b.UserId
|
||||
AND nb.WeekDefinitionId = @PreviousWeekDefinitionId
|
||||
AND nb.PackageId = @PackageId;
|
||||
END
|
||||
|
||||
-- =============================================
|
||||
@@ -269,6 +296,7 @@ BEGIN
|
||||
INSERT INTO CMS.NetworkWeeklyBalances (
|
||||
UserId,
|
||||
WeekDefinitionId,
|
||||
PackageId,
|
||||
LeftLegNewMembers,
|
||||
RightLegNewMembers,
|
||||
LeftLegCarryover,
|
||||
@@ -295,6 +323,7 @@ BEGIN
|
||||
SELECT
|
||||
UserId,
|
||||
@WeekDefinitionId,
|
||||
@PackageId,
|
||||
LeftLegNewMembers,
|
||||
RightLegNewMembers,
|
||||
LeftLegCarryover,
|
||||
|
||||
+41
-16
@@ -62,10 +62,20 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
|
||||
.Select(w => w.UserId)
|
||||
.ToHashSetAsync(cancellationToken);
|
||||
|
||||
var activeClubMemberUserIds = await _context.ClubMemberships
|
||||
// دریافت عضویتهای فعال به همراه PackageId هر کاربر
|
||||
var activeClubMemberships = await _context.ClubMemberships
|
||||
.Where(c => c.IsActive && !magicModeUserIds.Contains(c.UserId))
|
||||
.Select(c => new { c.UserId, PackageId = c.LastPackageId })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var activeClubMemberUserIds = activeClubMemberships
|
||||
.Select(c => c.UserId)
|
||||
.ToHashSetAsync(cancellationToken);
|
||||
.ToHashSet();
|
||||
|
||||
// نگاشت کاربر → PackageId
|
||||
var userPackageMap = activeClubMemberships
|
||||
.Where(c => c.PackageId.HasValue)
|
||||
.ToDictionary(c => c.UserId, c => c.PackageId!.Value);
|
||||
|
||||
// دریافت کاربران فعال در شبکه که عضو باشگاه هستند
|
||||
var usersInNetwork = await _context.Users
|
||||
@@ -73,40 +83,52 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
|
||||
.Select(x => new { x.Id })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// دریافت باقیماندههای هفته قبل
|
||||
// بارگذاری همه پکیجهای فعال (bulk load)
|
||||
var allPackages = await _context.Packages
|
||||
.Where(p => !p.IsDeleted)
|
||||
.ToDictionaryAsync(p => p.Id, cancellationToken);
|
||||
|
||||
// پکیج پیشفرض (fallback برای کاربرانی که PackageId ندارند)
|
||||
var defaultPackage = allPackages.Values
|
||||
.FirstOrDefault(p => p.IsBasePackage)
|
||||
?? throw new InvalidOperationException("پکیج پایه یافت نشد");
|
||||
|
||||
// دریافت باقیماندههای هفته قبل — شامل PackageId
|
||||
var previousWeekDefinitionId = GetPreviousWeekDefinitionId(weekDefinitionId);
|
||||
Dictionary<long, (int LeftLegRemainder, int RightLegRemainder)> previousWeekCarryovers;
|
||||
Dictionary<(long UserId, long PackageId), (int LeftLegRemainder, int RightLegRemainder)> previousWeekCarryovers;
|
||||
|
||||
if (previousWeekDefinitionId.HasValue)
|
||||
{
|
||||
previousWeekCarryovers = await _context.NetworkWeeklyBalances
|
||||
.Where(x => x.WeekDefinitionId == previousWeekDefinitionId.Value)
|
||||
.ToDictionaryAsync(
|
||||
x => x.UserId,
|
||||
x => (x.UserId, x.PackageId),
|
||||
x => (x.LeftLegRemainder, x.RightLegRemainder),
|
||||
cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
previousWeekCarryovers = new Dictionary<long, (int, int)>();
|
||||
previousWeekCarryovers = new Dictionary<(long, long), (int, int)>();
|
||||
}
|
||||
|
||||
var balancesList = new List<NetworkWeeklyBalance>();
|
||||
var calculatedAt = DateTime.Now;
|
||||
|
||||
// استفاده از SystemConstants به جای دیتابیس
|
||||
var maxBalancesPerLeg = SystemConstants.CommissionMaxWeeklyBalancesPerLeg;
|
||||
var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel;
|
||||
|
||||
foreach (var user in usersInNetwork.OrderBy(o => o.Id))
|
||||
{
|
||||
// دریافت باقیمانده هفته قبل
|
||||
// تعیین پکیج هر کاربر (per-user)
|
||||
var userPackageId = userPackageMap.GetValueOrDefault(user.Id, defaultPackage.Id);
|
||||
var package = allPackages.GetValueOrDefault(userPackageId, defaultPackage);
|
||||
var maxBalancesPerLeg = package.MaxBalancesPerLeg;
|
||||
var maxNetworkLevel = package.MaxNetworkLevel;
|
||||
// دریافت باقیمانده هفته قبل — فیلتر per-package
|
||||
var leftCarryover = 0;
|
||||
var rightCarryover = 0;
|
||||
if (previousWeekCarryovers.ContainsKey(user.Id))
|
||||
var carryoverKey = (UserId: user.Id, PackageId: userPackageId);
|
||||
if (previousWeekCarryovers.ContainsKey(carryoverKey))
|
||||
{
|
||||
leftCarryover = previousWeekCarryovers[user.Id].LeftLegRemainder;
|
||||
rightCarryover = previousWeekCarryovers[user.Id].RightLegRemainder;
|
||||
leftCarryover = previousWeekCarryovers[carryoverKey].LeftLegRemainder;
|
||||
rightCarryover = previousWeekCarryovers[carryoverKey].RightLegRemainder;
|
||||
}
|
||||
|
||||
// محاسبه تعداد اعضای جدید در این هفته (تا maxNetworkLevel لول پایینتر)
|
||||
@@ -135,6 +157,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
|
||||
{
|
||||
UserId = user.Id,
|
||||
WeekDefinitionId = weekDefinitionId,
|
||||
PackageId = package.Id,
|
||||
LeftLegNewMembers = leftNewMembers,
|
||||
RightLegNewMembers = rightNewMembers,
|
||||
LeftLegCarryover = leftCarryover,
|
||||
@@ -162,15 +185,16 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
|
||||
await _context.NetworkWeeklyBalances.AddRangeAsync(balancesList, cancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// محاسبه تعادل زیرمجموعه
|
||||
// محاسبه تعادل زیرمجموعه — per-user maxNetworkLevel
|
||||
var balancesDictionary = balancesList.ToDictionary(x => x.UserId);
|
||||
|
||||
foreach (var balance in balancesList)
|
||||
{
|
||||
var balancePackage = allPackages.GetValueOrDefault(balance.PackageId, defaultPackage);
|
||||
var subordinateBalances = await CalculateSubordinateBalancesAsync(
|
||||
balance.UserId,
|
||||
balancesDictionary,
|
||||
maxNetworkLevel,
|
||||
balancePackage.MaxNetworkLevel,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
@@ -284,6 +308,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
|
||||
UserId = balance.UserId,
|
||||
WeekDefinitionId = weekDefinitionId,
|
||||
WeeklyPoolId = existingPool.Id,
|
||||
PackageId = balance.PackageId, // per-user PackageId از محاسبه تعادل
|
||||
BalancesEarned = userBalance,
|
||||
ValuePerBalance = valuePerBalance,
|
||||
TotalAmount = totalAmount,
|
||||
|
||||
+60
-23
@@ -6,6 +6,7 @@ namespace CMSMicroservice.Infrastructure.Services.Commission;
|
||||
/// <summary>
|
||||
/// پیادهسازی محاسبه کمیسیون با استفاده از Stored Procedure
|
||||
/// این روش برای دادههای زیاد بهینهتر است و از CTE استفاده میکند
|
||||
/// حلقه روی پکیجهای فعال — هر پکیج با تنظیمات خودش
|
||||
/// </summary>
|
||||
public class StoredProcedureCommissionCalculationStrategy : ICommissionCalculationStrategy
|
||||
{
|
||||
@@ -22,45 +23,81 @@ public class StoredProcedureCommissionCalculationStrategy : ICommissionCalculati
|
||||
bool forceRecalculate,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// دسترسی به DbContext واقعی برای اجرای SP
|
||||
var dbContext = _context as DbContext;
|
||||
if (dbContext == null)
|
||||
{
|
||||
throw new InvalidOperationException("DbContext برای اجرای Stored Procedure در دسترس نیست");
|
||||
}
|
||||
|
||||
// بارگذاری پکیجهای فعال
|
||||
var activePackages = await _context.Packages
|
||||
.Where(p => !p.IsDeleted && p.IsActive)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (!activePackages.Any())
|
||||
{
|
||||
throw new InvalidOperationException("هیچ پکیج فعالی یافت نشد");
|
||||
}
|
||||
|
||||
var totalRowCount = 0;
|
||||
|
||||
var connection = dbContext.Database.GetDbConnection();
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "CMS.sp_CalculateWeeklyBalances";
|
||||
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||
command.CommandTimeout = 300; // 5 دقیقه timeout
|
||||
// حلقه روی پکیجها — هر پکیج با تنظیمات خودش
|
||||
foreach (var package in activePackages)
|
||||
{
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "CMS.sp_CalculateWeeklyBalances";
|
||||
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||
command.CommandTimeout = 300; // 5 دقیقه timeout
|
||||
|
||||
// پارامترها
|
||||
var paramWeekDefinitionId = command.CreateParameter();
|
||||
paramWeekDefinitionId.ParameterName = "@WeekDefinitionId";
|
||||
paramWeekDefinitionId.Value = weekDefinitionId;
|
||||
command.Parameters.Add(paramWeekDefinitionId);
|
||||
// پارامتر WeekDefinitionId
|
||||
var paramWeekDefinitionId = command.CreateParameter();
|
||||
paramWeekDefinitionId.ParameterName = "@WeekDefinitionId";
|
||||
paramWeekDefinitionId.Value = weekDefinitionId;
|
||||
command.Parameters.Add(paramWeekDefinitionId);
|
||||
|
||||
var paramForceRecalculate = command.CreateParameter();
|
||||
paramForceRecalculate.ParameterName = "@ForceRecalculate";
|
||||
paramForceRecalculate.Value = forceRecalculate;
|
||||
command.Parameters.Add(paramForceRecalculate);
|
||||
// پارامتر ForceRecalculate
|
||||
var paramForceRecalculate = command.CreateParameter();
|
||||
paramForceRecalculate.ParameterName = "@ForceRecalculate";
|
||||
paramForceRecalculate.Value = forceRecalculate;
|
||||
command.Parameters.Add(paramForceRecalculate);
|
||||
|
||||
// پارامتر خروجی
|
||||
var paramRowCount = command.CreateParameter();
|
||||
paramRowCount.ParameterName = "@RowCount";
|
||||
paramRowCount.Direction = System.Data.ParameterDirection.Output;
|
||||
paramRowCount.DbType = System.Data.DbType.Int32;
|
||||
command.Parameters.Add(paramRowCount);
|
||||
// پارامتر PackageId — per-package
|
||||
var paramPackageId = command.CreateParameter();
|
||||
paramPackageId.ParameterName = "@PackageId";
|
||||
paramPackageId.Value = package.Id;
|
||||
command.Parameters.Add(paramPackageId);
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
// پارامتر MaxBalancesPerLeg — per-package
|
||||
var paramMaxBalances = command.CreateParameter();
|
||||
paramMaxBalances.ParameterName = "@InputMaxBalancesPerLeg";
|
||||
paramMaxBalances.Value = package.MaxBalancesPerLeg;
|
||||
command.Parameters.Add(paramMaxBalances);
|
||||
|
||||
var rowCount = (int)(paramRowCount.Value ?? 0);
|
||||
return rowCount;
|
||||
// پارامتر MaxNetworkLevel — per-package
|
||||
var paramMaxLevel = command.CreateParameter();
|
||||
paramMaxLevel.ParameterName = "@InputMaxNetworkLevel";
|
||||
paramMaxLevel.Value = package.MaxNetworkLevel;
|
||||
command.Parameters.Add(paramMaxLevel);
|
||||
|
||||
// پارامتر خروجی
|
||||
var paramRowCount = command.CreateParameter();
|
||||
paramRowCount.ParameterName = "@RowCount";
|
||||
paramRowCount.Direction = System.Data.ParameterDirection.Output;
|
||||
paramRowCount.DbType = System.Data.DbType.Int32;
|
||||
command.Parameters.Add(paramRowCount);
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
|
||||
var rowCount = (int)(paramRowCount.Value ?? 0);
|
||||
totalRowCount += rowCount;
|
||||
}
|
||||
|
||||
return totalRowCount;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>0.0.184</Version>
|
||||
<Version>0.0.188</Version>
|
||||
<DebugType>None</DebugType>
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
@@ -64,8 +64,8 @@
|
||||
<Protobuf Include="Protos\appversion.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
<!-- Inventory Management System -->
|
||||
<Protobuf Include="Protos\inventory.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
<!-- FMS (File Management Service) - gRPC Client only -->
|
||||
<Protobuf Include="Protos\fms.proto" ProtoRoot="Protos\" GrpcServices="Client" />
|
||||
<!-- [ARCHIVED] FMS (File Management Service) - سرویس هرگز پیادهسازی نشد. فایلها از ImageResolver استفاده میکنند -->
|
||||
<!-- <Protobuf Include="Protos\fms.proto" ProtoRoot="Protos\" GrpcServices="Client" /> -->
|
||||
<!-- Blog & Content Management System -->
|
||||
<Protobuf Include="Protos\blogpost.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
<Protobuf Include="Protos\blogcategory.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
|
||||
@@ -217,6 +217,7 @@ message GetUserCommissionPayoutsRequest
|
||||
google.protobuf.Int64Value user_id = 1;
|
||||
google.protobuf.Int32Value status = 2; // CommissionPayoutStatus enum
|
||||
google.protobuf.Int64Value week_definition_id = 3; // Preferred filter
|
||||
google.protobuf.Int64Value package_id = 4; // فیلتر بر اساس پکیج
|
||||
int32 page_index = 5;
|
||||
int32 page_size = 6;
|
||||
}
|
||||
@@ -244,6 +245,8 @@ message UserCommissionPayoutModel
|
||||
string iban_number = 13;
|
||||
google.protobuf.Timestamp withdrawn_at = 14;
|
||||
google.protobuf.Timestamp created = 15;
|
||||
int64 package_id = 16; // شناسه پکیج
|
||||
string package_title = 17; // نام پکیج
|
||||
}
|
||||
|
||||
// GetCommissionPayoutHistory Query
|
||||
@@ -314,6 +317,7 @@ message GetUserWeeklyBalancesRequest
|
||||
{
|
||||
google.protobuf.Int64Value user_id = 1;
|
||||
google.protobuf.Int64Value week_definition_id = 2; // Preferred filter
|
||||
google.protobuf.Int64Value package_id = 3; // فیلتر بر اساس پکیج
|
||||
bool only_active = 4; // Only non-expired balances
|
||||
int32 page_index = 5;
|
||||
int32 page_size = 6;
|
||||
@@ -343,6 +347,8 @@ message UserWeeklyBalanceModel
|
||||
google.protobuf.Timestamp calculated_at = 14;
|
||||
bool is_expired = 15;
|
||||
google.protobuf.Timestamp created = 16;
|
||||
int64 package_id = 17; // شناسه پکیج
|
||||
string package_title = 18; // نام پکیج
|
||||
}
|
||||
|
||||
// GetAllWeeklyPools Query
|
||||
@@ -598,6 +604,7 @@ message GetMyCommissionPayoutsRequest
|
||||
int32 page_size = 2;
|
||||
google.protobuf.Int64Value week_definition_id = 3;
|
||||
google.protobuf.Int32Value status = 4; // 0=Pending, 1=Calculated, 2=Paid, 3=Withdrawn
|
||||
google.protobuf.Int64Value package_id = 5; // فیلتر بر اساس پکیج
|
||||
}
|
||||
|
||||
message GetMyCommissionPayoutsResponse
|
||||
@@ -622,6 +629,8 @@ message CustomerCommissionPayoutModel
|
||||
int32 status = 7; // 0=Pending, 1=Paid, 2=WithdrawRequested, 3=Withdrawn, 4=PaymentFailed, 5=Cancelled
|
||||
google.protobuf.Timestamp calculated_date = 8;
|
||||
string date_persian = 9;
|
||||
int64 package_id = 10; // شناسه پکیج
|
||||
string package_title = 11; // نام پکیج
|
||||
}
|
||||
|
||||
// GetMyWeeklyBalances - for frontend customer display
|
||||
@@ -631,6 +640,7 @@ message GetMyWeeklyBalancesRequest
|
||||
int32 page_size = 2;
|
||||
google.protobuf.Int64Value week_definition_id = 3;
|
||||
bool only_active = 4;
|
||||
google.protobuf.Int64Value package_id = 5; // فیلتر بر اساس پکیج
|
||||
}
|
||||
|
||||
message GetMyWeeklyBalancesResponse
|
||||
@@ -658,4 +668,6 @@ message CustomerWeeklyBalanceModel
|
||||
int32 right_leg_carryover = 12;
|
||||
int32 left_leg_new_members = 13;
|
||||
int32 right_leg_new_members = 14;
|
||||
int64 package_id = 15; // شناسه پکیج
|
||||
string package_title = 16; // نام پکیج
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ message GetUserNetworkResponse
|
||||
bool has_received_daya_credit = 32;
|
||||
google.protobuf.Timestamp daya_credit_received_at = 33;
|
||||
int32 package_purchase_method = 34;
|
||||
bool has_purchased_golden_package = 35;
|
||||
bool has_purchased_package = 35;
|
||||
|
||||
// آمار مالی
|
||||
double total_earned_commission = 36;
|
||||
|
||||
@@ -44,39 +44,12 @@ service PackageContract
|
||||
};
|
||||
};
|
||||
|
||||
// Package Purchase System
|
||||
rpc PurchaseGoldenPackage(PurchaseGoldenPackageRequest) returns (PurchaseGoldenPackageResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/PurchaseGoldenPackage"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc VerifyGoldenPackagePurchase(VerifyGoldenPackagePurchaseRequest) returns (VerifyGoldenPackagePurchaseResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/VerifyGoldenPackagePurchase"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc GetUserPackageStatus(GetUserPackageStatusRequest) returns (GetUserPackageStatusResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/GetUserPackageStatus"
|
||||
};
|
||||
};
|
||||
|
||||
// Base Package Payment (56M)
|
||||
rpc InitiateBasePackagePayment(InitiateBasePackagePaymentRequest) returns (InitiateBasePackagePaymentResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/InitiateBasePackagePayment"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc VerifyBasePackagePayment(VerifyBasePackagePaymentRequest) returns (VerifyBasePackagePaymentResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/VerifyBasePackagePayment"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
|
||||
// ============= Customer-specific Methods =============
|
||||
|
||||
rpc GetCustomerPackages(GetCustomerPackagesRequest) returns (GetCustomerPackagesResponse){
|
||||
@@ -114,6 +87,19 @@ message CreateNewPackageRequest
|
||||
string image_path = 3;
|
||||
int64 price = 4;
|
||||
BoostCardFileModel image_file = 5;
|
||||
int32 sort_order = 6;
|
||||
bool is_active = 7;
|
||||
bool is_base_package = 8;
|
||||
bool supports_daya_purchase = 9;
|
||||
bool supports_direct_purchase = 10;
|
||||
int64 activation_fee = 11;
|
||||
double discount_multiplier = 12;
|
||||
double magic_wallet_multiplier = 13;
|
||||
int32 max_balances_per_leg = 14;
|
||||
int32 max_network_level = 15;
|
||||
int64 magic_wallet_max_deposit = 16;
|
||||
int64 magic_wallet_max_credit = 17;
|
||||
repeated int64 feature_ids = 18;
|
||||
}
|
||||
message CreateNewPackageResponse
|
||||
{
|
||||
@@ -127,6 +113,19 @@ message UpdatePackageRequest
|
||||
string image_path = 4;
|
||||
int64 price = 5;
|
||||
BoostCardFileModel image_file = 6;
|
||||
int32 sort_order = 7;
|
||||
bool is_active = 8;
|
||||
bool is_base_package = 9;
|
||||
bool supports_daya_purchase = 10;
|
||||
bool supports_direct_purchase = 11;
|
||||
int64 activation_fee = 12;
|
||||
double discount_multiplier = 13;
|
||||
double magic_wallet_multiplier = 14;
|
||||
int32 max_balances_per_leg = 15;
|
||||
int32 max_network_level = 16;
|
||||
int64 magic_wallet_max_deposit = 17;
|
||||
int64 magic_wallet_max_credit = 18;
|
||||
repeated int64 feature_ids = 19;
|
||||
}
|
||||
message DeletePackageRequest
|
||||
{
|
||||
@@ -143,6 +142,19 @@ message GetPackageResponse
|
||||
string description = 3;
|
||||
string image_path = 4;
|
||||
int64 price = 5;
|
||||
int32 sort_order = 6;
|
||||
bool is_active = 7;
|
||||
bool is_base_package = 8;
|
||||
bool supports_daya_purchase = 9;
|
||||
bool supports_direct_purchase = 10;
|
||||
int64 activation_fee = 11;
|
||||
double discount_multiplier = 12;
|
||||
double magic_wallet_multiplier = 13;
|
||||
int32 max_balances_per_leg = 14;
|
||||
int32 max_network_level = 15;
|
||||
int64 magic_wallet_max_deposit = 16;
|
||||
int64 magic_wallet_max_credit = 17;
|
||||
repeated int64 feature_ids = 18;
|
||||
}
|
||||
message GetAllPackageByFilterRequest
|
||||
{
|
||||
@@ -170,40 +182,19 @@ message GetAllPackageByFilterResponseModel
|
||||
string description = 3;
|
||||
string image_path = 4;
|
||||
int64 price = 5;
|
||||
}
|
||||
|
||||
// Package Purchase Messages
|
||||
message PurchaseGoldenPackageRequest
|
||||
{
|
||||
int64 user_id = 1;
|
||||
int64 package_id = 2;
|
||||
string return_url = 3;
|
||||
}
|
||||
|
||||
message PurchaseGoldenPackageResponse
|
||||
{
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
int64 order_id = 3;
|
||||
string payment_gateway_url = 4;
|
||||
string tracking_code = 5;
|
||||
}
|
||||
|
||||
message VerifyGoldenPackagePurchaseRequest
|
||||
{
|
||||
int64 order_id = 1;
|
||||
string authority = 2;
|
||||
string status = 3;
|
||||
}
|
||||
|
||||
message VerifyGoldenPackagePurchaseResponse
|
||||
{
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
int64 order_id = 3;
|
||||
int64 transaction_id = 4;
|
||||
string reference_code = 5;
|
||||
int64 wallet_balance = 6;
|
||||
int32 sort_order = 6;
|
||||
bool is_active = 7;
|
||||
bool is_base_package = 8;
|
||||
bool supports_daya_purchase = 9;
|
||||
bool supports_direct_purchase = 10;
|
||||
int64 activation_fee = 11;
|
||||
double discount_multiplier = 12;
|
||||
double magic_wallet_multiplier = 13;
|
||||
int32 max_balances_per_leg = 14;
|
||||
int32 max_network_level = 15;
|
||||
int64 magic_wallet_max_deposit = 16;
|
||||
int64 magic_wallet_max_credit = 17;
|
||||
repeated int64 feature_ids = 18;
|
||||
}
|
||||
|
||||
message GetUserPackageStatusRequest
|
||||
@@ -224,43 +215,6 @@ message GetUserPackageStatusResponse
|
||||
google.protobuf.Timestamp last_purchase_date = 9;
|
||||
}
|
||||
|
||||
// Base Package Payment Messages
|
||||
message InitiateBasePackagePaymentRequest
|
||||
{
|
||||
int64 user_id = 1;
|
||||
string callback_url = 2; // Payment gateway callback URL
|
||||
}
|
||||
|
||||
message InitiateBasePackagePaymentResponse
|
||||
{
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
int64 order_id = 3;
|
||||
int64 transaction_id = 4;
|
||||
int64 amount = 5;
|
||||
string payment_gateway_url = 6;
|
||||
}
|
||||
|
||||
message VerifyBasePackagePaymentRequest
|
||||
{
|
||||
int64 order_id = 1;
|
||||
int64 transaction_id = 2;
|
||||
bool payment_success = 3;
|
||||
google.protobuf.StringValue ref_id = 4;
|
||||
google.protobuf.StringValue message = 5;
|
||||
}
|
||||
|
||||
message VerifyBasePackagePaymentResponse
|
||||
{
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
int64 order_id = 3;
|
||||
int64 transaction_id = 4;
|
||||
google.protobuf.StringValue reference_code = 5;
|
||||
int64 wallet_balance = 6;
|
||||
int64 discount_balance = 7;
|
||||
}
|
||||
|
||||
// ============= Customer Message Types =============
|
||||
|
||||
message GetCustomerPackagesRequest
|
||||
@@ -350,8 +304,17 @@ message CustomerPackageModel
|
||||
int32 validity_days = 9;
|
||||
bool is_popular = 10;
|
||||
string short_description = 11;
|
||||
string title = 12; // Alias for frontend compatibility (uses name)
|
||||
string image_path = 13; // Alias for frontend compatibility (uses image_url)
|
||||
string title = 12;
|
||||
string image_path = 13;
|
||||
// New package-based fields
|
||||
int64 activation_fee = 14;
|
||||
double discount_multiplier = 15;
|
||||
double magic_wallet_multiplier = 16;
|
||||
int64 magic_wallet_max_deposit = 17;
|
||||
int64 magic_wallet_max_credit = 18;
|
||||
bool is_base_package = 19;
|
||||
bool supports_daya_purchase = 20;
|
||||
bool supports_direct_purchase = 21;
|
||||
}
|
||||
|
||||
message PackageFeature
|
||||
|
||||
@@ -68,7 +68,9 @@ public class CommissionProfile : IRegister
|
||||
.Map(dest => dest.WithdrawnAt, src => src.WithdrawnAt.HasValue
|
||||
? Timestamp.FromDateTime(src.WithdrawnAt.Value.ToUniversalTime())
|
||||
: null)
|
||||
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created));
|
||||
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created))
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.PackageTitle, src => src.PackageTitle);
|
||||
|
||||
// GetWeekDefinitions Request Mapping
|
||||
config.NewConfig<GetWeekDefinitionsRequest, GetWeekDefinitionsQuery>()
|
||||
@@ -127,7 +129,9 @@ public class CommissionProfile : IRegister
|
||||
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedAt.Value, DateTimeKind.Utc))
|
||||
: null)
|
||||
.Map(dest => dest.IsExpired, src => src.IsExpired)
|
||||
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created));
|
||||
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created))
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.PackageTitle, src => src.PackageTitle);
|
||||
|
||||
// GetMyCommissionPayouts Request Mapping
|
||||
config.NewConfig<GetMyCommissionPayoutsRequest, GetMyCommissionPayoutsQuery>()
|
||||
@@ -135,6 +139,7 @@ public class CommissionProfile : IRegister
|
||||
? (CommissionPayoutStatus?)src.Status.Value
|
||||
: null)
|
||||
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId != null ? src.WeekDefinitionId.Value : (long?)null)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId != null ? src.PackageId.Value : (long?)null)
|
||||
.Map(dest => dest.PaginationState, src => new PaginationState
|
||||
{
|
||||
PageNumber = src.PageNumber > 0 ? src.PageNumber : 1,
|
||||
@@ -160,12 +165,15 @@ public class CommissionProfile : IRegister
|
||||
.Map(dest => dest.CalculatedDate, src => src.CalculatedDate.HasValue
|
||||
? Timestamp.FromDateTime(src.CalculatedDate.Value.ToUniversalTime())
|
||||
: null)
|
||||
.Map(dest => dest.DatePersian, src => src.DatePersian);
|
||||
.Map(dest => dest.DatePersian, src => src.DatePersian)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.PackageTitle, src => src.PackageTitle);
|
||||
|
||||
// GetMyWeeklyBalances Request Mapping
|
||||
config.NewConfig<GetMyWeeklyBalancesRequest, GetMyWeeklyBalancesQuery>()
|
||||
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId != null ? src.WeekDefinitionId.Value : (long?)null)
|
||||
.Map(dest => dest.OnlyActive, src => src.OnlyActive)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId != null ? src.PackageId.Value : (long?)null)
|
||||
.Map(dest => dest.PaginationState, src => new PaginationState
|
||||
{
|
||||
PageNumber = src.PageNumber > 0 ? src.PageNumber : 1,
|
||||
@@ -199,6 +207,8 @@ public class CommissionProfile : IRegister
|
||||
.Map(dest => dest.IsExpired, src => src.IsExpired)
|
||||
.Map(dest => dest.CalculatedAt, src => src.CalculatedAt.HasValue
|
||||
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedAt.Value, DateTimeKind.Utc))
|
||||
: null);
|
||||
: null)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.PackageTitle, src => src.PackageTitle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class NetworkMembershipProfile : IRegister
|
||||
.Map(dest => dest.MaxNetworkDepth, src => src.MaxNetworkDepth)
|
||||
.Map(dest => dest.HasReceivedDayaCredit, src => src.HasReceivedDayaCredit)
|
||||
.Map(dest => dest.PackagePurchaseMethod, src => (int)src.PackagePurchaseMethod)
|
||||
.Map(dest => dest.HasPurchasedGoldenPackage, src => src.HasPurchasedGoldenPackage)
|
||||
.Map(dest => dest.HasPurchasedPackage, src => src.HasPurchasedPackage)
|
||||
.Map(dest => dest.TotalEarnedCommission, src => (double)src.TotalEarnedCommission)
|
||||
.Map(dest => dest.TotalPaidCommission, src => (double)src.TotalPaidCommission)
|
||||
.Map(dest => dest.PendingCommission, src => (double)src.PendingCommission)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
|
||||
using CMSMicroservice.Protobuf.Protos.Package;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
@@ -12,33 +8,6 @@ public class PackageProfile : IRegister
|
||||
{
|
||||
void IRegister.Register(TypeAdapterConfig config)
|
||||
{
|
||||
// PurchaseGoldenPackage
|
||||
config.NewConfig<PurchaseGoldenPackageRequest, PurchaseGoldenPackageCommand>()
|
||||
.Map(dest => dest.UserId, src => src.UserId)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.ReturnUrl, src => src.ReturnUrl);
|
||||
|
||||
config.NewConfig<PurchaseGoldenPackageResponseDto, PurchaseGoldenPackageResponse>()
|
||||
.Map(dest => dest.Success, src => src.Success)
|
||||
.Map(dest => dest.Message, src => src.Message)
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.PaymentGatewayUrl, src => src.PaymentGatewayUrl)
|
||||
.Map(dest => dest.TrackingCode, src => src.TrackingCode);
|
||||
|
||||
// VerifyGoldenPackagePurchase
|
||||
config.NewConfig<VerifyGoldenPackagePurchaseRequest, VerifyGoldenPackagePurchaseCommand>()
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.Authority, src => src.Authority)
|
||||
.Map(dest => dest.Status, src => src.Status);
|
||||
|
||||
config.NewConfig<VerifyGoldenPackagePurchaseResponseDto, VerifyGoldenPackagePurchaseResponse>()
|
||||
.Map(dest => dest.Success, src => src.Success)
|
||||
.Map(dest => dest.Message, src => src.Message)
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.TransactionId, src => src.TransactionId)
|
||||
.Map(dest => dest.ReferenceCode, src => src.ReferenceCode)
|
||||
.Map(dest => dest.WalletBalance, src => src.WalletBalance);
|
||||
|
||||
// GetUserPackageStatus
|
||||
config.NewConfig<GetUserPackageStatusRequest, GetUserPackageStatusQuery>()
|
||||
.Map(dest => dest.UserId, src => src.UserId);
|
||||
@@ -55,34 +24,6 @@ public class PackageProfile : IRegister
|
||||
.Map(dest => dest.LastPurchaseDate, src => src.LastPurchaseDate.HasValue
|
||||
? Timestamp.FromDateTime(src.LastPurchaseDate.Value.ToUniversalTime())
|
||||
: null);
|
||||
|
||||
// InitiateBasePackagePayment (56M پکیج پایه)
|
||||
config.NewConfig<InitiateBasePackagePaymentRequest, InitiateBasePackagePaymentCommand>()
|
||||
.Map(dest => dest.UserId, src => src.UserId);
|
||||
|
||||
config.NewConfig<InitiateBasePackagePaymentResponseDto, InitiateBasePackagePaymentResponse>()
|
||||
.Map(dest => dest.Success, src => src.Success)
|
||||
.Map(dest => dest.Message, src => src.Message)
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.TransactionId, src => src.TransactionId)
|
||||
.Map(dest => dest.Amount, src => src.Amount);
|
||||
|
||||
// VerifyBasePackagePayment (تأیید پرداخت پکیج پایه)
|
||||
config.NewConfig<VerifyBasePackagePaymentRequest, VerifyBasePackagePaymentCommand>()
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.TransactionId, src => src.TransactionId)
|
||||
.Map(dest => dest.PaymentSuccess, src => src.PaymentSuccess)
|
||||
.Map(dest => dest.RefId, src => src.RefId)
|
||||
.Map(dest => dest.Message, src => src.Message);
|
||||
|
||||
config.NewConfig<VerifyBasePackagePaymentResponseDto, VerifyBasePackagePaymentResponse>()
|
||||
.Map(dest => dest.Success, src => src.Success)
|
||||
.Map(dest => dest.Message, src => src.Message)
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.TransactionId, src => src.TransactionId)
|
||||
.Map(dest => dest.ReferenceCode, src => src.ReferenceCode)
|
||||
.Map(dest => dest.WalletBalance, src => src.WalletBalance)
|
||||
.Map(dest => dest.DiscountBalance, src => src.DiscountBalance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,8 +98,11 @@ public class CategoryService : CategoryContract.CategoryContractBase
|
||||
return response;
|
||||
}
|
||||
|
||||
#region [ARCHIVED] GetCategoryByIdForCustomer — تکراری: GetCategory (admin) + GetAllCategoriesForCustomer کافی است
|
||||
[Obsolete("ARCHIVED: Use GetCategory or GetAllCategoriesForCustomer instead")]
|
||||
public override async Task<GetCategoryByIdForCustomerResponse> GetCategoryByIdForCustomer(GetCategoryByIdForCustomerRequest request, ServerCallContext context)
|
||||
{
|
||||
// [ARCHIVED] duplicate — GetCategory + GetAllCategoriesForCustomer cover this
|
||||
var query = new GetCategoryQuery { Id = request.Id };
|
||||
var result = await _sender.Send(query, context.CancellationToken);
|
||||
|
||||
@@ -118,4 +121,5 @@ public class CategoryService : CategoryContract.CategoryContractBase
|
||||
}
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -177,6 +177,8 @@ public class CityService : CityContract.CityContractBase
|
||||
};
|
||||
}
|
||||
|
||||
#region [ARCHIVED] UpdateCity & DeleteCity — شهرها seed data هستند، ویرایش/حذف دستی باعث خرابی آدرسها میشود
|
||||
[Obsolete("ARCHIVED: Cities are seed data — manual edit/delete breaks user addresses")]
|
||||
public override async Task<Empty> UpdateCity(
|
||||
UpdateCityRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -195,6 +197,7 @@ public class CityService : CityContract.CityContractBase
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
[Obsolete("ARCHIVED: Cities are seed data — manual delete breaks user addresses")]
|
||||
public override async Task<Empty> DeleteCity(
|
||||
DeleteCityRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -206,6 +209,7 @@ public class CityService : CityContract.CityContractBase
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Empty();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -2,37 +2,42 @@ using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures;
|
||||
using CMSMicroservice.Application.Common.Authorization;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Protobuf.Protos.Configuration;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس تنظیمات سیستم - خواندن از SystemConstants
|
||||
/// سرویس تنظیمات سیستم — مقادیر پکیجی از Package entity خوانده میشوند
|
||||
/// </summary>
|
||||
public class ConfigurationService : ConfigurationContract.ConfigurationContractBase
|
||||
{
|
||||
private readonly ILogger<ConfigurationService> _logger;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public ConfigurationService(
|
||||
ILogger<ConfigurationService> logger,
|
||||
ICurrentUserService currentUserService,
|
||||
IMediator mediator)
|
||||
IMediator mediator,
|
||||
IApplicationDbContext context)
|
||||
{
|
||||
_logger = logger;
|
||||
_currentUserService = currentUserService;
|
||||
_mediator = mediator;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تنظیمات با کلید خاص
|
||||
/// </summary>
|
||||
public override Task<GetConfigurationByKeyResponse> GetConfigurationByKey(GetConfigurationByKeyRequest request, ServerCallContext context)
|
||||
public override async Task<GetConfigurationByKeyResponse> GetConfigurationByKey(GetConfigurationByKeyRequest request, ServerCallContext context)
|
||||
{
|
||||
var response = new GetConfigurationByKeyResponse
|
||||
{
|
||||
@@ -43,30 +48,33 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
LastModified = Timestamp.FromDateTime(DateTime.UtcNow)
|
||||
};
|
||||
|
||||
// خواندن مقدار از SystemConstants بر اساس کلید
|
||||
response.Value = GetConfigurationValue(request.Key);
|
||||
// برای کلیدهای پکیجی، از دیتابیس میخوانیم
|
||||
var package = await GetBasePackageAsync(context.CancellationToken);
|
||||
response.Value = GetConfigurationValue(request.Key, package);
|
||||
response.Description = GetConfigurationDescription(request.Key);
|
||||
|
||||
_logger.LogDebug("Configuration requested: Key={Key}, Value={Value}", request.Key, response.Value);
|
||||
|
||||
return Task.FromResult(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تنظیمات باشگاه مشتریان
|
||||
/// </summary>
|
||||
public override Task<GetClubConfigurationResponse> GetClubConfiguration(Empty request, ServerCallContext context)
|
||||
public override async Task<GetClubConfigurationResponse> GetClubConfiguration(Empty request, ServerCallContext context)
|
||||
{
|
||||
var package = await GetBasePackageAsync(context.CancellationToken);
|
||||
|
||||
var response = new GetClubConfigurationResponse
|
||||
{
|
||||
ActivationFee = SystemConstants.ClubActivationFee,
|
||||
MembershipGiftValue = SystemConstants.ClubMembershipGiftValue
|
||||
ActivationFee = package.ActivationFee,
|
||||
MembershipGiftValue = package.ActivationFee
|
||||
};
|
||||
|
||||
_logger.LogDebug("Club configuration requested: ActivationFee={ActivationFee}, GiftValue={GiftValue}",
|
||||
response.ActivationFee, response.MembershipGiftValue);
|
||||
|
||||
return Task.FromResult(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -111,18 +119,19 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
/// دریافت تمام تنظیمات
|
||||
/// </summary>
|
||||
[RequiresPermission(PermissionNames.SettingsView)]
|
||||
public override Task<GetAllConfigurationsResponse> GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context)
|
||||
public override async Task<GetAllConfigurationsResponse> GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context)
|
||||
{
|
||||
var package = await GetBasePackageAsync(context.CancellationToken);
|
||||
var response = new GetAllConfigurationsResponse();
|
||||
|
||||
// Club Settings
|
||||
response.Models.Add(CreateConfigModel("Club.ActivationFee", SystemConstants.ClubActivationFee.ToString(), "هزینه فعالسازی عضویت باشگاه", 2));
|
||||
response.Models.Add(CreateConfigModel("Club.MembershipGiftValue", SystemConstants.ClubMembershipGiftValue.ToString(), "مبلغ هدیه حق عضویت باشگاه", 2));
|
||||
// Club Settings (از Package)
|
||||
response.Models.Add(CreateConfigModel("Club.ActivationFee", package.ActivationFee.ToString(), "هزینه فعالسازی عضویت باشگاه", 2));
|
||||
response.Models.Add(CreateConfigModel("Club.MembershipGiftValue", package.ActivationFee.ToString(), "مبلغ هدیه حق عضویت باشگاه", 2));
|
||||
|
||||
// Commission Settings
|
||||
// Commission Settings (سقفها از Package)
|
||||
response.Models.Add(CreateConfigModel("Commission.MinWithdrawalAmount", SystemConstants.CommissionMinWithdrawalAmount.ToString(), "حداقل مبلغ برداشت", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.MaxWeeklyBalancesPerLeg", SystemConstants.CommissionMaxWeeklyBalancesPerLeg.ToString(), "سقف تعادل هفتگی برای هر دست", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.MaxNetworkLevel", SystemConstants.CommissionMaxNetworkLevel.ToString(), "حداکثر عمق شبکه برای محاسبه کمیسیون", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.MaxWeeklyBalancesPerLeg", package.MaxBalancesPerLeg.ToString(), "سقف تعادل هفتگی برای هر دست", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.MaxNetworkLevel", package.MaxNetworkLevel.ToString(), "حداکثر عمق شبکه برای محاسبه کمیسیون", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.CashWithdrawalEnabled", SystemConstants.CommissionCashWithdrawalEnabled.ToString(), "امکان برداشت نقدی", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.CalculationStrategy", SystemConstants.CommissionCalculationStrategy, "روش محاسبه کمیسیون", 3));
|
||||
|
||||
@@ -130,9 +139,14 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
response.Models.Add(CreateConfigModel("Network.AllowOrphanNodes", SystemConstants.NetworkAllowOrphanNodes.ToString(), "اجازه حذف والدین با فرزند", 1));
|
||||
response.Models.Add(CreateConfigModel("Network.MaxChildrenPerLeg", SystemConstants.NetworkMaxChildrenPerLeg.ToString(), "حداکثر تعداد فرزند مستقیم در هر پا", 1));
|
||||
|
||||
// Package Settings
|
||||
response.Models.Add(CreateConfigModel("Package.BasePackageAmount", SystemConstants.BasePackageAmount.ToString(), "مبلغ پکیج پایه", 0));
|
||||
response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", SystemConstants.DayaLoanAmount.ToString(), "مبلغ وام دایا", 0));
|
||||
// Package Settings (از Package)
|
||||
response.Models.Add(CreateConfigModel("Package.BasePackageAmount", package.Price.ToString(), "مبلغ پکیج پایه", 0));
|
||||
response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", package.Price.ToString(), "مبلغ وام دایا", 0));
|
||||
|
||||
// Magic Wallet Settings (از Package)
|
||||
response.Models.Add(CreateConfigModel("MagicWallet.Multiplier", package.MagicWalletMultiplier.ToString(), "ضریب شارژ کیفپول جادویی", 0));
|
||||
response.Models.Add(CreateConfigModel("MagicWallet.MaxDeposit", package.MagicWalletMaxDeposit.ToString(), "سقف واریز هر دور جادویی", 0));
|
||||
response.Models.Add(CreateConfigModel("MagicWallet.MaxCredit", package.MagicWalletMaxCredit.ToString(), "سقف اعتبار هر دور جادویی", 0));
|
||||
|
||||
// System Settings
|
||||
response.Models.Add(CreateConfigModel("System.MaintenanceMode", SystemConstants.SystemMaintenanceMode.ToString(), "حالت تعمیر و نگهداری", 0));
|
||||
@@ -141,7 +155,7 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
// Shop Settings
|
||||
response.Models.Add(CreateConfigModel("Shop.VAT", SystemConstants.ShopVAT.ToString(), "مالیات بر ارزش افزوده", 0));
|
||||
|
||||
return Task.FromResult(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,36 +168,43 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. تغییر از طریق کد انجام میشود"));
|
||||
}
|
||||
|
||||
#region [ARCHIVED] DeactivateConfiguration & GetConfigurationHistory — SystemConstants ثابت هستند، این RPCها بیمعنی
|
||||
[RequiresPermission(PermissionNames.SettingsManageConfiguration)]
|
||||
[Obsolete("ARCHIVED: SystemConstants are read-only — deactivation is meaningless")]
|
||||
public override Task<Empty> DeactivateConfiguration(DeactivateConfigurationRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogWarning("DeactivateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key);
|
||||
_logger.LogWarning("[ARCHIVED] DeactivateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key);
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. غیرفعالسازی از طریق کد انجام میشود"));
|
||||
}
|
||||
|
||||
[Obsolete("ARCHIVED: SystemConstants have no history — always returns empty")]
|
||||
public override Task<GetConfigurationHistoryResponse> GetConfigurationHistory(GetConfigurationHistoryRequest request, ServerCallContext context)
|
||||
{
|
||||
// چون constant هستند، تاریخچهای وجود نداره
|
||||
// [ARCHIVED] چون constant هستند، تاریخچهای وجود نداره
|
||||
return Task.FromResult(new GetConfigurationHistoryResponse());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
private static string GetConfigurationValue(string key)
|
||||
private static string GetConfigurationValue(string key, Package? package)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
"Club.ActivationFee" => SystemConstants.ClubActivationFee.ToString(),
|
||||
"Club.MembershipGiftValue" => SystemConstants.ClubMembershipGiftValue.ToString(),
|
||||
"Club.ActivationFee" => (package?.ActivationFee ?? 0).ToString(),
|
||||
"Club.MembershipGiftValue" => (package?.ActivationFee ?? 0).ToString(),
|
||||
"Commission.MinWithdrawalAmount" => SystemConstants.CommissionMinWithdrawalAmount.ToString(),
|
||||
"Commission.MaxWeeklyBalancesPerLeg" => SystemConstants.CommissionMaxWeeklyBalancesPerLeg.ToString(),
|
||||
"Commission.MaxNetworkLevel" => SystemConstants.CommissionMaxNetworkLevel.ToString(),
|
||||
"Commission.MaxWeeklyBalancesPerLeg" => (package?.MaxBalancesPerLeg ?? 0).ToString(),
|
||||
"Commission.MaxNetworkLevel" => (package?.MaxNetworkLevel ?? 0).ToString(),
|
||||
"Commission.CashWithdrawalEnabled" => SystemConstants.CommissionCashWithdrawalEnabled.ToString(),
|
||||
"Commission.CalculationStrategy" => SystemConstants.CommissionCalculationStrategy,
|
||||
"Network.AllowOrphanNodes" => SystemConstants.NetworkAllowOrphanNodes.ToString(),
|
||||
"Network.MaxChildrenPerLeg" => SystemConstants.NetworkMaxChildrenPerLeg.ToString(),
|
||||
"Package.BasePackageAmount" => SystemConstants.BasePackageAmount.ToString(),
|
||||
"Package.DayaLoanAmount" => SystemConstants.DayaLoanAmount.ToString(),
|
||||
"Package.BasePackageAmount" => (package?.Price ?? 0).ToString(),
|
||||
"Package.DayaLoanAmount" => (package?.Price ?? 0).ToString(),
|
||||
"MagicWallet.Multiplier" => (package?.MagicWalletMultiplier ?? 0).ToString(),
|
||||
"MagicWallet.MaxDeposit" => (package?.MagicWalletMaxDeposit ?? 0).ToString(),
|
||||
"MagicWallet.MaxCredit" => (package?.MagicWalletMaxCredit ?? 0).ToString(),
|
||||
"System.MaintenanceMode" => SystemConstants.SystemMaintenanceMode.ToString(),
|
||||
"System.EnableAuditLog" => SystemConstants.SystemEnableAuditLog.ToString(),
|
||||
"Shop.VAT" => SystemConstants.ShopVAT.ToString(),
|
||||
@@ -191,6 +212,13 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<Package> GetBasePackageAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new RpcException(new Status(StatusCode.Internal, "پکیج پایه یافت نشد"));
|
||||
}
|
||||
|
||||
private static string GetConfigurationDescription(string key)
|
||||
{
|
||||
return key switch
|
||||
@@ -206,6 +234,9 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
"Network.MaxChildrenPerLeg" => "حداکثر تعداد فرزند مستقیم در هر پا",
|
||||
"Package.BasePackageAmount" => "مبلغ پکیج پایه (ریال)",
|
||||
"Package.DayaLoanAmount" => "مبلغ وام دایا (ریال)",
|
||||
"MagicWallet.Multiplier" => "ضریب شارژ کیفپول جادویی",
|
||||
"MagicWallet.MaxDeposit" => "سقف واریز هر دور جادویی (ریال)",
|
||||
"MagicWallet.MaxCredit" => "سقف اعتبار هر دور جادویی (ریال)",
|
||||
"System.MaintenanceMode" => "حالت تعمیر و نگهداری سیستم",
|
||||
"System.EnableAuditLog" => "فعالسازی لاگ تغییرات",
|
||||
"Shop.VAT" => "مالیات بر ارزش افزوده",
|
||||
|
||||
@@ -25,9 +25,11 @@ public class HealthService : HealthContract.HealthContractBase
|
||||
return result.Adapt<GetSystemHealthResponse>();
|
||||
}
|
||||
|
||||
#region [ARCHIVED] GetServiceHealth — تکراری: GetSystemHealth کل سیستم را برمیگرداند، فیلتر client-side کافی است
|
||||
[Obsolete("ARCHIVED: Use GetSystemHealth + client-side filter by service name")]
|
||||
public override async Task<GetServiceHealthResponse> GetServiceHealth(GetServiceHealthRequest request, ServerCallContext context)
|
||||
{
|
||||
// For now, just return system health filtered by service name
|
||||
// [ARCHIVED] duplicate — GetSystemHealth returns all, client can filter
|
||||
var systemHealthQuery = new GetSystemHealthQuery();
|
||||
var systemHealth = await _mediator.Send(systemHealthQuery, context.CancellationToken);
|
||||
|
||||
@@ -45,4 +47,5 @@ public class HealthService : HealthContract.HealthContractBase
|
||||
CheckedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow)
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -3,10 +3,6 @@ using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.CreateNewPackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.UpdatePackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.DeletePackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetPackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetAllPackageByFilter;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
|
||||
@@ -72,32 +68,11 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
return await _dispatchRequestToCQRS.Handle<GetAllPackageByFilterRequest, GetAllPackageByFilterQuery, GetAllPackageByFilterResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<PurchaseGoldenPackageResponse> PurchaseGoldenPackage(PurchaseGoldenPackageRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<PurchaseGoldenPackageRequest, PurchaseGoldenPackageCommand, PurchaseGoldenPackageResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<VerifyGoldenPackagePurchaseResponse> VerifyGoldenPackagePurchase(VerifyGoldenPackagePurchaseRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<VerifyGoldenPackagePurchaseRequest, VerifyGoldenPackagePurchaseCommand, VerifyGoldenPackagePurchaseResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<GetUserPackageStatusResponse> GetUserPackageStatus(GetUserPackageStatusRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetUserPackageStatusRequest, GetUserPackageStatusQuery, GetUserPackageStatusResponse>(request, context);
|
||||
}
|
||||
|
||||
// Base Package Payment (56M)
|
||||
public override async Task<InitiateBasePackagePaymentResponse> InitiateBasePackagePayment(InitiateBasePackagePaymentRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<InitiateBasePackagePaymentRequest, InitiateBasePackagePaymentCommand, InitiateBasePackagePaymentResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<VerifyBasePackagePaymentResponse> VerifyBasePackagePayment(VerifyBasePackagePaymentRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<VerifyBasePackagePaymentRequest, VerifyBasePackagePaymentCommand, VerifyBasePackagePaymentResponse>(request, context);
|
||||
}
|
||||
|
||||
// ============= Customer-specific Method Implementations =============
|
||||
|
||||
public override async Task<GetCustomerPackagesResponse> GetCustomerPackages(GetCustomerPackagesRequest request, ServerCallContext context)
|
||||
@@ -207,13 +182,17 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
.Select(u => new { u.Mobile })
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
// Embed orderId in callback URL so FrontOffice can pass it back for verification
|
||||
var separator = request.CallbackUrl.Contains('?') ? "&" : "?";
|
||||
var callbackWithOrder = $"{request.CallbackUrl}{separator}orderId={purchase.Id}";
|
||||
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||
{
|
||||
Amount = package.Price,
|
||||
UserId = userId,
|
||||
Mobile = user?.Mobile ?? string.Empty,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
CallbackUrl = request.CallbackUrl
|
||||
CallbackUrl = callbackWithOrder
|
||||
}, context.CancellationToken);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
@@ -317,6 +296,48 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success;
|
||||
transaction.PaymentDate = DateTime.UtcNow;
|
||||
transaction.RefId = verifyResult.RefId;
|
||||
|
||||
// شارژ کیف پول کاربر
|
||||
var wallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(w => w.UserId == purchase.UserId, context.CancellationToken);
|
||||
|
||||
if (wallet == null)
|
||||
{
|
||||
wallet = new CMSMicroservice.Domain.Entities.UserWallet
|
||||
{
|
||||
UserId = purchase.UserId,
|
||||
Balance = 0,
|
||||
DiscountBalance = 0,
|
||||
NetworkBalance = 0
|
||||
};
|
||||
_context.UserWallets.Add(wallet);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
}
|
||||
|
||||
var discountAmount = (long)(purchase.Amount * (double)(purchase.Package?.DiscountMultiplier ?? 2.0m));
|
||||
wallet.Balance += purchase.Amount;
|
||||
wallet.DiscountBalance += discountAmount;
|
||||
|
||||
// ثبت لاگ کیف پول
|
||||
var walletLog = new CMSMicroservice.Domain.Entities.UserWalletChangeLog
|
||||
{
|
||||
WalletId = wallet.Id,
|
||||
CurrentBalance = wallet.Balance,
|
||||
ChangeValue = purchase.Amount,
|
||||
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||
ChangeNerworkValue = 0,
|
||||
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||
ChangeDiscountValue = discountAmount,
|
||||
IsIncrease = true,
|
||||
RefrenceId = transaction.Id
|
||||
};
|
||||
_context.UserWalletChangeLogs.Add(walletLog);
|
||||
|
||||
// بهروزرسانی کاربر
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == purchase.UserId, context.CancellationToken);
|
||||
if (user != null)
|
||||
user.PackagePurchaseMethod = Domain.Enums.PackagePurchaseMethod.DirectPurchase;
|
||||
}
|
||||
else if (transaction != null)
|
||||
{
|
||||
|
||||
@@ -344,9 +344,18 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
.Include(u => u.ClubMembership)
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, context.CancellationToken);
|
||||
|
||||
if (wallet.WalletMode == WalletMode.Magic
|
||||
&& wallet.MagicTotalDeposited >= SystemConstants.MagicWalletMaxDeposit)
|
||||
if (wallet.WalletMode == WalletMode.Magic)
|
||||
{
|
||||
// بارگذاری پکیج کاربر برای سقف کیفپول جادویی
|
||||
var userCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken);
|
||||
var userPackage = userCycle != null
|
||||
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == userCycle.PackageId, context.CancellationToken)
|
||||
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken);
|
||||
var magicMaxDeposit = userPackage?.MagicWalletMaxDeposit ?? 1_000_000_000;
|
||||
|
||||
if (wallet.MagicTotalDeposited >= magicMaxDeposit)
|
||||
{
|
||||
// ── EXIT Magic Mode ──
|
||||
// هر دو شرط: Balance=0 و سقف 100M پر شده
|
||||
wallet.WalletMode = WalletMode.Normal;
|
||||
@@ -358,6 +367,14 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
if (currentCycle != null)
|
||||
{
|
||||
currentCycle.MagicCompletedAt = DateTime.UtcNow;
|
||||
currentCycle.IsCurrentCycle = false;
|
||||
}
|
||||
|
||||
// B6 fix: ریست PackagePurchaseMethod تا Guards G1-G3 خرید مجدد را مسدود نکنند
|
||||
if (user != null)
|
||||
{
|
||||
user.PackagePurchaseMethod = PackagePurchaseMethod.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (wallet.WalletMode == WalletMode.Normal
|
||||
@@ -497,7 +514,9 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
};
|
||||
}
|
||||
|
||||
#region [ARCHIVED] GetOrdersByDateRange — تکراری: GetAllUserOrderByFilter همین قابلیت + فیلترهای بیشتر دارد
|
||||
[RequiresPermission(PermissionNames.ReportsView)]
|
||||
[Obsolete("ARCHIVED: Use GetAllUserOrderByFilter instead — has date range + more filters")]
|
||||
public override async Task<GetOrdersByDateRangeResponse> GetOrdersByDateRange(GetOrdersByDateRangeRequest request, ServerCallContext context)
|
||||
{
|
||||
var pageNumber = request.PageNumber > 0 ? request.PageNumber : 1;
|
||||
@@ -551,6 +570,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
Orders = { orders }
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
[RequiresPermission(PermissionNames.OrdersUpdate)]
|
||||
public override async Task<ApplyDiscountToOrderResponse> ApplyDiscountToOrder(ApplyDiscountToOrderRequest request, ServerCallContext context)
|
||||
@@ -624,17 +644,21 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
|
||||
// ============= Customer-specific Methods =============
|
||||
|
||||
#region [ARCHIVED] CreateNewOrderForCustomer & SubmitOrderForCustomer — Stub خالی: مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد
|
||||
[Obsolete("ARCHIVED: Empty stub — customer order flow uses SubmitShopBuyOrder")]
|
||||
public override async Task<CreateNewUserOrderResponse> CreateNewOrderForCustomer(CreateNewUserOrderRequest request, ServerCallContext context)
|
||||
{
|
||||
// For now, return empty response - will be implemented properly later
|
||||
// [ARCHIVED] Empty stub — مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد
|
||||
return new CreateNewUserOrderResponse();
|
||||
}
|
||||
|
||||
[Obsolete("ARCHIVED: Empty stub — customer order flow uses SubmitShopBuyOrder")]
|
||||
public override async Task<SubmitShopBuyOrderResponse> SubmitOrderForCustomer(SubmitShopBuyOrderRequest request, ServerCallContext context)
|
||||
{
|
||||
// For now, return empty response - will be implemented properly later
|
||||
// [ARCHIVED] Empty stub — مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد
|
||||
return new SubmitShopBuyOrderResponse();
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override async Task<GetAllUserOrderByFilterResponse> GetCustomerOrders(GetAllUserOrderByFilterRequest request, ServerCallContext context)
|
||||
{
|
||||
|
||||
@@ -227,13 +227,21 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
||||
var cycleCount = await _context.ClubMembershipCycles
|
||||
.CountAsync(c => c.UserId == userId, context.CancellationToken);
|
||||
|
||||
// بارگذاری پکیج کاربر برای سقف کیفپول جادویی
|
||||
var currentCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken);
|
||||
var package = currentCycle != null
|
||||
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, context.CancellationToken)
|
||||
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken);
|
||||
var magicMaxDeposit = package?.MagicWalletMaxDeposit ?? 1_000_000_000;
|
||||
|
||||
var response = new GetMagicWalletStatusResponse
|
||||
{
|
||||
WalletMode = (int)wallet.WalletMode,
|
||||
MagicTotalDeposited = wallet.MagicTotalDeposited,
|
||||
MagicTotalCredited = wallet.MagicTotalCredited,
|
||||
MagicMaxDeposit = SystemConstants.MagicWalletMaxDeposit,
|
||||
MagicRemainingDeposit = Math.Max(0, SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited),
|
||||
MagicMaxDeposit = magicMaxDeposit,
|
||||
MagicRemainingDeposit = Math.Max(0, magicMaxDeposit - wallet.MagicTotalDeposited),
|
||||
Balance = wallet.Balance,
|
||||
PurchaseCycleCount = cycleCount
|
||||
};
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
-- =============================================
|
||||
-- Data Migration: Package-Based System
|
||||
-- Date: 2026-02-25
|
||||
-- Description: Seeds golden package and backfills
|
||||
-- existing records with PackageId = 1
|
||||
-- NOTE: This is automatically executed in the EF Migration
|
||||
-- (AddPackageBasedSystem). This file is for reference only.
|
||||
-- =============================================
|
||||
|
||||
USE [YourDatabaseName]; -- Replace with actual DB name
|
||||
GO
|
||||
|
||||
-- 1. Seed Golden Package (Id=1)
|
||||
SET IDENTITY_INSERT [CMS].[Packages] ON;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM [CMS].[Packages] WHERE [Id] = 1)
|
||||
BEGIN
|
||||
INSERT INTO [CMS].[Packages]
|
||||
([Id], [Title], [Description], [ImagePath], [Price],
|
||||
[SortOrder], [IsActive], [IsBasePackage],
|
||||
[SupportsDayaPurchase], [SupportsDirectPurchase],
|
||||
[ActivationFee], [DiscountMultiplier], [MagicWalletMultiplier],
|
||||
[MaxBalancesPerLeg], [MaxNetworkLevel],
|
||||
[MagicWalletMaxDeposit], [MagicWalletMaxCredit],
|
||||
[Created], [IsDeleted])
|
||||
VALUES
|
||||
(1, N'پکیج طلایی', N'پکیج اصلی باشگاه مشتریان کارا بازار سلامت',
|
||||
N'/images/packages/golden.png', 56000000,
|
||||
1, 1, 1,
|
||||
1, 1,
|
||||
25200000, 2.0, 2.5,
|
||||
300, 15,
|
||||
1000000000, 2500000000,
|
||||
GETUTCDATE(), 0);
|
||||
|
||||
PRINT 'Golden Package (Id=1) seeded successfully.';
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
PRINT 'Golden Package (Id=1) already exists. Skipping seed.';
|
||||
END
|
||||
|
||||
SET IDENTITY_INSERT [CMS].[Packages] OFF;
|
||||
GO
|
||||
|
||||
-- 2. Backfill PackageId on commission/network tables
|
||||
UPDATE [CMS].[WeeklyCommissionPools] SET [PackageId] = 1 WHERE [PackageId] = 0;
|
||||
PRINT CONCAT('WeeklyCommissionPools updated: ', @@ROWCOUNT, ' rows');
|
||||
|
||||
UPDATE [CMS].[UserCommissionPayouts] SET [PackageId] = 1 WHERE [PackageId] = 0;
|
||||
PRINT CONCAT('UserCommissionPayouts updated: ', @@ROWCOUNT, ' rows');
|
||||
|
||||
UPDATE [CMS].[NetworkWeeklyBalances] SET [PackageId] = 1 WHERE [PackageId] = 0;
|
||||
PRINT CONCAT('NetworkWeeklyBalances updated: ', @@ROWCOUNT, ' rows');
|
||||
|
||||
UPDATE [CMS].[ClubMembershipCycles] SET [PackageId] = 1 WHERE [PackageId] = 0;
|
||||
PRINT CONCAT('ClubMembershipCycles updated: ', @@ROWCOUNT, ' rows');
|
||||
GO
|
||||
|
||||
-- 3. Backfill ClubMembership First/Last Activation from existing ActivatedAt
|
||||
UPDATE cm SET
|
||||
cm.[FirstActivationDate] = cm.[ActivatedAt],
|
||||
cm.[LastActivationDate] = cm.[ActivatedAt],
|
||||
cm.[FirstPackageId] = 1,
|
||||
cm.[LastPackageId] = 1
|
||||
FROM [CMS].[ClubMemberships] cm
|
||||
WHERE cm.[ActivatedAt] IS NOT NULL
|
||||
AND cm.[FirstActivationDate] IS NULL;
|
||||
|
||||
PRINT CONCAT('ClubMemberships backfilled: ', @@ROWCOUNT, ' rows');
|
||||
GO
|
||||
|
||||
-- 4. Verification Queries
|
||||
SELECT 'Packages' AS [Table], COUNT(*) AS [Total],
|
||||
SUM(CASE WHEN [IsBasePackage] = 1 THEN 1 ELSE 0 END) AS [BasePackages]
|
||||
FROM [CMS].[Packages] WHERE [IsDeleted] = 0;
|
||||
|
||||
SELECT 'WeeklyCommissionPools' AS [Table],
|
||||
COUNT(*) AS [Total],
|
||||
SUM(CASE WHEN [PackageId] = 0 THEN 1 ELSE 0 END) AS [MissingPackageId]
|
||||
FROM [CMS].[WeeklyCommissionPools] WHERE [IsDeleted] = 0;
|
||||
|
||||
SELECT 'ClubMemberships' AS [Table],
|
||||
COUNT(*) AS [Total],
|
||||
SUM(CASE WHEN [FirstPackageId] IS NOT NULL THEN 1 ELSE 0 END) AS [WithFirstPackage],
|
||||
SUM(CASE WHEN [LastActivationDate] IS NOT NULL THEN 1 ELSE 0 END) AS [WithLastActivation]
|
||||
FROM [CMS].[ClubMemberships] WHERE [IsDeleted] = 0;
|
||||
|
||||
PRINT 'Data migration completed successfully.';
|
||||
GO
|
||||
Reference in New Issue
Block a user