feat: Enhance club membership activation and commission pool validation
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m9s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m9s
- Updated AcceptClubMembershipContractCommandHandler to set PackagePurchasedAt to the contract-signing time, ensuring accurate pool charging. - Modified ActivateClubMembershipCommand to allow specifying a PackageId for forced activation, defaulting to the base package if not provided. - Improved ActivateClubMembershipCommandHandler to handle new and existing memberships differently regarding pool charging. - Added validation logic in GetWeeklyCommissionPoolQueryHandler to compare TotalPoolAmount against actual activations. - Introduced validation fields in WeeklyCommissionPoolDto for tracking discrepancies and expected amounts. - Updated stored procedures to ensure accurate calculations based on package purchases and to handle empty pools. - Enhanced protobuf definitions to include validation fields for commission pools and network tree nodes. - Added mapping for new fields in CommissionProfile and NetworkMembershipProfile. - Created DiscountProductProfile for mapping discount product requests and responses.
This commit is contained in:
+48
-1
@@ -221,7 +221,54 @@ public class AcceptClubMembershipContractCommandHandler
|
||||
_context.ClubMembershipHistories.Add(history);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 9. اضافه کردن مبلغ به Pool هفته جاری
|
||||
// 8.5. آپدیت PackagePurchasedAt سایکل جاری به زمان امضای قرارداد
|
||||
// دلیل: Pool در همین لحظه شارژ میشود — Validation باید به همین هفته اشاره کند
|
||||
// سناریوی cross-week: کاربر هفته ۱۸ پکیج خریده، هفته ۱۹ قرارداد امضا کرده
|
||||
// بدون این Fix: Cycle.PackagePurchasedAt = هفته ۱۸ ولی Pool هفته ۱۹ → اختلاف
|
||||
// با این Fix: Cycle.PackagePurchasedAt = هفته ۱۹ = Pool هفته ۱۹ → درست
|
||||
var currentCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, cancellationToken);
|
||||
|
||||
if (currentCycle != null)
|
||||
{
|
||||
currentCycle.PackagePurchasedAt = activationDate;
|
||||
_context.ClubMembershipCycles.Update(currentCycle);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Updated Cycle #{CycleNumber} PackagePurchasedAt to contract-signing time for UserId {UserId}",
|
||||
currentCycle.CycleNumber,
|
||||
userId
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// در صورتی که Cycle وجود ندارد (legacy یا مسیر مستقیم AcceptContract)
|
||||
// Cycle را همینجا بساز تا Validation درست کار کند
|
||||
_logger.LogWarning(
|
||||
"No IsCurrentCycle found for UserId {UserId} at AcceptContract — creating Cycle now",
|
||||
userId
|
||||
);
|
||||
|
||||
var maxCycleNumber = await _context.ClubMembershipCycles
|
||||
.Where(c => c.ClubMembershipId == clubMembership.Id)
|
||||
.MaxAsync(c => (int?)c.CycleNumber, cancellationToken) ?? 0;
|
||||
|
||||
var newCycle = new ClubMembershipCycle
|
||||
{
|
||||
UserId = userId,
|
||||
ClubMembershipId = clubMembership.Id,
|
||||
CycleNumber = maxCycleNumber + 1,
|
||||
PackagePurchasedAt = activationDate,
|
||||
PurchaseMethod = user.PackagePurchaseMethod,
|
||||
PackageAmount = package.Price,
|
||||
PackageId = package.Id,
|
||||
IsCurrentCycle = true
|
||||
};
|
||||
|
||||
_context.ClubMembershipCycles.Add(newCycle);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
|
||||
var weeklyPool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
|
||||
|
||||
+6
@@ -17,4 +17,10 @@ public record ActivateClubMembershipCommand : IRequest<bool>
|
||||
/// فعالسازی اجباری توسط ادمین — بدون بررسی سفارش و کیفپول
|
||||
/// </summary>
|
||||
public bool ForceActivation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پکیج — فقط در ForceActivation استفاده میشود.
|
||||
/// اگر 0 باشد، پکیج پایه (IsBasePackage) استفاده میشود.
|
||||
/// </summary>
|
||||
public long PackageId { get; init; }
|
||||
}
|
||||
|
||||
+61
-39
@@ -170,10 +170,19 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
// بارگذاری پکیج پایه برای فعالسازی اجباری
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException("پکیج پایه یافت نشد");
|
||||
// بارگذاری پکیج — اگر PackageId مشخص شده همان را لود کن، وگرنه پکیج پایه
|
||||
if (request.PackageId > 0)
|
||||
{
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.Id == request.PackageId && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException($"پکیج {request.PackageId} یافت نشد");
|
||||
}
|
||||
else
|
||||
{
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException("پکیج پایه یافت نشد");
|
||||
}
|
||||
}
|
||||
|
||||
// 6. بررسی عضویت فعلی
|
||||
@@ -226,10 +235,11 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
var hasCurrentCycle = await _context.ClubMembershipCycles
|
||||
.AnyAsync(c => c.UserId == user.Id && c.IsCurrentCycle, cancellationToken);
|
||||
|
||||
if (existingMembership.IsActive && hasCurrentCycle)
|
||||
// اگر چرخه فعال دارد — یعنی Activate قبلاً برای این خرید اجرا شده؛ جلوگیری از ثبت Cycle تکراری
|
||||
if (hasCurrentCycle)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"User {UserId} is already an active club member with a running cycle",
|
||||
"User {UserId} already has a running cycle — skipping duplicate activation",
|
||||
user.Id
|
||||
);
|
||||
return true;
|
||||
@@ -307,48 +317,60 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری
|
||||
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
|
||||
var weeklyPool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
|
||||
|
||||
if (weeklyPool == null)
|
||||
// عضو جدید (isNewMembership=true): Pool توسط AcceptClubMembershipContract شارژ میشود
|
||||
// چون کاربر قبل از امضای قرارداد IsActive=false است و قرارداد فقط یکبار امضا میشود.
|
||||
// خرید مجدد (isNewMembership=false): قرارداد مجدد امضا نمیشود — Pool همینجا شارژ میشود.
|
||||
if (!isNewMembership)
|
||||
{
|
||||
// ایجاد Pool جدید برای این هفته × این پکیج
|
||||
weeklyPool = new WeeklyCommissionPool
|
||||
{
|
||||
WeekDefinitionId = currentWeekDefinitionId,
|
||||
PackageId = package.Id,
|
||||
TotalPoolAmount = activationFeeValue,
|
||||
TotalBalances = 0, // در CalculateWeeklyBalances محاسبه میشه
|
||||
ValuePerBalance = 0, // در CalculateWeeklyCommissionPool محاسبه میشه
|
||||
IsCalculated = false,
|
||||
CalculatedAt = null
|
||||
};
|
||||
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
|
||||
var weeklyPool = await _context.WeeklyCommissionPools
|
||||
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
|
||||
|
||||
await _context.WeeklyCommissionPools.AddAsync(weeklyPool, cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created new WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId} with initial amount: {Amount}",
|
||||
currentWeekDefinitionId,
|
||||
giftValue
|
||||
);
|
||||
if (weeklyPool == null)
|
||||
{
|
||||
// ایجاد Pool جدید برای این هفته × این پکیج
|
||||
weeklyPool = new WeeklyCommissionPool
|
||||
{
|
||||
WeekDefinitionId = currentWeekDefinitionId,
|
||||
PackageId = package.Id,
|
||||
TotalPoolAmount = activationFeeValue,
|
||||
TotalBalances = 0,
|
||||
ValuePerBalance = 0,
|
||||
IsCalculated = false,
|
||||
CalculatedAt = null
|
||||
};
|
||||
|
||||
await _context.WeeklyCommissionPools.AddAsync(weeklyPool, cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created new WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId} with initial amount: {Amount}",
|
||||
currentWeekDefinitionId,
|
||||
activationFeeValue
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
weeklyPool.TotalPoolAmount += activationFeeValue;
|
||||
_context.WeeklyCommissionPools.Update(weeklyPool);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Added {Amount} to existing WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId}. New total: {NewTotal}",
|
||||
activationFeeValue,
|
||||
currentWeekDefinitionId,
|
||||
weeklyPool.TotalPoolAmount
|
||||
);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// اضافه کردن به Pool موجود
|
||||
weeklyPool.TotalPoolAmount += activationFeeValue;
|
||||
_context.WeeklyCommissionPools.Update(weeklyPool);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Added {Amount} to existing WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId}. New total: {NewTotal}",
|
||||
activationFeeValue,
|
||||
currentWeekDefinitionId,
|
||||
weeklyPool.TotalPoolAmount
|
||||
"New member — Pool charge deferred to AcceptClubMembershipContract for UserId {UserId}",
|
||||
user.Id
|
||||
);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 9. اعمال ویژگیهای پکیج — DIFF/تفاضل (Q20)
|
||||
await ApplyFeatureDiffAsync(user.Id, entity.Id, package.Id, isNewMembership, activationDate, cancellationToken);
|
||||
|
||||
|
||||
+9
-5
@@ -266,12 +266,16 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
|
||||
|
||||
var count = 0;
|
||||
|
||||
// اگر فرزند در این هفته پکیج خریده (Cycle جدید ساخته شده)، 1 امتیاز
|
||||
// ⚠️ از ClubMembershipCycle.PackagePurchasedAt استفاده میکنیم (نه ActivatedAt که overwrite میشد)
|
||||
var currentCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(x => x.UserId == child.Id && x.IsCurrentCycle, cancellationToken);
|
||||
// ⭐ بر اساس بازه تاریخی هفته، نه IsCurrentCycle
|
||||
// IsCurrentCycle فقط آخرین خرید را نشان میدهد — برای تاریخچهای (هفتههای گذشته)
|
||||
// باید از PackagePurchasedAt استفاده کرد که تاریخ دقیق خرید را نگه میدارد
|
||||
var hadCycleInWeek = await _context.ClubMembershipCycles
|
||||
.AnyAsync(x => x.UserId == child.Id
|
||||
&& x.PackagePurchasedAt >= startDate
|
||||
&& x.PackagePurchasedAt <= endDate,
|
||||
cancellationToken);
|
||||
|
||||
if (currentCycle?.PackagePurchasedAt >= startDate && currentCycle?.PackagePurchasedAt <= endDate)
|
||||
if (hadCycleInWeek)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
+56
-1
@@ -41,6 +41,61 @@ public class GetWeeklyCommissionPoolQueryHandler : IRequestHandler<GetWeeklyComm
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return pool ?? new WeeklyCommissionPoolDto();
|
||||
pool ??= new WeeklyCommissionPoolDto();
|
||||
|
||||
// ── Q29: اعتبارسنجی TotalPoolAmount در برابر فعالسازیهای واقعی ──
|
||||
await ValidatePoolAmountAsync(pool, request, cancellationToken);
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TotalPoolAmount استخر را با تعداد فعالسازیهای ثبتشده (AcceptContract + ActivateClubMembership) مقایسه میکند.
|
||||
/// نتیجه در فیلدهای Validation... ذخیره میشود.
|
||||
/// </summary>
|
||||
private async Task ValidatePoolAmountAsync(WeeklyCommissionPoolDto pool, GetWeeklyCommissionPoolQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var packageId = pool.PackageId > 0 ? pool.PackageId : request.PackageId;
|
||||
if (packageId is null or 0)
|
||||
return;
|
||||
|
||||
var weekDef = _weekDefinitionRepository.GetWeekById(request.WeekDefinitionId);
|
||||
if (weekDef is null)
|
||||
return;
|
||||
|
||||
var activationFee = await _context.Packages
|
||||
.AsNoTracking()
|
||||
.Where(p => p.Id == packageId.Value)
|
||||
.Select(p => p.ActivationFee)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (activationFee <= 0)
|
||||
return;
|
||||
|
||||
// شمارش Cycleهایی که Pool آنها واقعاً شارژ شده:
|
||||
// - عضو جدید: Pool توسط AcceptContract شارژ میشود → UserContract وجود دارد
|
||||
// - خرید مجدد: Pool در ActivateClubMembership شارژ میشود → IsCurrentCycle نبوده (قبلاً active بوده)
|
||||
// - نباید از IsActive استفاده شود: وضعیت تاریخی نیست (عضو ممکن است بعداً غیرفعال شده باشد)
|
||||
//
|
||||
// قانون: Cycle شارژ شده = یا قرارداد دارد (عضو جدید) یا IsActive قبلاً true بوده (خرید مجدد)
|
||||
// سادهترین معادل: Cycle هایی که ClubMembership.ActivatedAt در بازه هفته است (زمان واقعی شارژ Pool)
|
||||
var totalActivations = await _context.ClubMembershipCycles
|
||||
.AsNoTracking()
|
||||
.Where(c => c.PackageId == packageId.Value
|
||||
&& c.PackagePurchasedAt >= weekDef.StartDate
|
||||
&& c.PackagePurchasedAt < weekDef.EndDate
|
||||
&& _context.UserContracts.Any(uc =>
|
||||
uc.UserId == c.UserId
|
||||
&& _context.Contracts.Any(con =>
|
||||
con.Id == uc.ContractId
|
||||
&& con.Type == ContractType.ClubMembership)))
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
var expectedAmount = (long)totalActivations * activationFee;
|
||||
|
||||
pool.ValidationActivationCount = totalActivations;
|
||||
pool.ValidationExpectedAmount = expectedAmount;
|
||||
pool.ValidationDiscrepancy = pool.TotalPoolAmount - expectedAmount;
|
||||
pool.ValidationIsValid = pool.TotalPoolAmount == expectedAmount;
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -16,4 +16,14 @@ public class WeeklyCommissionPoolDto
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public long PackageId { get; set; }
|
||||
public string PackageTitle { get; set; } = string.Empty;
|
||||
|
||||
// ── Validation Fields (Q29) ──
|
||||
/// <summary>آیا TotalPoolAmount با تعداد فعالسازیهای ثبتشده در دیتابیس مطابقت دارد؟</summary>
|
||||
public bool ValidationIsValid { get; set; } = true;
|
||||
/// <summary>مبلغ مورد انتظار بر اساس شمارش فعالسازیها × ActivationFee</summary>
|
||||
public long ValidationExpectedAmount { get; set; }
|
||||
/// <summary>اختلاف: TotalPoolAmount - ValidationExpectedAmount</summary>
|
||||
public long ValidationDiscrepancy { get; set; }
|
||||
/// <summary>تعداد فعالسازیهای شناساییشده برای این هفته و پکیج</summary>
|
||||
public int ValidationActivationCount { get; set; }
|
||||
}
|
||||
|
||||
+27
@@ -1,3 +1,4 @@
|
||||
using CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership;
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
@@ -14,15 +15,18 @@ public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPay
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
private readonly ISender _sender;
|
||||
private readonly ILogger<CreateManualPaymentCommandHandler> _logger;
|
||||
|
||||
public CreateManualPaymentCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUser,
|
||||
ISender sender,
|
||||
ILogger<CreateManualPaymentCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_currentUser = currentUser;
|
||||
_sender = sender;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -152,6 +156,29 @@ public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPay
|
||||
// 10. ذخیره همه تغییرات
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 11. فعالسازی باشگاه مشتریان — ساخت Cycle و آمادهسازی برای AcceptContract
|
||||
// این مرحله تضمین میکند که:
|
||||
// الف) ClubMembershipCycle ساخته شود تا والدین امتیاز «عضو جدید» بگیرند
|
||||
// ب) Pool توسط AcceptContract (که کاربر در FO امضا میکند) شارژ شود
|
||||
try
|
||||
{
|
||||
await _sender.Send(new ActivateClubMembershipCommand
|
||||
{
|
||||
UserId = request.UserId,
|
||||
ForceActivation = true,
|
||||
PackageId = package.Id
|
||||
}, cancellationToken);
|
||||
}
|
||||
catch (Exception activateEx)
|
||||
{
|
||||
// عضویت باشگاه اختیاری است — خطا نباید پرداخت دستی را برگرداند
|
||||
_logger.LogWarning(
|
||||
activateEx,
|
||||
"Club membership activation failed after manual payment for UserId {UserId} — payment recorded successfully",
|
||||
request.UserId
|
||||
);
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Manual membership payment created successfully. " +
|
||||
"ManualPaymentId: {Id}, UserId: {UserId}, TransactionId: {TransactionId}, " +
|
||||
|
||||
+8
-2
@@ -118,7 +118,10 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
||||
ActivationWeekDefinitionId = reader.IsDBNull(reader.GetOrdinal("ActivationWeekDefinitionId")) ? null : reader.GetInt64(reader.GetOrdinal("ActivationWeekDefinitionId")),
|
||||
ActivationWeekDisplayName = reader.IsDBNull(reader.GetOrdinal("ActivationWeekDisplayName")) ? null : reader.GetString(reader.GetOrdinal("ActivationWeekDisplayName")),
|
||||
IsActivatedInTargetWeek = Convert.ToBoolean(reader.GetValue(reader.GetOrdinal("IsActivatedInTargetWeek"))),
|
||||
UserCreated = new DateTimeOffset(reader.GetDateTime(reader.GetOrdinal("UserCreated")))
|
||||
UserCreated = new DateTimeOffset(reader.GetDateTime(reader.GetOrdinal("UserCreated"))),
|
||||
IsNewActivation = reader.IsDBNull(reader.GetOrdinal("IsNewActivation")) ? null : (bool?)(reader.GetInt32(reader.GetOrdinal("IsNewActivation")) == 1),
|
||||
PackageName = reader.IsDBNull(reader.GetOrdinal("PackageName")) ? null : reader.GetString(reader.GetOrdinal("PackageName")),
|
||||
PackageId = reader.IsDBNull(reader.GetOrdinal("PackageId")) ? null : reader.GetInt64(reader.GetOrdinal("PackageId"))
|
||||
};
|
||||
|
||||
results.Add(node);
|
||||
@@ -160,7 +163,10 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
||||
ActivationWeekDefinitionId = flatNode.ActivationWeekDefinitionId,
|
||||
ActivationWeekDisplayName = flatNode.ActivationWeekDisplayName,
|
||||
IsActivatedInTargetWeek = flatNode.IsActivatedInTargetWeek,
|
||||
UserCreated = flatNode.UserCreated
|
||||
UserCreated = flatNode.UserCreated,
|
||||
IsNewActivation = flatNode.IsNewActivation,
|
||||
PackageName = flatNode.PackageName,
|
||||
PackageId = flatNode.PackageId
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -47,7 +47,19 @@ public class NetworkTreeDto
|
||||
/// تاریخ ایجاد کاربر
|
||||
/// </summary>
|
||||
public DateTimeOffset UserCreated { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// آیا فعالسازی در هفته هدف اولین بار بوده؟ null = بدون Cycle
|
||||
/// </summary>
|
||||
public bool? IsNewActivation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نام پکیج موثر (هفته هدف یا پکیج فعلی)
|
||||
/// </summary>
|
||||
public string? PackageName { get; set; }
|
||||
|
||||
public long? PackageId { get; set; }
|
||||
|
||||
public NetworkTreeDto? LeftChild { get; set; }
|
||||
public NetworkTreeDto? RightChild { get; set; }
|
||||
}
|
||||
|
||||
+13
@@ -20,6 +20,19 @@ public class NetworkTreeNodeDto
|
||||
public string? ActivationWeekDisplayName { get; set; }
|
||||
public bool IsActivatedInTargetWeek { get; set; }
|
||||
public DateTimeOffset UserCreated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// آیا فعالسازی در هفته هدف اولین بار بوده (true) یا خرید مجدد (false)؟
|
||||
/// null یعنی Cycleای ندارد
|
||||
/// </summary>
|
||||
public bool? IsNewActivation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نام پکیج موثر (هفته هدف یا پکیج فعلی)
|
||||
/// </summary>
|
||||
public string? PackageName { get; set; }
|
||||
|
||||
public long? PackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نام کامل کاربر
|
||||
|
||||
Reference in New Issue
Block a user