feat: implement user wallet charging and transaction logging for Daya loan processing

This commit is contained in:
masoodafar-web
2025-12-19 23:14:42 +03:30
parent 8b784101be
commit 9864302bab
9 changed files with 352 additions and 51 deletions
@@ -84,7 +84,10 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
{ {
var oldPayoutIds = oldPayouts.Select(p => p.Id).ToList(); var oldPayoutIds = oldPayouts.Select(p => p.Id).ToList();
// ⭐ اول باید تاریخچه‌ها حذف بشن (به خاطر FK constraint) // ⭐ ۱. برگرداندن NetworkBalance به حالت قبل (Revert)
await RevertUserWalletsAsync(oldPayoutIds, cancellationToken);
// ⭐ ۲. حذف تاریخچه‌ها (به خاطر FK constraint)
var oldHistories = await _context.CommissionPayoutHistories var oldHistories = await _context.CommissionPayoutHistories
.Where(h => oldPayoutIds.Contains(h.UserCommissionPayoutId)) .Where(h => oldPayoutIds.Contains(h.UserCommissionPayoutId))
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
@@ -94,7 +97,7 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
_context.CommissionPayoutHistories.RemoveRange(oldHistories); _context.CommissionPayoutHistories.RemoveRange(oldHistories);
} }
// بعد پرداخت‌ها حذف می‌شن // ⭐ ۳. حذف پرداخت‌ها
_context.UserCommissionPayouts.RemoveRange(oldPayouts); _context.UserCommissionPayouts.RemoveRange(oldPayouts);
await _context.SaveChangesAsync(cancellationToken); await _context.SaveChangesAsync(cancellationToken);
} }
@@ -164,8 +167,141 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
await _context.CommissionPayoutHistories.AddRangeAsync(historyList, cancellationToken); await _context.CommissionPayoutHistories.AddRangeAsync(historyList, cancellationToken);
await _context.SaveChangesAsync(cancellationToken); await _context.SaveChangesAsync(cancellationToken);
// ⭐ شارژ کیف پول کاربران (NetworkBalance) و ثبت لاگ
await ChargeUserWalletsAsync(payouts, request.WeekDefinitionId, cancellationToken);
} }
return existingPool.Id; return existingPool.Id;
} }
/// <summary>
/// شارژ کیف پول کاربران با مبلغ کمیسیون (به NetworkBalance اضافه می‌شود)
/// و ثبت لاگ تغییرات کیف پول
/// </summary>
private async Task ChargeUserWalletsAsync(
List<UserCommissionPayout> payouts,
long weekDefinitionId,
CancellationToken cancellationToken)
{
var userIds = payouts.Select(p => p.UserId).Distinct().ToList();
// دریافت کیف پول‌های موجود
var existingWallets = await _context.UserWallets
.Where(w => userIds.Contains(w.UserId))
.ToDictionaryAsync(w => w.UserId, cancellationToken);
var newWallets = new List<UserWallet>();
var walletLogs = new List<UserWalletChangeLog>();
foreach (var payout in payouts)
{
if (payout.TotalAmount <= 0) continue;
UserWallet wallet;
// پیدا کردن یا ایجاد کیف پول
if (existingWallets.TryGetValue(payout.UserId, out var existingWallet))
{
wallet = existingWallet;
}
else
{
// ایجاد کیف پول جدید
wallet = new UserWallet
{
UserId = payout.UserId,
Balance = 0,
NetworkBalance = 0,
DiscountBalance = 0
};
newWallets.Add(wallet);
existingWallets[payout.UserId] = wallet; // برای جلوگیری از تکرار
}
}
// ذخیره کیف پول‌های جدید
if (newWallets.Any())
{
await _context.UserWallets.AddRangeAsync(newWallets, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
// شارژ کیف پول‌ها و ثبت لاگ
foreach (var payout in payouts)
{
if (payout.TotalAmount <= 0) continue;
var wallet = existingWallets[payout.UserId];
// ذخیره موجودی قبلی برای لاگ
var previousNetworkBalance = wallet.NetworkBalance;
// شارژ NetworkBalance
wallet.NetworkBalance += payout.TotalAmount;
// ایجاد لاگ تغییر کیف پول
var walletLog = new UserWalletChangeLog
{
WalletId = wallet.Id,
CurrentBalance = wallet.Balance,
ChangeValue = 0, // Balance اصلی تغییر نمی‌کند
CurrentNetworkBalance = wallet.NetworkBalance,
ChangeNerworkValue = payout.TotalAmount,
CurrentDiscountBalance = wallet.DiscountBalance,
ChangeDiscountValue = 0,
IsIncrease = true,
RefrenceId = payout.Id // ارجاع به UserCommissionPayout
};
walletLogs.Add(walletLog);
}
// ذخیره تغییرات کیف پول و لاگ‌ها
await _context.UserWalletChangeLogs.AddRangeAsync(walletLogs, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
/// <summary>
/// برگرداندن NetworkBalance به حالت قبل از شارژ کمیسیون
/// این متد قبل از محاسبه مجدد (ForceRecalculate) اجرا می‌شود
/// </summary>
private async Task RevertUserWalletsAsync(
List<long> oldPayoutIds,
CancellationToken cancellationToken)
{
// پیدا کردن لاگ‌های کیف پول مرتبط با پرداخت‌های قبلی
var oldWalletLogs = await _context.UserWalletChangeLogs
.Where(l => l.RefrenceId.HasValue && oldPayoutIds.Contains(l.RefrenceId.Value))
.ToListAsync(cancellationToken);
if (!oldWalletLogs.Any()) return;
// پیدا کردن کیف پول‌های مرتبط
var walletIds = oldWalletLogs.Select(l => l.WalletId).Distinct().ToList();
var wallets = await _context.UserWallets
.Where(w => walletIds.Contains(w.Id))
.ToDictionaryAsync(w => w.Id, cancellationToken);
// برگرداندن NetworkBalance
foreach (var log in oldWalletLogs)
{
if (wallets.TryGetValue(log.WalletId, out var wallet))
{
// کم کردن مبلغ شارژ شده قبلی از NetworkBalance
wallet.NetworkBalance -= log.ChangeNerworkValue;
// اطمینان از منفی نشدن
if (wallet.NetworkBalance < 0)
{
wallet.NetworkBalance = 0;
}
}
}
// حذف لاگ‌های قبلی
_context.UserWalletChangeLogs.RemoveRange(oldWalletLogs);
await _context.SaveChangesAsync(cancellationToken);
}
} }
@@ -11,6 +11,11 @@ public class CheckDayaLoanStatusCommandHandler : IRequestHandler<CheckDayaLoanSt
private readonly IDayaLoanApiService _dayaApiService; private readonly IDayaLoanApiService _dayaApiService;
private readonly ILogger<CheckDayaLoanStatusCommandHandler> _logger; private readonly ILogger<CheckDayaLoanStatusCommandHandler> _logger;
/// <summary>
/// مبلغ وام دایا - 56 میلیون ریال
/// </summary>
private const long DAYA_LOAN_AMOUNT = 56_000_000;
public CheckDayaLoanStatusCommandHandler( public CheckDayaLoanStatusCommandHandler(
IApplicationDbContext context, IApplicationDbContext context,
IDayaLoanApiService dayaApiService, IDayaLoanApiService dayaApiService,
@@ -48,9 +53,24 @@ public class CheckDayaLoanStatusCommandHandler : IRequestHandler<CheckDayaLoanSt
if (existingContract != null) if (existingContract != null)
{ {
var previousStatus = existingContract.Status;
existingContract.LastCheckDate = DateTime.Now; existingContract.LastCheckDate = DateTime.Now;
existingContract.Status = dayaResult.Status; existingContract.Status = dayaResult.Status;
existingContract.ContractNumber = dayaResult.ContractNumber; existingContract.ContractNumber = dayaResult.ContractNumber;
// بررسی تغییر وضعیت به PendingReceive یا Received
// فقط اگر قبلاً پردازش نشده باشد
if (!existingContract.IsProcessed &&
!string.IsNullOrEmpty(dayaResult.ContractNumber) &&
(dayaResult.Status == DayaLoanStatus.PendingReceive || dayaResult.Status == DayaLoanStatus.Received))
{
await ProcessDayaLoanReceivedAsync(existingContract.UserId, dayaResult.ContractNumber, cancellationToken);
existingContract.IsProcessed = true;
_logger.LogInformation(
"Daya loan processed for User {UserId}, ContractNumber: {ContractNumber}, Amount: {Amount}",
existingContract.UserId, dayaResult.ContractNumber, DAYA_LOAN_AMOUNT);
}
} }
else else
{ {
@@ -59,6 +79,20 @@ public class CheckDayaLoanStatusCommandHandler : IRequestHandler<CheckDayaLoanSt
if (user != null) if (user != null)
{ {
var isProcessed = false;
// اگر وضعیت PendingReceive یا Received بود و قرارداد دارد، فوری پردازش کن
if (!string.IsNullOrEmpty(dayaResult.ContractNumber) &&
(dayaResult.Status == DayaLoanStatus.PendingReceive || dayaResult.Status == DayaLoanStatus.Received))
{
await ProcessDayaLoanReceivedAsync(user.Id, dayaResult.ContractNumber, cancellationToken);
isProcessed = true;
_logger.LogInformation(
"Daya loan processed for new contract - User {UserId}, ContractNumber: {ContractNumber}, Amount: {Amount}",
user.Id, dayaResult.ContractNumber, DAYA_LOAN_AMOUNT);
}
var newContract = new DayaLoanContract var newContract = new DayaLoanContract
{ {
UserId = user.Id, UserId = user.Id,
@@ -66,7 +100,7 @@ public class CheckDayaLoanStatusCommandHandler : IRequestHandler<CheckDayaLoanSt
Status = dayaResult.Status, Status = dayaResult.Status,
ContractNumber = dayaResult.ContractNumber, ContractNumber = dayaResult.ContractNumber,
LastCheckDate = DateTime.Now, LastCheckDate = DateTime.Now,
IsProcessed = false IsProcessed = isProcessed
}; };
await _context.DayaLoanContracts.AddAsync(newContract, cancellationToken); await _context.DayaLoanContracts.AddAsync(newContract, cancellationToken);
@@ -105,4 +139,101 @@ public class CheckDayaLoanStatusCommandHandler : IRequestHandler<CheckDayaLoanSt
SuccessCount = results.Count(r => r.ContractNumber != null) SuccessCount = results.Count(r => r.ContractNumber != null)
}; };
} }
/// <summary>
/// پردازش دریافت وام دایا برای کاربر
/// 1. شارژ کیف پول (Balance + DiscountBalance)
/// 2. ثبت تراکنش
/// 3. ثبت لاگ کیف پول
/// 4. به‌روزرسانی وضعیت کاربر
/// </summary>
private async Task ProcessDayaLoanReceivedAsync(long userId, string contractNumber, CancellationToken cancellationToken)
{
// پیدا کردن کاربر
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
if (user == null)
{
_logger.LogWarning("User {UserId} not found for Daya loan processing", userId);
return;
}
// بررسی اینکه قبلاً دریافت نکرده باشد
if (user.HasReceivedDayaCredit)
{
_logger.LogWarning("User {UserId} has already received Daya credit", userId);
return;
}
// پیدا کردن یا ایجاد کیف پول کاربر
var wallet = await _context.UserWallets
.FirstOrDefaultAsync(w => w.UserId == userId, cancellationToken);
if (wallet == null)
{
wallet = new UserWallet
{
UserId = userId,
Balance = 0,
NetworkBalance = 0,
DiscountBalance = 0
};
await _context.UserWallets.AddAsync(wallet, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
// 1. شارژ کیف پول - هم موجودی اصلی و هم موجودی تخفیف
var previousBalance = wallet.Balance;
var previousDiscountBalance = wallet.DiscountBalance;
wallet.Balance += DAYA_LOAN_AMOUNT;
wallet.DiscountBalance += DAYA_LOAN_AMOUNT;
// 2. ثبت تراکنش - RefId = شماره قرارداد، Status = 0 (Success)، Type = 2 (DepositExternal1)
var transaction = new Transaction
{
Amount = DAYA_LOAN_AMOUNT,
Description = $"شارژ کیف پول از وام دایا - قرارداد {contractNumber}",
PaymentStatus = PaymentStatus.Success, // 0
PaymentDate = DateTime.Now,
RefId = contractNumber,
Type = TransactionType.DepositExternal1 // 2
};
await _context.Transactions.AddAsync(transaction, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
// 3. ثبت لاگ کیف پول با شناسه تراکنش
var walletLog = new UserWalletChangeLog
{
WalletId = wallet.Id,
CurrentBalance = wallet.Balance,
ChangeValue = DAYA_LOAN_AMOUNT,
CurrentNetworkBalance = wallet.NetworkBalance,
ChangeNerworkValue = 0,
CurrentDiscountBalance = wallet.DiscountBalance,
ChangeDiscountValue = DAYA_LOAN_AMOUNT,
IsIncrease = true,
RefrenceId = transaction.Id
};
await _context.UserWalletChangeLogs.AddAsync(walletLog, cancellationToken);
// 4. به‌روزرسانی وضعیت کاربر
user.HasReceivedDayaCredit = true;
user.DayaCreditReceivedAt = DateTime.Now;
user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan; // 1
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Daya loan fully processed for User {UserId}: " +
"Wallet Balance {PreviousBalance} -> {NewBalance}, " +
"DiscountBalance {PreviousDiscountBalance} -> {NewDiscountBalance}, " +
"Transaction Id: {TransactionId}",
userId, previousBalance, wallet.Balance,
previousDiscountBalance, wallet.DiscountBalance,
transaction.Id);
}
} }
@@ -23,5 +23,5 @@ public record GetNetworkTreeQuery : IRequest<NetworkTreeDto?>
/// <summary> /// <summary>
/// فیلتر براساس شماره هفته فعالسازی (مثال: 2025-W05) /// فیلتر براساس شماره هفته فعالسازی (مثال: 2025-W05)
/// </summary> /// </summary>
public string? ActivationWeekNumber { get; init; } public long? ActivationWeekDefinitionId { get; init; }
} }
@@ -42,19 +42,21 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
} }
// محاسبه شماره هفته فعال‌سازی // محاسبه شماره هفته فعال‌سازی
string? activationWeekNumber = null; long? activationWeekDefinitionId = null;
string? activationWeekDisplayName = null; string? activationWeekDisplayName = null;
bool isActivatedInTargetWeek = false; bool isActivatedInTargetWeek = false;
if (user.ClubMembership?.ActivatedAt != null) if (user.ClubMembership?.ActivatedAt != null)
{ {
activationWeekNumber = CalculateWeekNumber(user.ClubMembership.ActivatedAt.Value); // activationWeekDefinitionId = CalculateWeekNumber(user.ClubMembership.ActivatedAt.Value);
activationWeekDisplayName = _weekDefinitionRepository.GetDisplayNameByGregorianWeekNumber(activationWeekNumber); var week = _weekDefinitionRepository.GetWeekByDate(user.ClubMembership.ActivatedAt.Value);
activationWeekDefinitionId = week.Id;
activationWeekDisplayName = week.DisplayName;
// بررسی آیا در هفته هدف فعال شده است // بررسی آیا در هفته هدف فعال شده است
if (!string.IsNullOrEmpty(request.ActivationWeekNumber)) if (request.ActivationWeekDefinitionId!=null)
{ {
isActivatedInTargetWeek = activationWeekNumber == request.ActivationWeekNumber; isActivatedInTargetWeek = activationWeekDefinitionId == request.ActivationWeekDefinitionId;
} }
} }
@@ -68,7 +70,7 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
CurrentDepth = currentDepth, CurrentDepth = currentDepth,
ClubActivatedAt = user.ClubMembership?.ActivatedAt, ClubActivatedAt = user.ClubMembership?.ActivatedAt,
IsClubActive = user.ClubMembership?.IsActive ?? false, IsClubActive = user.ClubMembership?.IsActive ?? false,
ActivationWeekNumber = activationWeekNumber, ActivationWeekDefinitionId = activationWeekDefinitionId,
ActivationWeekDisplayName = activationWeekDisplayName, ActivationWeekDisplayName = activationWeekDisplayName,
IsActivatedInTargetWeek = isActivatedInTargetWeek, IsActivatedInTargetWeek = isActivatedInTargetWeek,
UserCreated = user.Created UserCreated = user.Created
@@ -123,18 +125,19 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
/// <summary> /// <summary>
/// محاسبه شماره هفته از تاریخ /// محاسبه شماره هفته از تاریخ
/// </summary> /// </summary>
private string CalculateWeekNumber(DateTime date) // private long CalculateWeekNumber(DateTime date)
{ // {
// First try to get from repository cache // // First try to get from repository cache
var weekDef = _weekDefinitionRepository.GetWeekByDate(date); // var weekDef = _weekDefinitionRepository.GetWeekByDate(date);
if (weekDef != null) // if (weekDef != null)
{ // {
return $"{weekDef.GregorianYear}-W{weekDef.GregorianWeekNumber:D2}"; // return weekDef.Id;
} // }
//
// Fallback: use repository's calculation method // // Fallback: use repository's calculation method
return _weekDefinitionRepository.CalculateGregorianWeekNumber(date); // // return _weekDefinitionRepository.CalculateGregorianWeekNumber(date);
} // return 0;
// }
@@ -25,7 +25,7 @@ public class NetworkTreeDto
/// <summary> /// <summary>
/// شماره هفته فعال‌سازی /// شماره هفته فعال‌سازی
/// </summary> /// </summary>
public string? ActivationWeekNumber { get; set; } public long? ActivationWeekDefinitionId { get; set; }
/// <summary> /// <summary>
/// نام نمایشی هفته فعال‌سازی (مثل: هفته یکم) /// نام نمایشی هفته فعال‌سازی (مثل: هفته یکم)
@@ -10,6 +10,7 @@ public class
{ {
private readonly IApplicationDbContext _context; private readonly IApplicationDbContext _context;
private readonly ILogger<SubmitShopBuyOrderCommandHandler> _logger; private readonly ILogger<SubmitShopBuyOrderCommandHandler> _logger;
private float _vatRate;
public SubmitShopBuyOrderCommandHandler( public SubmitShopBuyOrderCommandHandler(
IApplicationDbContext context, IApplicationDbContext context,
@@ -44,7 +45,8 @@ public class
} }
else if (cartItem.Product.RemainingCount < cartItem.Count) else if (cartItem.Product.RemainingCount < cartItem.Count)
{ {
insufficientStockProducts.Add((cartItem.Product.Title, cartItem.Count, cartItem.Product.RemainingCount)); insufficientStockProducts.Add((cartItem.Product.Title, cartItem.Count,
cartItem.Product.RemainingCount));
} }
} }
@@ -60,7 +62,28 @@ public class
throw new Exception($"موجودی محصولات زیر کافی نیست: {string.Join(" | ", messages)}"); throw new Exception($"موجودی محصولات زیر کافی نیست: {string.Join(" | ", messages)}");
} }
if (user.UserCarts.Sum(s => s.Count * s.Product.Price) != request.TotalAmount) long finalAmount = 0;
var vatIsEnable =
_context.SystemConfigurations
.FirstOrDefault(f => f.Scope == ConfigurationScope.VAT && f.Key == "IsEnabled")?.Value ??
"0";
if (vatIsEnable=="1")
{
_vatRate = float.Parse(
_context.SystemConfigurations
.FirstOrDefault(f => f.Scope == ConfigurationScope.VAT && f.Key == "Shop.VAT")?.Value ??
throw new InvalidOperationException());
finalAmount = AddVAT(user.UserCarts.Sum(s => s.Count * s.Product.Price));
_logger.LogInformation(
"Calculating final amount with VAT. Base Amount: {BaseAmount}, VAT Rate: {VATRate}, Final Amount: {FinalAmount}",
user.UserCarts.Sum(s => s.Count * s.Product.Price), _vatRate, finalAmount);
}
else
{
finalAmount = user.UserCarts.Sum(s => s.Count * s.Product.Price);
}
if (finalAmount != request.TotalAmount)
throw new Exception("مبلغ سفارش با مجموع سبد خرید مطابقت ندارد."); throw new Exception("مبلغ سفارش با مجموع سبد خرید مطابقت ندارد.");
@@ -124,7 +147,7 @@ public class
{ {
ProductId = s.ProductId, ProductId = s.ProductId,
Count = s.Count, Count = s.Count,
UnitPrice = s.Product.Price, UnitPrice =vatIsEnable=="1" ?AddVAT(s.Product.Price): s.Product.Price,
OrderId = newOrder.Id OrderId = newOrder.Id
}); });
await _context.FactorDetails.AddRangeAsync(factorDetailsList, cancellationToken); await _context.FactorDetails.AddRangeAsync(factorDetailsList, cancellationToken);
@@ -145,11 +168,17 @@ public class
var finalResult = new SubmitShopBuyOrderResponseDto() var finalResult = new SubmitShopBuyOrderResponseDto()
{ {
Id = newOrder.Id, Id = newOrder.Id,
}; };
return finalResult; return finalResult;
} }
public long CalculateVAT(long amount) => (long)(amount * _vatRate);
/// <summary>
/// محاسبه قیمت با احتساب مالیات
/// </summary>
public long AddVAT(long amount) => amount + CalculateVAT(amount);
private async Task<bool> CalculateAndSaveVAT(long orderId, long orderAmount, CancellationToken cancellationToken) private async Task<bool> CalculateAndSaveVAT(long orderId, long orderAmount, CancellationToken cancellationToken)
{ {
try try
@@ -166,7 +195,7 @@ public class
// دریافت نرخ VAT // دریافت نرخ VAT
var vatRateConfig = await _context.SystemConfigurations var vatRateConfig = await _context.SystemConfigurations
.FirstOrDefaultAsync(x => x.Scope == ConfigurationScope.VAT && x.Key == "Rate", cancellationToken); .FirstOrDefaultAsync(x => x.Scope == ConfigurationScope.VAT && x.Key == "Shop.VAT", cancellationToken);
if (vatRateConfig == null || !decimal.TryParse(vatRateConfig.Value, out var vatRate)) if (vatRateConfig == null || !decimal.TryParse(vatRateConfig.Value, out var vatRate))
{ {
@@ -347,6 +347,7 @@ message WeeklyCommissionPoolModel
{ {
int64 id = 1; int64 id = 1;
int64 week_definition_id = 2; int64 week_definition_id = 2;
string week_display_name = 3;
int64 total_pool_amount = 4; int64 total_pool_amount = 4;
int32 total_balances = 5; int32 total_balances = 5;
int64 value_per_balance = 6; int64 value_per_balance = 6;
@@ -433,6 +434,7 @@ message WorkerExecutionLogModel
{ {
string execution_id = 1; string execution_id = 1;
int64 week_definition_id = 2; int64 week_definition_id = 2;
string week_display_name = 3;
string step = 4; // "Balances" | "Pool" | "Payouts" | "Full" string step = 4; // "Balances" | "Pool" | "Payouts" | "Full"
bool success = 5; bool success = 5;
google.protobuf.StringValue error_message = 6; google.protobuf.StringValue error_message = 6;
@@ -156,7 +156,7 @@ message GetNetworkTreeRequest
google.protobuf.Int32Value max_depth = 2; google.protobuf.Int32Value max_depth = 2;
google.protobuf.BoolValue only_active = 3; google.protobuf.BoolValue only_active = 3;
google.protobuf.BoolValue is_club_active = 4; google.protobuf.BoolValue is_club_active = 4;
google.protobuf.StringValue activation_week_number = 5; google.protobuf.StringValue activation_week_definition_id = 5;
} }
message GetNetworkTreeResponse message GetNetworkTreeResponse
@@ -175,7 +175,7 @@ message NetworkTreeNodeModel
google.protobuf.Timestamp joined_at = 7; google.protobuf.Timestamp joined_at = 7;
google.protobuf.Timestamp club_activated_at = 8; // تاریخ فعال‌سازی در باشگاه google.protobuf.Timestamp club_activated_at = 8; // تاریخ فعال‌سازی در باشگاه
bool is_club_active = 9; // فعال بودن در باشگاه bool is_club_active = 9; // فعال بودن در باشگاه
string activation_week_number = 10; // شماره هفته فعال‌سازی google.protobuf.Int64Value activation_week_definition_id = 10; // شماره هفته فعال‌سازی
bool is_activated_in_target_week = 11; // آیا در هفته هدف فعال شده bool is_activated_in_target_week = 11; // آیا در هفته هدف فعال شده
google.protobuf.Timestamp user_created = 12; // تاریخ ایجاد کاربر google.protobuf.Timestamp user_created = 12; // تاریخ ایجاد کاربر
} }
@@ -14,7 +14,7 @@ public class NetworkMembershipProfile : IRegister
.Map(dest => dest.UserId, src => src.UserId) .Map(dest => dest.UserId, src => src.UserId)
.Map(dest => dest.MaxDepth, src => src.MaxDepth != null && src.MaxDepth.Value > 0 ? src.MaxDepth.Value : 3) .Map(dest => dest.MaxDepth, src => src.MaxDepth != null && src.MaxDepth.Value > 0 ? src.MaxDepth.Value : 3)
.Map(dest => dest.IsClubActive, src => src.IsClubActive) .Map(dest => dest.IsClubActive, src => src.IsClubActive)
.Map(dest => dest.ActivationWeekNumber, src => src.ActivationWeekNumber); .Map(dest => dest.ActivationWeekDefinitionId, src => src.ActivationWeekDefinitionId);
// Response mapping: تبدیل درخت به لیست مسطح // Response mapping: تبدیل درخت به لیست مسطح
config.NewConfig<NetworkTreeDto, GetNetworkTreeResponse>() config.NewConfig<NetworkTreeDto, GetNetworkTreeResponse>()
@@ -85,7 +85,7 @@ public class NetworkMembershipProfile : IRegister
NetworkLevel = node.CurrentDepth, NetworkLevel = node.CurrentDepth,
IsActive = true, IsActive = true,
IsClubActive = node.IsClubActive, IsClubActive = node.IsClubActive,
ActivationWeekNumber = node.ActivationWeekNumber ?? string.Empty, ActivationWeekDefinitionId = node.ActivationWeekDefinitionId ?? 0,
IsActivatedInTargetWeek = node.IsActivatedInTargetWeek IsActivatedInTargetWeek = node.IsActivatedInTargetWeek
}; };