feat: Implement customer profile and referral queries

- Add GetCustomerProfileResponseDto for retrieving customer profile information.
- Create GetCustomerReferralsQuery and GetCustomerReferralsQueryHandler to fetch customer referrals with pagination and filtering options.
- Introduce GetCustomerReferralsResponseDto to structure the response for customer referrals.
- Implement GetCustomerSettingsQuery and GetCustomerSettingsQueryHandler to retrieve user settings.
- Add GetCustomerOrder and GetCustomerOrderQueryHandler for fetching specific customer orders.
- Create GetCustomerOrderHistoryQuery and GetCustomerOrderHistoryQueryHandler to retrieve order history with filtering options.
- Implement GetCustomerOrdersQuery and GetCustomerOrdersQueryHandler for fetching multiple customer orders with filters.
- Add GetCustomerWalletChangeLogQuery and GetCustomerWalletChangeLogQueryHandler for retrieving wallet change logs.
- Implement GetCustomerWithdrawalSettingsQuery and GetCustomerWithdrawalSettingsQueryHandler for fetching withdrawal settings.
- Create GetCustomerWithdrawalsQuery and GetCustomerWithdrawalsQueryHandler to retrieve customer withdrawal requests.
This commit is contained in:
masoodafar-web
2026-02-05 23:01:50 +03:30
parent b41342dcad
commit b2d676b555
96 changed files with 4822 additions and 589 deletions
@@ -66,7 +66,7 @@ public class CreateNewOtpTokenCommandHandler : IRequestHandler<CreateNewOtpToken
return new CreateNewOtpTokenResponseDto
{
IsSuccess = true,
Success = true,
Message = "کد تایید با موفقیت ارسال شد",
ExpiresAt = otpToken.ExpiresAt
};
@@ -2,7 +2,7 @@ namespace CMSMicroservice.Application.UserCQ.Commands.CreateNewOtpToken;
public class CreateNewOtpTokenResponseDto
{
//موفق؟
public bool IsSuccess { get; set; }
public bool Success { get; set; }
//پیام
public string Message { get; set; }
//تلاش باقی مانده
@@ -21,7 +21,7 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenComman
.FirstOrDefaultAsync(cancellationToken);
if (otpToken == null || !otpToken.IsValid(request.Code))
return new VerifyOtpTokenResponseDto { IsSuccess = false, Message = "کد تایید نامعتبر است" };
return new VerifyOtpTokenResponseDto { Success = false, Message = "کد تایید نامعتبر است" };
var user = await _context.Users
.Include(u => u.UserContracts)
@@ -33,7 +33,7 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenComman
.FirstOrDefaultAsync(cancellationToken);
if (user == null)
return new VerifyOtpTokenResponseDto { IsSuccess = false, Message = "کاربر یافت نشد" };
return new VerifyOtpTokenResponseDto { Success = false, Message = "کاربر یافت نشد" };
// Mark OTP as used
otpToken.IsUsed = true;
@@ -44,7 +44,7 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenComman
return new VerifyOtpTokenResponseDto
{
IsSuccess = true,
Success = true,
Message = "کد تایید با موفقیت تایید شد",
Token = token
};
@@ -2,7 +2,7 @@ namespace CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken;
public class VerifyOtpTokenResponseDto
{
//موفق؟
public bool IsSuccess { get; set; }
public bool Success { get; set; }
//پیام
public string Message { get; set; }
//توکن
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile;
public class GetCustomerProfileQuery : IRequest<GetCustomerProfileResponseDto>
{
public long UserId { get; set; }
}
@@ -0,0 +1,77 @@
using CMSMicroservice.Application.Common.Interfaces;
namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile;
public class GetCustomerProfileQueryHandler : IRequestHandler<GetCustomerProfileQuery, GetCustomerProfileResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerProfileQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerProfileResponseDto> Handle(GetCustomerProfileQuery request, CancellationToken cancellationToken)
{
// Get userId from ICurrentUserService if not provided
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var id) ? id : 0)
: request.UserId;
if (userId == 0)
throw new UnauthorizedAccessException("User ID not found in JWT token");
var user = await _context.Users
.AsNoTracking()
.Where(x => x.Id == userId)
.FirstOrDefaultAsync(cancellationToken);
if (user == null)
throw new NotFoundException(nameof(User), userId);
var fullName = $"{user.FirstName} {user.LastName}".Trim();
var profileCompletionPercentage = CalculateProfileCompletion(user);
return new GetCustomerProfileResponseDto
{
Id = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
Mobile = user.Mobile,
Email = user.Email,
NationalCode = user.NationalCode,
AvatarPath = user.AvatarPath,
ParentId = user.NetworkParentId,
ReferralCode = user.ReferralCode,
IsMobileVerified = user.IsMobileVerified,
MobileVerifiedAt = user.MobileVerifiedAt,
EmailNotifications = user.EmailNotifications,
SmsNotifications = user.SmsNotifications,
PushNotifications = user.PushNotifications,
BirthDate = user.BirthDate,
FullName = fullName,
ProfileCompletionPercentage = profileCompletionPercentage
};
}
private int CalculateProfileCompletion(Domain.Entities.User user)
{
var totalFields = 10;
var completedFields = 0;
if (!string.IsNullOrEmpty(user.FirstName)) completedFields++;
if (!string.IsNullOrEmpty(user.LastName)) completedFields++;
if (!string.IsNullOrEmpty(user.Mobile)) completedFields++;
if (!string.IsNullOrEmpty(user.Email)) completedFields++;
if (!string.IsNullOrEmpty(user.NationalCode)) completedFields++;
if (!string.IsNullOrEmpty(user.AvatarPath)) completedFields++;
if (user.BirthDate.HasValue) completedFields++;
if (user.IsMobileVerified) completedFields++;
if (user.NetworkParentId.HasValue) completedFields++;
if (!string.IsNullOrEmpty(user.ReferralCode)) completedFields++;
return (int)((double)completedFields / totalFields * 100);
}
}
@@ -0,0 +1,22 @@
namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile;
public class GetCustomerProfileResponseDto
{
public long Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string Mobile { get; set; }
public string? Email { get; set; }
public string? NationalCode { get; set; }
public string? AvatarPath { get; set; }
public long? ParentId { get; set; }
public string ReferralCode { get; set; }
public bool IsMobileVerified { get; set; }
public DateTime? MobileVerifiedAt { get; set; }
public bool EmailNotifications { get; set; }
public bool SmsNotifications { get; set; }
public bool PushNotifications { get; set; }
public DateTime? BirthDate { get; set; }
public string FullName { get; set; }
public int ProfileCompletionPercentage { get; set; }
}
@@ -0,0 +1,10 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals;
public class GetCustomerReferralsQuery : IRequest<GetCustomerReferralsResponseDto>
{
public long UserId { get; set; }
public PaginationState? PaginationState { get; set; }
public string? StatusFilter { get; set; } // ACTIVE, INACTIVE, ALL
}
@@ -0,0 +1,114 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals;
public class GetCustomerReferralsQueryHandler : IRequestHandler<GetCustomerReferralsQuery, GetCustomerReferralsResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerReferralsQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerReferralsResponseDto> Handle(GetCustomerReferralsQuery request, CancellationToken cancellationToken)
{
// Get userId from ICurrentUserService if not provided
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var id) ? id : 0)
: request.UserId;
if (userId == 0)
throw new UnauthorizedAccessException("User ID not found in JWT token");
// Get referrals (children in network)
var query = _context.Users
.AsNoTracking()
.Where(x => x.NetworkParentId == userId);
// Apply status filter
if (!string.IsNullOrEmpty(request.StatusFilter))
{
if (request.StatusFilter.ToUpper() == "ACTIVE")
query = query.Where(x => x.IsMobileVerified);
else if (request.StatusFilter.ToUpper() == "INACTIVE")
query = query.Where(x => !x.IsMobileVerified);
// ALL - no additional filter
}
query = query.OrderByDescending(x => x.Created);
// Calculate stats
var allReferrals = await _context.Users
.AsNoTracking()
.Where(x => x.NetworkParentId == userId)
.ToListAsync(cancellationToken);
var totalReferrals = allReferrals.Count;
var activeReferrals = allReferrals.Count(x => x.IsMobileVerified);
// Calculate commission for current user
var userWallet = await _context.UserWallets
.AsNoTracking()
.Where(x => x.UserId == userId)
.FirstOrDefaultAsync(cancellationToken);
var totalCommission = userWallet?.NetworkBalance ?? 0;
// Calculate this month's commission from wallet changelog
var startOfMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
var thisMonthCommission = await _context.UserWalletChangeLogs
.AsNoTracking()
.Include(x => x.Wallet)
.Where(x => x.Wallet.UserId == userId && x.Created >= startOfMonth)
.SumAsync(x => x.ChangeNerworkValue, cancellationToken);
// Pagination
var totalCount = await query.CountAsync(cancellationToken);
var paginationState = request.PaginationState ?? new PaginationState { PageNumber = 1, PageSize = 10 };
var referrals = await query
.Skip((paginationState.PageNumber - 1) * paginationState.PageSize)
.Take(paginationState.PageSize)
.ToListAsync(cancellationToken);
var metaData = new MetaData
{
TotalCount = totalCount,
CurrentPage = paginationState.PageNumber,
PageSize = paginationState.PageSize,
TotalPage = (int)Math.Ceiling((double)totalCount / paginationState.PageSize),
HasPrevious = paginationState.PageNumber > 1,
HasNext = paginationState.PageNumber < (int)Math.Ceiling((double)totalCount / paginationState.PageSize)
};
var referralModels = referrals.Select(r => new CustomerReferralModel
{
Id = r.Id,
FirstName = r.FirstName,
LastName = r.LastName,
Mobile = r.Mobile,
JoinDate = r.Created,
IsActive = r.IsMobileVerified,
StatusMessage = r.IsMobileVerified ? "Active" : "Inactive",
Level = 1, // Direct referral
TotalCommission = 0 // Not tracking per-referral commission
}).ToList();
return new GetCustomerReferralsResponseDto
{
MetaData = metaData,
Referrals = referralModels,
Stats = new CustomerReferralStats
{
TotalReferrals = totalReferrals,
ActiveReferrals = activeReferrals,
TotalCommissionEarned = totalCommission,
ThisMonthCommission = thisMonthCommission
}
};
}
}
@@ -0,0 +1,31 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals;
public class GetCustomerReferralsResponseDto
{
public MetaData MetaData { get; set; }
public List<CustomerReferralModel> Referrals { get; set; }
public CustomerReferralStats Stats { get; set; }
}
public class CustomerReferralModel
{
public long Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string Mobile { get; set; }
public DateTime JoinDate { get; set; }
public bool IsActive { get; set; }
public string StatusMessage { get; set; }
public int Level { get; set; }
public long TotalCommission { get; set; }
}
public class CustomerReferralStats
{
public int TotalReferrals { get; set; }
public int ActiveReferrals { get; set; }
public long TotalCommissionEarned { get; set; }
public long ThisMonthCommission { get; set; }
}
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings;
public class GetCustomerSettingsQuery : IRequest<GetCustomerSettingsResponseDto>
{
public long UserId { get; set; }
}
@@ -0,0 +1,45 @@
using CMSMicroservice.Application.Common.Interfaces;
namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings;
public class GetCustomerSettingsQueryHandler : IRequestHandler<GetCustomerSettingsQuery, GetCustomerSettingsResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerSettingsQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerSettingsResponseDto> Handle(GetCustomerSettingsQuery request, CancellationToken cancellationToken)
{
// Get userId from ICurrentUserService if not provided
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var id) ? id : 0)
: request.UserId;
if (userId == 0)
throw new UnauthorizedAccessException("User ID not found in JWT token");
var user = await _context.Users
.AsNoTracking()
.Where(x => x.Id == userId)
.FirstOrDefaultAsync(cancellationToken);
if (user == null)
throw new NotFoundException(nameof(User), userId);
return new GetCustomerSettingsResponseDto
{
EmailNotifications = user.EmailNotifications,
SmsNotifications = user.SmsNotifications,
PushNotifications = user.PushNotifications,
MarketingNotifications = false, // Not in User entity, default to false
PreferredLanguage = "fa", // Default Persian
TimeZone = "Asia/Tehran", // Default Iran timezone
TwoFactorAuthEnabled = false // Not implemented yet
};
}
}
@@ -0,0 +1,12 @@
namespace CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings;
public class GetCustomerSettingsResponseDto
{
public bool EmailNotifications { get; set; }
public bool SmsNotifications { get; set; }
public bool PushNotifications { get; set; }
public bool MarketingNotifications { get; set; }
public string PreferredLanguage { get; set; }
public string TimeZone { get; set; }
public bool TwoFactorAuthEnabled { get; set; }
}
@@ -2,21 +2,28 @@ namespace CMSMicroservice.Application.UserCQ.Queries.GetUser;
public class GetUserQueryHandler : IRequestHandler<GetUserQuery, GetUserResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetUserQueryHandler(IApplicationDbContext context)
public GetUserQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetUserResponseDto> Handle(GetUserQuery request,
CancellationToken cancellationToken)
{
// If Id is 0 or not provided, get the current authenticated user's ID
var userId = request.Id == 0
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
: request.Id;
var response = await _context.Users
.AsNoTracking()
.Where(x => x.Id == request.Id)
.Where(x => x.Id == userId)
.ProjectToType<GetUserResponseDto>()
.FirstOrDefaultAsync(cancellationToken);
return response ?? throw new NotFoundException(nameof(User), request.Id);
return response ?? throw new NotFoundException(nameof(User), userId);
}
}