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
@@ -4,13 +4,16 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommi
{
private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
private readonly ICurrentUserService _currentUser;
public GetUserCommissionPayoutsQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
IWeekDefinitionRepository weekDefinitionRepository,
ICurrentUserService currentUser)
{
_context = context;
_weekDefinitionRepository = weekDefinitionRepository;
_currentUser = currentUser;
}
public async Task<GetUserCommissionPayoutsResponseDto> Handle(GetUserCommissionPayoutsQuery request, CancellationToken cancellationToken)
@@ -21,10 +24,20 @@ public class GetUserCommissionPayoutsQueryHandler : IRequestHandler<GetUserCommi
.AsNoTracking()
.AsQueryable();
// فیلترها
if (request.UserId.HasValue)
// اگر UserId داده نشده، از CurrentUser بگیر (برای Customer API)
long? userId = request.UserId;
if (!userId.HasValue || userId.Value == 0)
{
query = query.Where(x => x.UserId == request.UserId.Value);
if (long.TryParse(_currentUser.UserId, out var currentUserId))
{
userId = currentUserId;
}
}
// فیلترها
if (userId.HasValue && userId.Value > 0)
{
query = query.Where(x => x.UserId == userId.Value);
}
if (request.Status.HasValue)
@@ -4,13 +4,16 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
{
private readonly IApplicationDbContext _context;
private readonly IWeekDefinitionRepository _weekDefinitionRepository;
private readonly ICurrentUserService _currentUser;
public GetUserWeeklyBalancesQueryHandler(
IApplicationDbContext context,
IWeekDefinitionRepository weekDefinitionRepository)
IWeekDefinitionRepository weekDefinitionRepository,
ICurrentUserService currentUser)
{
_context = context;
_weekDefinitionRepository = weekDefinitionRepository;
_currentUser = currentUser;
}
public async Task<GetUserWeeklyBalancesResponseDto> Handle(GetUserWeeklyBalancesQuery request, CancellationToken cancellationToken)
@@ -21,10 +24,20 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
.AsNoTracking()
.AsQueryable();
// فیلترها
if (request.UserId.HasValue)
// اگر UserId داده نشده، از CurrentUser بگیر (برای Customer API)
long? userId = request.UserId;
if (!userId.HasValue || userId.Value == 0)
{
query = query.Where(x => x.UserId == request.UserId.Value);
if (long.TryParse(_currentUser.UserId, out var currentUserId))
{
userId = currentUserId;
}
}
// فیلترها
if (userId.HasValue && userId.Value > 0)
{
query = query.Where(x => x.UserId == userId.Value);
}
// فیلتر بر اساس WeekDefinitionId (روش ترجیحی)
@@ -0,0 +1,20 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCustomerCart;
/// <summary>
/// Command برای افزودن محصول به سبد خرید کاربر فعلی
/// </summary>
public class AddToCustomerCartCommand : IRequest<AddToCustomerCartCommandResponse>
{
public long ProductId { get; set; }
public int Count { get; set; }
// UserId from ICurrentUserService
}
public class AddToCustomerCartCommandResponse
{
public long Id { get; set; }
public string Message { get; set; } = string.Empty;
public bool Success { get; set; }
}
@@ -0,0 +1,80 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCustomerCart;
public class AddToCustomerCartCommandHandler : IRequestHandler<AddToCustomerCartCommand, AddToCustomerCartCommandResponse>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public AddToCustomerCartCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<AddToCustomerCartCommandResponse> Handle(AddToCustomerCartCommand request, CancellationToken cancellationToken)
{
// Extract UserId from JWT token
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// Check if product exists and is not deleted
var product = await _context.Products
.FirstOrDefaultAsync(p => p.Id == request.ProductId && !p.IsDeleted, cancellationToken);
if (product == null)
{
return new AddToCustomerCartCommandResponse
{
Success = false,
Message = "محصول یافت نشد یا حذف شده است"
};
}
// Check if item already exists in cart
var existingCartItem = await _context.UserCarts
.FirstOrDefaultAsync(uc => uc.UserId == userId && uc.ProductId == request.ProductId, cancellationToken);
if (existingCartItem != null)
{
// Update count
existingCartItem.Count += request.Count;
_context.UserCarts.Update(existingCartItem);
await _context.SaveChangesAsync(cancellationToken);
return new AddToCustomerCartCommandResponse
{
Id = existingCartItem.Id,
Success = true,
Message = "تعداد محصول در سبد خرید به‌روزرسانی شد"
};
}
// Create new cart item
var cartItem = new UserCart
{
UserId = userId,
ProductId = request.ProductId,
Count = request.Count,
Created = DateTime.UtcNow
};
_context.UserCarts.Add(cartItem);
await _context.SaveChangesAsync(cancellationToken);
return new AddToCustomerCartCommandResponse
{
Id = cartItem.Id,
Success = true,
Message = "محصول به سبد خرید اضافه شد"
};
}
}
@@ -0,0 +1,18 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCustomerCart;
/// <summary>
/// Command برای حذف محصول از سبد خرید
/// </summary>
public class RemoveFromCustomerCartCommand : IRequest<RemoveFromCustomerCartCommandResponse>
{
public long CartItemId { get; set; }
// UserId from ICurrentUserService
}
public class RemoveFromCustomerCartCommandResponse
{
public string Message { get; set; } = string.Empty;
public bool Success { get; set; }
}
@@ -0,0 +1,49 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCustomerCart;
public class RemoveFromCustomerCartCommandHandler : IRequestHandler<RemoveFromCustomerCartCommand, RemoveFromCustomerCartCommandResponse>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public RemoveFromCustomerCartCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<RemoveFromCustomerCartCommandResponse> Handle(RemoveFromCustomerCartCommand request, CancellationToken cancellationToken)
{
// Extract UserId from JWT token
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// Find and remove cart item
var cartItem = await _context.UserCarts
.FirstOrDefaultAsync(uc => uc.Id == request.CartItemId && uc.UserId == userId, cancellationToken);
if (cartItem == null)
{
return new RemoveFromCustomerCartCommandResponse
{
Success = false,
Message = "آیتم سبد خرید یافت نشد"
};
}
_context.UserCarts.Remove(cartItem);
await _context.SaveChangesAsync(cancellationToken);
return new RemoveFromCustomerCartCommandResponse
{
Success = true,
Message = "آیتم از سبد خرید حذف شد"
};
}
}
@@ -0,0 +1,19 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCustomerCartItem;
/// <summary>
/// Command برای به‌روزرسانی تعداد محصول در سبد خرید
/// </summary>
public class UpdateCustomerCartItemCommand : IRequest<UpdateCustomerCartItemCommandResponse>
{
public long CartItemId { get; set; }
public int Count { get; set; }
// UserId from ICurrentUserService
}
public class UpdateCustomerCartItemCommandResponse
{
public string Message { get; set; } = string.Empty;
public bool Success { get; set; }
}
@@ -0,0 +1,64 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCustomerCartItem;
public class UpdateCustomerCartItemCommandHandler : IRequestHandler<UpdateCustomerCartItemCommand, UpdateCustomerCartItemCommandResponse>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public UpdateCustomerCartItemCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<UpdateCustomerCartItemCommandResponse> Handle(UpdateCustomerCartItemCommand request, CancellationToken cancellationToken)
{
// Extract UserId from JWT token
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// Find cart item
var cartItem = await _context.UserCarts
.FirstOrDefaultAsync(uc => uc.Id == request.CartItemId && uc.UserId == userId, cancellationToken);
if (cartItem == null)
{
return new UpdateCustomerCartItemCommandResponse
{
Success = false,
Message = "آیتم سبد خرید یافت نشد"
};
}
// Update count
if (request.Count <= 0)
{
// Remove item if count is 0 or negative
_context.UserCarts.Remove(cartItem);
await _context.SaveChangesAsync(cancellationToken);
return new UpdateCustomerCartItemCommandResponse
{
Success = true,
Message = "آیتم از سبد خرید حذف شد"
};
}
cartItem.Count = request.Count;
_context.UserCarts.Update(cartItem);
await _context.SaveChangesAsync(cancellationToken);
return new UpdateCustomerCartItemCommandResponse
{
Success = true,
Message = "تعداد آیتم به‌روزرسانی شد"
};
}
}
@@ -0,0 +1,11 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetCustomerCart;
/// <summary>
/// Query برای دریافت سبد خرید کاربر فعلی
/// </summary>
public class GetCustomerCartQuery : IRequest<GetCustomerCartQueryResponse>
{
// UserId from ICurrentUserService
}
@@ -0,0 +1,68 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetCustomerCart;
public class GetCustomerCartQueryHandler : IRequestHandler<GetCustomerCartQuery, GetCustomerCartQueryResponse>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerCartQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerCartQueryResponse> Handle(GetCustomerCartQuery request, CancellationToken cancellationToken)
{
// Extract UserId from JWT token
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// Get all cart items for the current user
var cartItems = await _context.UserCarts
.Include(uc => uc.Product)
.Where(uc => uc.UserId == userId)
.ToListAsync(cancellationToken);
var response = new GetCustomerCartQueryResponse
{
TotalItemsCount = cartItems.Sum(c => c.Count),
Message = cartItems.Count > 0 ? "سبد خرید با موفقیت بازیابی شد" : "سبد خرید خالی است"
};
foreach (var item in cartItems)
{
// Use Product.ThumbnailPath directly
var thumbnailPath = item.Product?.ThumbnailPath ?? string.Empty;
var itemPrice = item.Product?.Price ?? 0;
var itemDiscount = item.Product?.Discount ?? 0;
var finalPrice = itemPrice * (100 - itemDiscount) / 100;
var totalItemPrice = finalPrice * item.Count;
response.Items.Add(new CustomerCartItemModel
{
Id = item.Id,
ProductId = item.ProductId,
ProductTitle = item.Product?.Title ?? string.Empty,
ProductShortInformation = item.Product?.ShortInfomation ?? string.Empty, // Typo in DB: ShortInfomation
ProductPrice = itemPrice,
ProductDiscount = itemDiscount,
ProductThumbnailPath = thumbnailPath,
Count = item.Count,
TotalItemPrice = totalItemPrice,
Created = item.Created
});
response.TotalPrice += totalItemPrice;
}
return response;
}
}
@@ -0,0 +1,23 @@
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetCustomerCart;
public class GetCustomerCartQueryResponse
{
public List<CustomerCartItemModel> Items { get; set; } = new();
public long TotalPrice { get; set; }
public int TotalItemsCount { get; set; }
public string Message { get; set; } = string.Empty;
}
public class CustomerCartItemModel
{
public long Id { get; set; }
public long ProductId { get; set; }
public string ProductTitle { get; set; } = string.Empty;
public string ProductShortInformation { get; set; } = string.Empty;
public long ProductPrice { get; set; }
public int ProductDiscount { get; set; }
public string ProductThumbnailPath { get; set; } = string.Empty;
public int Count { get; set; }
public long TotalItemPrice { get; set; }
public DateTime Created { get; set; }
}
@@ -0,0 +1,15 @@
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree;
/// <summary>
/// Query برای دریافت درخت شبکه کاربر جاری (Customer-facing)
/// از ICurrentUserService برای دریافت UserId استفاده می‌کند
/// </summary>
public record GetMyNetworkTreeQuery : IRequest<NetworkTreeDto?>
{
/// <summary>
/// تعداد سطوح (Depth) که می‌خواهیم نمایش دهیم (پیش‌فرض: 3)
/// </summary>
public int MaxDepth { get; init; } = 3;
}
@@ -0,0 +1,40 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree;
/// <summary>
/// Handler برای دریافت درخت شبکه کاربر جاری
/// </summary>
public class GetMyNetworkTreeQueryHandler : IRequestHandler<GetMyNetworkTreeQuery, NetworkTreeDto?>
{
private readonly ICurrentUserService _currentUser;
private readonly ISender _sender;
public GetMyNetworkTreeQueryHandler(
ICurrentUserService currentUser,
ISender sender)
{
_currentUser = currentUser;
_sender = sender;
}
public async Task<NetworkTreeDto?> Handle(GetMyNetworkTreeQuery request, CancellationToken cancellationToken)
{
// دریافت UserId از JWT
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// استفاده از GetNetworkTreeQuery موجود با UserId از JWT
var query = new GetNetworkTreeQuery
{
UserId = userId,
MaxDepth = request.MaxDepth > 0 ? request.MaxDepth : 3
};
return await _sender.Send(query, cancellationToken);
}
}
@@ -0,0 +1,24 @@
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree;
public class GetMyNetworkTreeQueryValidator : AbstractValidator<GetMyNetworkTreeQuery>
{
public GetMyNetworkTreeQueryValidator()
{
RuleFor(x => x.MaxDepth)
.InclusiveBetween(1, 100)
.WithMessage("عمق درخت باید بین 1 تا 100 باشد");
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(
ValidationContext<GetMyNetworkTreeQuery>.CreateWithOptions(
(GetMyNetworkTreeQuery)model,
x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
@@ -2,5 +2,8 @@ namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStat
public class GetNetworkStatisticsQuery : IRequest<GetNetworkStatisticsResponseDto>
{
// No parameters - returns overall statistics
/// <summary>
/// شناسه کاربر برای محاسبه آمار شبکه او - 0 یا null یعنی کاربر جاری
/// </summary>
public long UserId { get; set; }
}
@@ -5,61 +5,79 @@ namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStat
public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatisticsQuery, GetNetworkStatisticsResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetNetworkStatisticsQueryHandler(IApplicationDbContext context)
public GetNetworkStatisticsQueryHandler(
IApplicationDbContext context,
ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetNetworkStatisticsResponseDto> Handle(GetNetworkStatisticsQuery request, CancellationToken cancellationToken)
{
// Basic statistics - using Users table with NetworkParentId
var totalMembers = await _context.Users
.Where(x => x.NetworkParentId != null)
.CountAsync(cancellationToken);
var activeMembers = await _context.Users
.Where(x => x.NetworkParentId != null)
.CountAsync(cancellationToken);
// Get userId - use current user if not specified or is 0
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
: request.UserId;
var leftLegCount = await _context.Users
.Where(x => x.LegPosition == NetworkLeg.Left)
.CountAsync(cancellationToken);
if (userId == 0)
{
throw new UnauthorizedAccessException("User ID not found");
}
var rightLegCount = await _context.Users
.Where(x => x.LegPosition == NetworkLeg.Right)
.CountAsync(cancellationToken);
// Get all descendants recursively
var allUsers = await _context.Users.ToListAsync(cancellationToken);
var allDescendants = GetAllDescendants(userId, allUsers);
// Statistics for the user's network (all descendants)
var totalMembers = allDescendants.Count;
var activeMembers = allDescendants.Count(x => !x.IsDeleted);
// Get direct left and right children
var leftChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Left);
var rightChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Right);
// Count all descendants in left and right subtrees
var leftLegCount = leftChild != null ? GetAllDescendants(leftChild.Id, allUsers).Count + 1 : 0; // +1 for leftChild itself
var rightLegCount = rightChild != null ? GetAllDescendants(rightChild.Id, allUsers).Count + 1 : 0; // +1 for rightChild itself
double leftPercentage = totalMembers > 0 ? (leftLegCount / (double)totalMembers) * 100 : 0;
double rightPercentage = totalMembers > 0 ? (rightLegCount / (double)totalMembers) * 100 : 0;
// Calculate depth based on network parent relationships
// For simplicity, we'll estimate average depth as 3-5 levels
double averageDepth = 4.5; // Estimated average
int maxDepth = 10; // Estimated max depth
// Level distribution - simplified estimation based on growth pattern
var levelDistribution = new List<LevelDistributionModel>();
if (totalMembers > 0)
// Calculate actual depth
int maxDepth = 0;
double totalDepthSum = 0;
var userDepths = new Dictionary<long, int>();
CalculateDepths(userId, allUsers, 0, userDepths, ref maxDepth);
if (allDescendants.Count > 0)
{
// Approximate distribution: Level 1 (10%), Level 2 (20%), Level 3 (30%), Level 4 (20%), Level 5+ (20%)
levelDistribution = new List<LevelDistributionModel>
{
new() { Level = 1, Count = (int)(totalMembers * 0.1) },
new() { Level = 2, Count = (int)(totalMembers * 0.2) },
new() { Level = 3, Count = (int)(totalMembers * 0.3) },
new() { Level = 4, Count = (int)(totalMembers * 0.2) },
new() { Level = 5, Count = (int)(totalMembers * 0.15) },
new() { Level = 6, Count = totalMembers - (int)(totalMembers * 0.95) }
};
totalDepthSum = allDescendants.Sum(d => userDepths.ContainsKey(d.Id) ? userDepths[d.Id] : 0);
}
double averageDepth = allDescendants.Count > 0 ? totalDepthSum / allDescendants.Count : 0;
// Level distribution - calculate from depths
var levelDistribution = new List<LevelDistributionModel>();
if (allDescendants.Count > 0)
{
var levelCounts = allDescendants
.Where(d => userDepths.ContainsKey(d.Id))
.GroupBy(d => userDepths[d.Id])
.OrderBy(g => g.Key)
.Select(g => new LevelDistributionModel { Level = g.Key, Count = g.Count() })
.ToList();
levelDistribution = levelCounts;
}
// Monthly growth (last 6 months) - using Created date
// Monthly growth (last 6 months) - using descendants Created date
var sixMonthsAgo = DateTime.Now.AddMonths(-6);
var monthlyGrowthRaw = await _context.Users
.Where(x => x.NetworkParentId != null && x.Created >= sixMonthsAgo)
var monthlyGrowthRaw = allDescendants
.Where(x => x.Created >= sixMonthsAgo)
.Select(x => new { x.Created.Year, x.Created.Month })
.ToListAsync(cancellationToken);
.ToList();
var monthlyGrowth = monthlyGrowthRaw
.GroupBy(x => new { x.Year, x.Month })
@@ -71,27 +89,34 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
.OrderBy(x => x.Month)
.ToList();
// Top users by total children count
var topUsers = await _context.Users
.Where(x => x.NetworkParentId != null)
// Top users by total descendants count
var userDescendantCounts = new Dictionary<long, int>();
foreach (var user in allDescendants)
{
var descendants = GetAllDescendants(user.Id, allUsers);
userDescendantCounts[user.Id] = descendants.Count;
}
var topUserData = allDescendants
.Where(x => x.Id != userId && userDescendantCounts[x.Id] > 0)
.Select(x => new
{
x.Id,
UserName = (x.FirstName + " " + x.LastName).Trim(),
LeftCount = _context.Users.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Left),
RightCount = _context.Users.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Right)
DescendantCount = userDescendantCounts[x.Id],
LeftCount = allUsers.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Left),
RightCount = allUsers.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Right)
})
.Where(x => x.LeftCount + x.RightCount > 0)
.OrderByDescending(x => x.LeftCount + x.RightCount)
.OrderByDescending(x => x.DescendantCount)
.Take(10)
.ToListAsync(cancellationToken);
.ToList();
var topUserModels = topUsers.Select((x, index) => new TopNetworkUserModel
var topUserModels = topUserData.Select((x, index) => new TopNetworkUserModel
{
Rank = index + 1,
UserId = x.Id,
UserName = x.UserName,
TotalChildren = x.LeftCount + x.RightCount,
TotalChildren = x.DescendantCount,
LeftCount = x.LeftCount,
RightCount = x.RightCount
}).ToList();
@@ -111,4 +136,40 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
TopUsers = topUserModels
};
}
/// <summary>
/// Recursively get all descendants of a user
/// </summary>
private List<User> GetAllDescendants(long userId, List<User> allUsers)
{
var descendants = new List<User>();
var directChildren = allUsers.Where(x => x.NetworkParentId == userId).ToList();
foreach (var child in directChildren)
{
descendants.Add(child);
descendants.AddRange(GetAllDescendants(child.Id, allUsers));
}
return descendants;
}
/// <summary>
/// Calculate depth for all descendants recursively
/// </summary>
private void CalculateDepths(long userId, List<User> allUsers, int currentDepth, Dictionary<long, int> depths, ref int maxDepth)
{
var children = allUsers.Where(x => x.NetworkParentId == userId).ToList();
foreach (var child in children)
{
var childDepth = currentDepth + 1;
depths[child.Id] = childDepth;
if (childDepth > maxDepth)
maxDepth = childDepth;
CalculateDepths(child.Id, allUsers, childDepth, depths, ref maxDepth);
}
}
}
@@ -8,21 +8,37 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
{
private readonly IApplicationDbContext _context;
private readonly ILogger<GetNetworkTreeQueryHandler> _logger;
private readonly ICurrentUserService _currentUser;
public GetNetworkTreeQueryHandler(
IApplicationDbContext context,
ILogger<GetNetworkTreeQueryHandler> logger)
ILogger<GetNetworkTreeQueryHandler> logger,
ICurrentUserService currentUser)
{
_context = context;
_logger = logger;
_currentUser = currentUser;
}
public async Task<NetworkTreeDto?> Handle(GetNetworkTreeQuery request, CancellationToken cancellationToken)
{
// Get userId - use current user if UserId is 0
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
: request.UserId;
if (userId == 0)
{
throw new UnauthorizedAccessException("User ID not found");
}
// Create a new request with the resolved userId
var resolvedRequest = request with { UserId = userId };
try
{
// دریافت نتایج flat از Stored Procedure
var flatNodes = await ExecuteStoredProcedureAsync(request, cancellationToken);
var flatNodes = await ExecuteStoredProcedureAsync(resolvedRequest, cancellationToken);
if (flatNodes == null || !flatNodes.Any())
{
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails;
public class GetCustomerPackageDetailsQuery : IRequest<GetCustomerPackageDetailsResponseDto>
{
public long PackageId { get; set; }
}
@@ -0,0 +1,68 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using Mapster;
namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails;
public class GetCustomerPackageDetailsQueryHandler : IRequestHandler<GetCustomerPackageDetailsQuery, GetCustomerPackageDetailsResponseDto>
{
private readonly IApplicationDbContext _context;
public GetCustomerPackageDetailsQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetCustomerPackageDetailsResponseDto> Handle(GetCustomerPackageDetailsQuery request, CancellationToken cancellationToken)
{
var package = await _context.Packages
.AsNoTracking()
.Where(x => x.Id == request.PackageId)
.ProjectToType<GetCustomerPackageDetailsResponseDto>()
.FirstOrDefaultAsync(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>
{
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
}
};
// Set purchase requirements
package.Requirements = new PurchaseRequirementsDto
{
RequiresMembership = false,
MinimumWalletBalance = package.Price / 10, // 10% minimum
Restrictions = new List<string>
{
"باید حداقل 18 سال سن داشته باشید",
"تایید هویت الزامی است"
}
};
return package;
}
}
@@ -0,0 +1,27 @@
namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails;
public class GetCustomerPackageDetailsResponseDto
{
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public long Price { get; set; }
public string ImagePath { get; set; }
public List<PackageFeatureDto> Features { get; set; } = new();
public PurchaseRequirementsDto Requirements { get; set; }
}
public class PackageFeatureDto
{
public string Title { get; set; }
public string Description { get; set; }
public string Icon { get; set; }
public bool IsHighlighted { get; set; }
}
public class PurchaseRequirementsDto
{
public bool RequiresMembership { get; set; }
public long MinimumWalletBalance { get; set; }
public List<string> Restrictions { get; set; } = new();
}
@@ -0,0 +1,7 @@
namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages;
public class GetCustomerPackagesQuery : IRequest<List<GetCustomerPackagesResponseDto>>
{
public bool IncludeInactive { get; set; }
public int? PackageTypeFilter { get; set; }
}
@@ -0,0 +1,51 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using Mapster;
namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages;
public class GetCustomerPackagesQueryHandler : IRequestHandler<GetCustomerPackagesQuery, List<GetCustomerPackagesResponseDto>>
{
private readonly IApplicationDbContext _context;
public GetCustomerPackagesQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<List<GetCustomerPackagesResponseDto>> Handle(GetCustomerPackagesQuery request, CancellationToken cancellationToken)
{
var query = _context.Packages
.AsNoTracking()
.AsQueryable();
// Filter by PackageType if specified
if (request.PackageTypeFilter.HasValue)
{
// 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;
}
}
@@ -0,0 +1,18 @@
namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages;
public class GetCustomerPackagesResponseDto
{
public long Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public long Price { get; set; }
public string Currency { get; set; } = "IRR";
public int PackageType { get; set; }
public bool IsAvailable { get; set; } = true;
public string ImageUrl { get; set; }
public string ImagePath { get; set; }
public int ValidityDays { get; set; }
public bool IsPopular { get; set; }
public string ShortDescription { get; set; }
}
@@ -0,0 +1,12 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory;
public class GetCustomerPurchaseHistoryQuery : IRequest<GetCustomerPurchaseHistoryResponseDto>
{
public long UserId { get; set; }
public PaginationState PaginationState { get; set; }
public int? PackageTypeFilter { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
}
@@ -0,0 +1,90 @@
using CMSMicroservice.Application.Common.Extensions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
using Mapster;
namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory;
public class GetCustomerPurchaseHistoryQueryHandler : IRequestHandler<GetCustomerPurchaseHistoryQuery, GetCustomerPurchaseHistoryResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerPurchaseHistoryQueryHandler(
IApplicationDbContext context,
ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerPurchaseHistoryResponseDto> Handle(GetCustomerPurchaseHistoryQuery request, CancellationToken cancellationToken)
{
// Resolve UserId from JWT if not specified
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
: request.UserId;
if (userId == 0)
throw new UnauthorizedAccessException("User ID not found");
var query = _context.UserOrders
.AsNoTracking()
.Where(x => x.UserId == userId && x.PackageId != null)
.Include(x => x.Package)
.AsQueryable();
// Apply date filters if specified
if (request.FromDate.HasValue)
query = query.Where(x => x.Created >= request.FromDate.Value);
if (request.ToDate.HasValue)
query = query.Where(x => x.Created <= request.ToDate.Value);
// Apply PackageType filter if needed (Package entity doesn't have Type enum currently)
// This would require Package entity to have a PackageType field
// Order by most recent first
query = query.OrderByDescending(x => x.Created);
// Get metadata
var metaData = await query.GetMetaData(request.PaginationState, cancellationToken);
// Get paginated results
var orders = await query
.PaginatedListAsync(request.PaginationState)
.ToListAsync(cancellationToken);
var purchases = orders.Select(order => new PackagePurchaseHistoryDto
{
Id = order.Id,
PackageId = order.PackageId ?? 0,
PackageName = order.Package?.Title ?? "نامشخص",
Amount = order.Amount,
PackageType = 0, // Default, needs Package.PackageType field
PurchaseDate = order.Created,
ExpiryDate = order.PaymentDate?.AddDays(365), // Assuming 1 year validity
Status = order.PaymentStatus,
StatusMessage = GetStatusMessage(order.PaymentStatus),
ReferenceCode = order.Transaction?.RefId ?? order.Id.ToString()
}).ToList();
return new GetCustomerPurchaseHistoryResponseDto
{
MetaData = metaData,
Purchases = purchases
};
}
private string GetStatusMessage(PaymentStatus status)
{
return status switch
{
PaymentStatus.Pending => "در انتظار پرداخت",
PaymentStatus.Success => "فعال",
PaymentStatus.Reject => "رد شده",
_ => "نامشخص"
};
}
}
@@ -0,0 +1,24 @@
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory;
public class GetCustomerPurchaseHistoryResponseDto
{
public MetaData MetaData { get; set; }
public List<PackagePurchaseHistoryDto> Purchases { get; set; } = new();
}
public class PackagePurchaseHistoryDto
{
public long Id { get; set; }
public long PackageId { get; set; }
public string PackageName { get; set; }
public long Amount { get; set; }
public int PackageType { get; set; }
public DateTime PurchaseDate { get; set; }
public DateTime? ExpiryDate { get; set; }
public PaymentStatus Status { get; set; }
public string StatusMessage { get; set; }
public string ReferenceCode { get; set; }
}
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProducts;
public class GetCustomerProductsQuery : IRequest<GetCustomerProductsResponseDto>
{
public long Id { get; set; }
}
@@ -0,0 +1,91 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProducts;
public class GetCustomerProductsQueryHandler : IRequestHandler<GetCustomerProductsQuery, GetCustomerProductsResponseDto>
{
private readonly IApplicationDbContext _context;
public GetCustomerProductsQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetCustomerProductsResponseDto> Handle(GetCustomerProductsQuery request, CancellationToken cancellationToken)
{
var product = await _context.Products
.AsNoTracking()
.Where(x => x.Id == request.Id)
.Include(x => x.ProductGalleries)
.ThenInclude(pg => pg.ProductImage)
.Include(x => x.ProductCategories)
.ThenInclude(pc => pc.Category)
.FirstOrDefaultAsync(cancellationToken);
if (product == null)
throw new NotFoundException(nameof(Products), request.Id);
var response = new GetCustomerProductsResponseDto
{
Id = product.Id,
Title = product.Title,
Description = product.Description,
ShortInfomation = product.ShortInfomation,
FullInformation = product.FullInformation,
Price = product.Price,
Discount = product.Discount,
Rate = product.Rate,
ImagePath = product.ImagePath,
ThumbnailPath = product.ThumbnailPath,
SaleCount = product.SaleCount,
ViewCount = product.ViewCount,
RemainingCount = product.RemainingCount,
Gallery = product.ProductGalleries?.Select(pg => new ProductGalleryModel
{
ProductGalleryId = pg.Id,
ProductImageId = pg.ProductImageId,
Title = pg.ProductImage?.Title ?? string.Empty,
ImagePath = pg.ProductImage?.ImagePath ?? string.Empty,
ImageThumbnailPath = pg.ProductImage?.ImageThumbnailPath ?? string.Empty
}).ToList() ?? new List<ProductGalleryModel>(),
Categories = product.ProductCategories?.Select(pc => new ProductCategoryModel
{
CategoryId = pc.CategoryId,
Title = pc.Category?.Title ?? string.Empty,
Path = BuildCategoryPath(pc.Category)
}).ToList() ?? new List<ProductCategoryModel>()
};
return response;
}
private List<CategoryNodeModel> BuildCategoryPath(Category? category)
{
var path = new List<CategoryNodeModel>();
while (category != null)
{
path.Insert(0, new CategoryNodeModel
{
Id = category.Id,
Title = category.Title,
ParentId = category.ParentId
});
// Move to parent
if (category.ParentId.HasValue)
{
category = _context.Categories
.AsNoTracking()
.FirstOrDefault(c => c.Id == category.ParentId.Value);
}
else
{
category = null;
}
}
return path;
}
}
@@ -0,0 +1,43 @@
namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProducts;
public class GetCustomerProductsResponseDto
{
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ShortInfomation { get; set; }
public string FullInformation { get; set; }
public long Price { get; set; }
public int Discount { get; set; }
public int Rate { get; set; }
public string ImagePath { get; set; }
public string ThumbnailPath { get; set; }
public int SaleCount { get; set; }
public int ViewCount { get; set; }
public int RemainingCount { get; set; }
public List<ProductGalleryModel> Gallery { get; set; }
public List<ProductCategoryModel> Categories { get; set; }
}
public class ProductGalleryModel
{
public long ProductGalleryId { get; set; }
public long ProductImageId { get; set; }
public string Title { get; set; }
public string ImagePath { get; set; }
public string ImageThumbnailPath { get; set; }
}
public class ProductCategoryModel
{
public long CategoryId { get; set; }
public string Title { get; set; }
public List<CategoryNodeModel> Path { get; set; }
}
public class CategoryNodeModel
{
public long Id { get; set; }
public string Title { get; set; }
public long? ParentId { get; set; }
}
@@ -0,0 +1,25 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProductsByFilter;
public class GetCustomerProductsByFilterQuery : IRequest<GetCustomerProductsByFilterResponseDto>
{
public PaginationState? PaginationState { get; set; }
public string? SortBy { get; set; }
// Filters
public long? Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public string? ShortInfomation { get; set; }
public string? FullInformation { get; set; }
public long? Price { get; set; }
public int? Discount { get; set; }
public int? Rate { get; set; }
public string? ImagePath { get; set; }
public string? ThumbnailPath { get; set; }
public int? SaleCount { get; set; }
public int? ViewCount { get; set; }
public int? RemainingCount { get; set; }
public List<long>? CategoryIds { get; set; }
}
@@ -0,0 +1,145 @@
using CMSMicroservice.Application.Common.Extensions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Entities;
namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProductsByFilter;
public class GetCustomerProductsByFilterQueryHandler : IRequestHandler<GetCustomerProductsByFilterQuery, GetCustomerProductsByFilterResponseDto>
{
private readonly IApplicationDbContext _context;
public GetCustomerProductsByFilterQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetCustomerProductsByFilterResponseDto> Handle(GetCustomerProductsByFilterQuery request, CancellationToken cancellationToken)
{
var query = _context.Products
.AsNoTracking()
.Include(x => x.ProductCategories)
.ThenInclude(pc => pc.Category)
.AsQueryable();
// Apply filters
if (request.Id.HasValue)
query = query.Where(x => x.Id == request.Id.Value);
if (!string.IsNullOrEmpty(request.Title))
query = query.Where(x => x.Title.Contains(request.Title));
if (!string.IsNullOrEmpty(request.Description))
query = query.Where(x => x.Description.Contains(request.Description));
if (!string.IsNullOrEmpty(request.ShortInfomation))
query = query.Where(x => x.ShortInfomation.Contains(request.ShortInfomation));
if (!string.IsNullOrEmpty(request.FullInformation))
query = query.Where(x => x.FullInformation.Contains(request.FullInformation));
if (request.Price.HasValue)
query = query.Where(x => x.Price == request.Price.Value);
if (request.Discount.HasValue)
query = query.Where(x => x.Discount == request.Discount.Value);
if (request.Rate.HasValue)
query = query.Where(x => x.Rate == request.Rate.Value);
if (request.SaleCount.HasValue)
query = query.Where(x => x.SaleCount == request.SaleCount.Value);
if (request.ViewCount.HasValue)
query = query.Where(x => x.ViewCount == request.ViewCount.Value);
if (request.RemainingCount.HasValue)
query = query.Where(x => x.RemainingCount == request.RemainingCount.Value);
if (request.CategoryIds != null && request.CategoryIds.Any())
query = query.Where(x => x.ProductCategories.Any(pc => request.CategoryIds.Contains(pc.CategoryId)));
// Apply sorting
if (!string.IsNullOrEmpty(request.SortBy))
query = query.ApplyOrder(request.SortBy);
else
query = query.OrderByDescending(x => x.Created);
// Pagination
var totalCount = await query.CountAsync(cancellationToken);
var paginationState = request.PaginationState ?? new PaginationState { PageNumber = 1, PageSize = 10 };
var products = 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 models = products.Select(p => new CustomerProductModel
{
Id = p.Id,
Title = p.Title,
Description = p.Description,
ShortInfomation = p.ShortInfomation,
FullInformation = p.FullInformation,
Price = p.Price,
Discount = p.Discount,
Rate = p.Rate,
ImagePath = p.ImagePath,
ThumbnailPath = p.ThumbnailPath,
SaleCount = p.SaleCount,
ViewCount = p.ViewCount,
RemainingCount = p.RemainingCount,
Categories = p.ProductCategories?.Select(pc => new ProductCategoryPathModel
{
CategoryId = pc.CategoryId,
Title = pc.Category?.Title ?? string.Empty,
Path = BuildCategoryPath(pc.Category)
}).ToList() ?? new List<ProductCategoryPathModel>()
}).ToList();
return new GetCustomerProductsByFilterResponseDto
{
MetaData = metaData,
Models = models
};
}
private List<CategoryNodeItemModel> BuildCategoryPath(Category? category)
{
var path = new List<CategoryNodeItemModel>();
while (category != null)
{
path.Insert(0, new CategoryNodeItemModel
{
Id = category.Id,
Title = category.Title,
ParentId = category.ParentId
});
// Move to parent
if (category.ParentId.HasValue)
{
category = _context.Categories
.AsNoTracking()
.FirstOrDefault(c => c.Id == category.ParentId.Value);
}
else
{
category = null;
}
}
return path;
}
}
@@ -0,0 +1,41 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProductsByFilter;
public class GetCustomerProductsByFilterResponseDto
{
public MetaData MetaData { get; set; }
public List<CustomerProductModel> Models { get; set; }
}
public class CustomerProductModel
{
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ShortInfomation { get; set; }
public string FullInformation { get; set; }
public long Price { get; set; }
public int Discount { get; set; }
public int Rate { get; set; }
public string ImagePath { get; set; }
public string ThumbnailPath { get; set; }
public int SaleCount { get; set; }
public int ViewCount { get; set; }
public int RemainingCount { get; set; }
public List<ProductCategoryPathModel> Categories { get; set; }
}
public class ProductCategoryPathModel
{
public long CategoryId { get; set; }
public string Title { get; set; }
public List<CategoryNodeItemModel> Path { get; set; }
}
public class CategoryNodeItemModel
{
public long Id { get; set; }
public string Title { get; set; }
public long? ParentId { get; set; }
}
@@ -0,0 +1,8 @@
namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction;
public class GetCustomerTransactionQuery : IRequest<GetCustomerTransactionResponseDto>
{
public long? Id { get; set; }
public string Authority { get; set; }
public long UserId { get; set; }
}
@@ -0,0 +1,52 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction;
public class GetCustomerTransactionQueryHandler : IRequestHandler<GetCustomerTransactionQuery, GetCustomerTransactionResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerTransactionQueryHandler(
IApplicationDbContext context,
ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerTransactionResponseDto> Handle(GetCustomerTransactionQuery request, CancellationToken cancellationToken)
{
// Resolve UserId from JWT if not specified
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
: request.UserId;
if (userId == 0)
throw new UnauthorizedAccessException("User ID not found");
// Transaction entity doesn't have UserId, so we need to find it through UserOrders
var transaction = await _context.Transactions
.AsNoTracking()
.Where(x => request.Id.HasValue ? x.Id == request.Id.Value : true)
.Include(x => x.UserOrders)
.Where(x => x.UserOrders.Any(o => o.UserId == userId))
.FirstOrDefaultAsync(cancellationToken);
if (transaction == null)
throw new NotFoundException(nameof(Transaction), request.Id ?? 0);
return new GetCustomerTransactionResponseDto
{
Id = transaction.Id,
Amount = transaction.Amount,
Description = transaction.Description ?? "",
PaymentStatus = transaction.PaymentStatus,
PaymentDate = transaction.PaymentDate,
RefId = transaction.RefId ?? "",
Type = transaction.Type
};
}
}
@@ -0,0 +1,14 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction;
public class GetCustomerTransactionResponseDto
{
public long Id { get; set; }
public long Amount { get; set; }
public string Description { get; set; }
public PaymentStatus PaymentStatus { get; set; }
public DateTime? PaymentDate { get; set; }
public string RefId { get; set; }
public TransactionType Type { get; set; }
}
@@ -0,0 +1,16 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter;
public class GetCustomerTransactionsByFilterQuery : IRequest<GetCustomerTransactionsByFilterResponseDto>
{
public long UserId { get; set; }
public PaginationState PaginationState { get; set; }
public string SortBy { get; set; }
public long? IdFilter { get; set; }
public long? AmountFilter { get; set; }
public string DescriptionFilter { get; set; }
public bool? PaymentStatusFilter { get; set; }
public string RefIdFilter { get; set; }
public int? TypeFilter { get; set; }
}
@@ -0,0 +1,92 @@
using CMSMicroservice.Application.Common.Extensions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter;
public class GetCustomerTransactionsByFilterQueryHandler : IRequestHandler<GetCustomerTransactionsByFilterQuery, GetCustomerTransactionsByFilterResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerTransactionsByFilterQueryHandler(
IApplicationDbContext context,
ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerTransactionsByFilterResponseDto> Handle(GetCustomerTransactionsByFilterQuery request, CancellationToken cancellationToken)
{
// Resolve UserId from JWT if not specified
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
: request.UserId;
if (userId == 0)
throw new UnauthorizedAccessException("User ID not found");
// Transaction doesn't have UserId, find through UserOrders
var query = _context.Transactions
.AsNoTracking()
.Include(x => x.UserOrders)
.Where(x => x.UserOrders.Any(o => o.UserId == userId))
.AsQueryable();
// Apply filters
if (request.IdFilter.HasValue)
query = query.Where(x => x.Id == request.IdFilter.Value);
if (request.AmountFilter.HasValue)
query = query.Where(x => x.Amount == request.AmountFilter.Value);
if (!string.IsNullOrEmpty(request.DescriptionFilter))
query = query.Where(x => x.Description.Contains(request.DescriptionFilter));
if (request.PaymentStatusFilter.HasValue)
{
var status = request.PaymentStatusFilter.Value
? Domain.Enums.PaymentStatus.Success
: Domain.Enums.PaymentStatus.Reject;
query = query.Where(x => x.PaymentStatus == status);
}
if (!string.IsNullOrEmpty(request.RefIdFilter))
query = query.Where(x => x.RefId == request.RefIdFilter);
if (request.TypeFilter.HasValue)
query = query.Where(x => (int)x.Type == request.TypeFilter.Value);
// Apply sorting
if (!string.IsNullOrEmpty(request.SortBy))
query = query.ApplyOrder(request.SortBy);
else
query = query.OrderByDescending(x => x.Created);
// Get metadata
var metaData = await query.GetMetaData(request.PaginationState, cancellationToken);
// Get paginated results
var transactions = await query
.PaginatedListAsync(request.PaginationState)
.ToListAsync(cancellationToken);
var models = transactions.Select(t => new CustomerTransactionModel
{
Id = t.Id,
Amount = t.Amount,
Description = t.Description ?? "",
PaymentStatus = t.PaymentStatus,
PaymentDate = t.PaymentDate,
RefId = t.RefId ?? "",
Type = t.Type
}).ToList();
return new GetCustomerTransactionsByFilterResponseDto
{
MetaData = metaData,
Models = models
};
}
}
@@ -0,0 +1,21 @@
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter;
public class GetCustomerTransactionsByFilterResponseDto
{
public MetaData MetaData { get; set; }
public List<CustomerTransactionModel> Models { get; set; } = new();
}
public class CustomerTransactionModel
{
public long Id { get; set; }
public long Amount { get; set; }
public string Description { get; set; }
public PaymentStatus PaymentStatus { get; set; }
public DateTime? PaymentDate { get; set; }
public string RefId { get; set; }
public TransactionType Type { get; set; }
}
@@ -0,0 +1,22 @@
using MediatR;
namespace CMSMicroservice.Application.UserAddressCQ.Commands.CreateCustomerAddress;
/// <summary>
/// Command برای ایجاد آدرس جدید برای کاربر فعلی
/// </summary>
public class CreateCustomerAddressCommand : IRequest<CreateCustomerAddressCommandResponse>
{
public string Title { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string PostalCode { get; set; } = string.Empty;
public bool IsDefault { get; set; }
public long CityId { get; set; }
// UserId from ICurrentUserService
}
public class CreateCustomerAddressCommandResponse
{
public long Id { get; set; }
public string Message { get; set; } = string.Empty;
}
@@ -0,0 +1,62 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.UserAddressCQ.Commands.CreateCustomerAddress;
public class CreateCustomerAddressCommandHandler : IRequestHandler<CreateCustomerAddressCommand, CreateCustomerAddressCommandResponse>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public CreateCustomerAddressCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<CreateCustomerAddressCommandResponse> Handle(CreateCustomerAddressCommand request, CancellationToken cancellationToken)
{
// Extract UserId from JWT token
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// If this address is set as default, unset other defaults
if (request.IsDefault)
{
var existingDefaults = await _context.UserAddresses
.Where(ua => ua.UserId == userId && ua.IsDefault && !ua.IsDeleted)
.ToListAsync(cancellationToken);
foreach (var addr in existingDefaults)
{
addr.IsDefault = false;
}
}
// Create new address
var address = new UserAddress
{
UserId = userId,
Title = request.Title,
Address = request.Address,
PostalCode = request.PostalCode,
IsDefault = request.IsDefault,
CityId = request.CityId,
Created = DateTime.UtcNow
};
_context.UserAddresses.Add(address);
await _context.SaveChangesAsync(cancellationToken);
return new CreateCustomerAddressCommandResponse
{
Id = address.Id,
Message = "آدرس با موفقیت ایجاد شد"
};
}
}
@@ -0,0 +1,12 @@
using MediatR;
namespace CMSMicroservice.Application.UserAddressCQ.Commands.DeleteCustomerAddress;
/// <summary>
/// Command برای حذف آدرس کاربر فعلی
/// </summary>
public class DeleteCustomerAddressCommand : IRequest<Unit>
{
public long Id { get; set; }
// UserId from ICurrentUserService
}
@@ -0,0 +1,45 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.UserAddressCQ.Commands.DeleteCustomerAddress;
public class DeleteCustomerAddressCommandHandler : IRequestHandler<DeleteCustomerAddressCommand, Unit>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public DeleteCustomerAddressCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<Unit> Handle(DeleteCustomerAddressCommand request, CancellationToken cancellationToken)
{
// Extract UserId from JWT token
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// Find address and verify ownership
var address = await _context.UserAddresses
.FirstOrDefaultAsync(ua => ua.Id == request.Id && ua.UserId == userId && !ua.IsDeleted, cancellationToken);
if (address == null)
{
throw new Exception("آدرس یافت نشد یا به شما تعلق ندارد");
}
// Soft delete
address.IsDeleted = true;
address.LastModified = DateTime.UtcNow;
_context.UserAddresses.Update(address);
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
@@ -0,0 +1,12 @@
using MediatR;
namespace CMSMicroservice.Application.UserAddressCQ.Commands.SetCustomerDefaultAddress;
/// <summary>
/// Command برای تنظیم آدرس پیش‌فرض کاربر فعلی
/// </summary>
public class SetCustomerDefaultAddressCommand : IRequest<Unit>
{
public long Id { get; set; }
// UserId from ICurrentUserService
}
@@ -0,0 +1,55 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.UserAddressCQ.Commands.SetCustomerDefaultAddress;
public class SetCustomerDefaultAddressCommandHandler : IRequestHandler<SetCustomerDefaultAddressCommand, Unit>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public SetCustomerDefaultAddressCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<Unit> Handle(SetCustomerDefaultAddressCommand request, CancellationToken cancellationToken)
{
// Extract UserId from JWT token
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// Find address and verify ownership
var address = await _context.UserAddresses
.FirstOrDefaultAsync(ua => ua.Id == request.Id && ua.UserId == userId && !ua.IsDeleted, cancellationToken);
if (address == null)
{
throw new Exception("آدرس یافت نشد یا به شما تعلق ندارد");
}
// Unset all other defaults for this user
var existingDefaults = await _context.UserAddresses
.Where(ua => ua.UserId == userId && ua.IsDefault && ua.Id != request.Id && !ua.IsDeleted)
.ToListAsync(cancellationToken);
foreach (var addr in existingDefaults)
{
addr.IsDefault = false;
}
// Set this address as default
address.IsDefault = true;
address.LastModified = DateTime.UtcNow;
_context.UserAddresses.Update(address);
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
@@ -0,0 +1,17 @@
using MediatR;
namespace CMSMicroservice.Application.UserAddressCQ.Commands.UpdateCustomerAddress;
/// <summary>
/// Command برای به‌روزرسانی آدرس کاربر فعلی
/// </summary>
public class UpdateCustomerAddressCommand : IRequest<Unit>
{
public long Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string PostalCode { get; set; } = string.Empty;
public bool IsDefault { get; set; }
public long CityId { get; set; }
// UserId from ICurrentUserService
}
@@ -0,0 +1,62 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.UserAddressCQ.Commands.UpdateCustomerAddress;
public class UpdateCustomerAddressCommandHandler : IRequestHandler<UpdateCustomerAddressCommand, Unit>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public UpdateCustomerAddressCommandHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<Unit> Handle(UpdateCustomerAddressCommand request, CancellationToken cancellationToken)
{
// Extract UserId from JWT token
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// Find address and verify ownership
var address = await _context.UserAddresses
.FirstOrDefaultAsync(ua => ua.Id == request.Id && ua.UserId == userId && !ua.IsDeleted, cancellationToken);
if (address == null)
{
throw new Exception("آدرس یافت نشد یا به شما تعلق ندارد");
}
// If setting as default, unset other defaults
if (request.IsDefault && !address.IsDefault)
{
var existingDefaults = await _context.UserAddresses
.Where(ua => ua.UserId == userId && ua.IsDefault && ua.Id != request.Id && !ua.IsDeleted)
.ToListAsync(cancellationToken);
foreach (var addr in existingDefaults)
{
addr.IsDefault = false;
}
}
// Update address
address.Title = request.Title;
address.Address = request.Address;
address.PostalCode = request.PostalCode;
address.IsDefault = request.IsDefault;
address.CityId = request.CityId;
address.LastModified = DateTime.UtcNow;
_context.UserAddresses.Update(address);
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
@@ -0,0 +1,11 @@
using MediatR;
namespace CMSMicroservice.Application.UserAddressCQ.Queries.GetCustomerAddresses;
/// <summary>
/// Query برای دریافت لیست آدرس‌های کاربر فعلی
/// </summary>
public class GetCustomerAddressesQuery : IRequest<GetCustomerAddressesQueryResponse>
{
// UserId from ICurrentUserService
}
@@ -0,0 +1,53 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.UserAddressCQ.Queries.GetCustomerAddresses;
public class GetCustomerAddressesQueryHandler : IRequestHandler<GetCustomerAddressesQuery, GetCustomerAddressesQueryResponse>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerAddressesQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerAddressesQueryResponse> Handle(GetCustomerAddressesQuery request, CancellationToken cancellationToken)
{
// Extract UserId from JWT token
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
if (userId == 0)
{
throw new UnauthorizedAccessException("User not authenticated");
}
// Get all addresses for the current user
var addresses = await _context.UserAddresses
.Where(ua => ua.UserId == userId && !ua.IsDeleted)
.OrderByDescending(ua => ua.IsDefault)
.ThenByDescending(ua => ua.Created)
.ToListAsync(cancellationToken);
var response = new GetCustomerAddressesQueryResponse();
foreach (var addr in addresses)
{
response.Addresses.Add(new CustomerAddressModel
{
Id = addr.Id,
Title = addr.Title,
Address = addr.Address,
PostalCode = addr.PostalCode,
IsDefault = addr.IsDefault,
CityId = addr.CityId,
CityName = string.Empty, // Will be populated by FrontOffice from City service
ProvinceName = string.Empty // Will be populated by FrontOffice from City service
});
}
return response;
}
}
@@ -0,0 +1,18 @@
namespace CMSMicroservice.Application.UserAddressCQ.Queries.GetCustomerAddresses;
public class GetCustomerAddressesQueryResponse
{
public List<CustomerAddressModel> Addresses { get; set; } = new();
}
public class CustomerAddressModel
{
public long Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string PostalCode { get; set; } = string.Empty;
public bool IsDefault { get; set; }
public long CityId { get; set; }
public string CityName { get; set; } = string.Empty;
public string ProvinceName { get; set; } = string.Empty;
}
@@ -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);
}
}
@@ -0,0 +1,7 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder;
public class GetCustomerOrderQuery : IRequest<GetCustomerOrderResponseDto>
{
public long OrderId { get; set; }
public long UserId { get; set; }
}
@@ -0,0 +1,75 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder;
public class GetCustomerOrderQueryHandler : IRequestHandler<GetCustomerOrderQuery, GetCustomerOrderResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerOrderQueryHandler(
IApplicationDbContext context,
ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerOrderResponseDto> Handle(GetCustomerOrderQuery request, CancellationToken cancellationToken)
{
// Resolve UserId from JWT if not specified
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
: request.UserId;
if (userId == 0)
throw new UnauthorizedAccessException("User ID not found");
var order = await _context.UserOrders
.AsNoTracking()
.Where(x => x.Id == request.OrderId && x.UserId == userId)
.Include(x => x.Package)
.Include(x => x.Transaction)
.Include(x => x.UserAddress)
.Include(x => x.User)
.Include(x => x.FactorDetails)
.ThenInclude(f => f.Product)
.Include(x => x.OrderVAT)
.FirstOrDefaultAsync(cancellationToken);
if (order == null)
throw new NotFoundException(nameof(UserOrder), request.OrderId);
return new GetCustomerOrderResponseDto
{
Id = order.Id,
Amount = order.Amount,
PackageId = order.PackageId,
TransactionId = order.TransactionId,
PaymentStatus = order.PaymentStatus,
PaymentDate = order.PaymentDate,
UserId = order.UserId,
UserAddressId = order.UserAddressId,
PaymentMethod = order.PaymentMethod,
UserAddressText = order.UserAddress?.Address ?? "",
DeliveryStatus = order.DeliveryStatus,
TrackingCode = order.TrackingCode ?? "",
DeliveryDescription = order.DeliveryDescription ?? "",
UserFullName = $"{order.User?.FirstName ?? ""} {order.User?.LastName ?? ""}".Trim(),
UserNationalCode = order.User?.NationalCode ?? "",
VatAmount = order.OrderVAT?.VATAmount ?? 0,
VatPercentage = order.OrderVAT != null ? (double)order.OrderVAT.VATRate * 100 : 0,
FactorDetails = order.FactorDetails?.Select(fd => new FactorDetailDto
{
ProductId = fd.ProductId,
ProductTitle = fd.Product?.Title ?? "",
ProductThumbnailPath = fd.Product?.ThumbnailPath ?? "",
UnitPrice = fd.UnitPrice,
Count = fd.Count,
UnitDiscountPrice = fd.UnitDiscountPrice
}).ToList() ?? new List<FactorDetailDto>()
};
}
}
@@ -0,0 +1,35 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder;
public class GetCustomerOrderResponseDto
{
public long Id { get; set; }
public long Amount { get; set; }
public long? PackageId { get; set; }
public long? TransactionId { get; set; }
public PaymentStatus PaymentStatus { get; set; }
public DateTime? PaymentDate { get; set; }
public long UserId { get; set; }
public long UserAddressId { get; set; }
public PaymentMethod? PaymentMethod { get; set; }
public string UserAddressText { get; set; }
public List<FactorDetailDto> FactorDetails { get; set; } = new();
public DeliveryStatus DeliveryStatus { get; set; }
public string TrackingCode { get; set; }
public string DeliveryDescription { get; set; }
public string UserFullName { get; set; }
public string UserNationalCode { get; set; }
public long VatAmount { get; set; }
public double VatPercentage { get; set; }
}
public class FactorDetailDto
{
public long ProductId { get; set; }
public string ProductTitle { get; set; }
public string ProductThumbnailPath { get; set; }
public long? UnitPrice { get; set; }
public int? Count { get; set; }
public long? UnitDiscountPrice { get; set; }
}
@@ -0,0 +1,12 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory;
public class GetCustomerOrderHistoryQuery : IRequest<GetCustomerOrderHistoryResponseDto>
{
public long UserId { get; set; }
public PaginationState PaginationState { get; set; }
public int? StatusFilter { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
}
@@ -0,0 +1,155 @@
using CMSMicroservice.Application.Common.Extensions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory;
public class GetCustomerOrderHistoryQueryHandler : IRequestHandler<GetCustomerOrderHistoryQuery, GetCustomerOrderHistoryResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerOrderHistoryQueryHandler(
IApplicationDbContext context,
ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerOrderHistoryResponseDto> Handle(GetCustomerOrderHistoryQuery request, CancellationToken cancellationToken)
{
// Resolve UserId from JWT if not specified
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
: request.UserId;
if (userId == 0)
throw new UnauthorizedAccessException("User ID not found");
var query = _context.UserOrders
.AsNoTracking()
.Where(x => x.UserId == userId)
.Include(x => x.Package)
.Include(x => x.FactorDetails)
.AsQueryable();
// Apply status filter if specified
if (request.StatusFilter.HasValue)
{
// Status filter is based on OrderStatusEnum from Proto
// We need to map to DeliveryStatus enum values
var deliveryStatus = MapProtoStatusToDeliveryStatus(request.StatusFilter.Value);
if (deliveryStatus.HasValue)
query = query.Where(x => x.DeliveryStatus == deliveryStatus.Value);
}
// Apply date filters
if (request.FromDate.HasValue)
query = query.Where(x => x.Created >= request.FromDate.Value);
if (request.ToDate.HasValue)
query = query.Where(x => x.Created <= request.ToDate.Value);
// Order by most recent first
query = query.OrderByDescending(x => x.Created);
// Get metadata
var metaData = await query.GetMetaData(request.PaginationState, cancellationToken);
// Get paginated results
var orders = await query
.PaginatedListAsync(request.PaginationState)
.ToListAsync(cancellationToken);
var orderModels = orders.Select(order => new CustomerOrderHistoryModel
{
Id = order.Id,
Amount = order.Amount,
PackageId = order.PackageId,
PackageName = order.Package?.Title ?? "سفارش محصولات",
Status = MapDeliveryStatusToProtoStatus(order.DeliveryStatus),
StatusMessage = GetStatusMessage(order.DeliveryStatus),
OrderDate = order.Created,
DeliveryDate = order.PaymentDate?.AddDays(GetEstimatedDeliveryDays(order.DeliveryStatus)),
TrackingCode = order.TrackingCode ?? "",
ItemsCount = order.FactorDetails?.Count ?? 0,
CanCancel = CanCancelOrder(order.DeliveryStatus, order.Created),
CanReorder = true // همیشه می‌توان دوباره سفارش داد
}).ToList();
return new GetCustomerOrderHistoryResponseDto
{
MetaData = metaData,
Orders = orderModels
};
}
private DeliveryStatus? MapProtoStatusToDeliveryStatus(int protoStatus)
{
// OrderStatusEnum from Proto:
// 0=Pending, 1=Confirmed, 2=Processing, 3=Shipped, 4=Delivered, 5=Cancelled, 6=Refunded
return protoStatus switch
{
0 => DeliveryStatus.Pending,
1 => DeliveryStatus.Pending,
2 => DeliveryStatus.Pending,
3 => DeliveryStatus.InTransit,
4 => DeliveryStatus.Delivered,
5 => DeliveryStatus.Cancelled,
6 => DeliveryStatus.Cancelled,
_ => null
};
}
private int MapDeliveryStatusToProtoStatus(DeliveryStatus status)
{
return status switch
{
DeliveryStatus.None => 0,
DeliveryStatus.Pending => 1,
DeliveryStatus.InTransit => 3,
DeliveryStatus.Delivered => 4,
DeliveryStatus.Cancelled => 5,
DeliveryStatus.Returned => 6,
_ => 0
};
}
private string GetStatusMessage(DeliveryStatus status)
{
return status switch
{
DeliveryStatus.None => "ثبت نشده",
DeliveryStatus.Pending => "در انتظار پردازش",
DeliveryStatus.InTransit => "ارسال شده",
DeliveryStatus.Delivered => "تحویل داده شد",
DeliveryStatus.Cancelled => "لغو شده",
DeliveryStatus.Returned => "مرجوع شده",
_ => "نامشخص"
};
}
private int GetEstimatedDeliveryDays(DeliveryStatus status)
{
return status switch
{
DeliveryStatus.None => 7,
DeliveryStatus.Pending => 5,
DeliveryStatus.InTransit => 3,
DeliveryStatus.Delivered => 0,
_ => 0
};
}
private bool CanCancelOrder(DeliveryStatus status, DateTime orderDate)
{
// فقط سفارشات Pending یا None که کمتر از 24 ساعت از ثبت آنها گذشته قابل لغو هستند
if (status != DeliveryStatus.Pending && status != DeliveryStatus.None)
return false;
var hoursSinceOrder = (DateTime.UtcNow - orderDate).TotalHours;
return hoursSinceOrder < 24;
}
}
@@ -0,0 +1,26 @@
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory;
public class GetCustomerOrderHistoryResponseDto
{
public MetaData MetaData { get; set; }
public List<CustomerOrderHistoryModel> Orders { get; set; } = new();
}
public class CustomerOrderHistoryModel
{
public long Id { get; set; }
public long Amount { get; set; }
public long? PackageId { get; set; }
public string PackageName { get; set; }
public int Status { get; set; }
public string StatusMessage { get; set; }
public DateTime OrderDate { get; set; }
public DateTime? DeliveryDate { get; set; }
public string TrackingCode { get; set; }
public int ItemsCount { get; set; }
public bool CanCancel { get; set; }
public bool CanReorder { get; set; }
}
@@ -0,0 +1,13 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders;
public class GetCustomerOrdersQuery : IRequest<GetCustomerOrdersResponseDto>
{
public long UserId { get; set; }
public PaginationState PaginationState { get; set; }
public int? PaymentStatusFilter { get; set; }
public int? DeliveryStatusFilter { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
}
@@ -0,0 +1,103 @@
using CMSMicroservice.Application.Common.Extensions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using Mapster;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders;
public class GetCustomerOrdersQueryHandler : IRequestHandler<GetCustomerOrdersQuery, GetCustomerOrdersResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerOrdersQueryHandler(
IApplicationDbContext context,
ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetCustomerOrdersResponseDto> Handle(GetCustomerOrdersQuery request, CancellationToken cancellationToken)
{
// Resolve UserId from JWT if not specified
var userId = request.UserId == 0
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
: request.UserId;
if (userId == 0)
throw new UnauthorizedAccessException("User ID not found");
var query = _context.UserOrders
.AsNoTracking()
.Where(x => x.UserId == userId)
.Include(x => x.Package)
.Include(x => x.Transaction)
.Include(x => x.UserAddress)
.Include(x => x.User)
.Include(x => x.FactorDetails)
.ThenInclude(f => f.Product)
.Include(x => x.OrderVAT)
.AsQueryable();
// Apply filters
if (request.PaymentStatusFilter.HasValue)
query = query.Where(x => (int)x.PaymentStatus == request.PaymentStatusFilter.Value);
if (request.DeliveryStatusFilter.HasValue)
query = query.Where(x => (int)x.DeliveryStatus == request.DeliveryStatusFilter.Value);
if (request.FromDate.HasValue)
query = query.Where(x => x.Created >= request.FromDate.Value);
if (request.ToDate.HasValue)
query = query.Where(x => x.Created <= request.ToDate.Value);
// Order by most recent first
query = query.OrderByDescending(x => x.Created);
// Get metadata
var metaData = await query.GetMetaData(request.PaginationState, cancellationToken);
// Get paginated results
var orders = await query
.PaginatedListAsync(request.PaginationState)
.ToListAsync(cancellationToken);
var models = orders.Select(order => new CustomerOrderModel
{
Id = order.Id,
Amount = order.Amount,
PackageId = order.PackageId,
TransactionId = order.TransactionId,
PaymentStatus = order.PaymentStatus,
PaymentDate = order.PaymentDate,
UserId = order.UserId,
UserAddressId = order.UserAddressId,
PaymentMethod = order.PaymentMethod,
UserAddressText = order.UserAddress?.Address ?? "",
DeliveryStatus = order.DeliveryStatus,
TrackingCode = order.TrackingCode ?? "",
DeliveryDescription = order.DeliveryDescription ?? "",
UserFullName = $"{order.User?.FirstName ?? ""} {order.User?.LastName ?? ""}".Trim(),
UserNationalCode = order.User?.NationalCode ?? "",
VatAmount = order.OrderVAT?.VATAmount ?? 0,
VatPercentage = order.OrderVAT != null ? (double)order.OrderVAT.VATRate * 100 : 0,
FactorDetails = order.FactorDetails?.Select(fd => new FactorDetailModel
{
ProductId = fd.ProductId,
ProductTitle = fd.Product?.Title ?? "",
ProductThumbnailPath = fd.Product?.ThumbnailPath ?? "",
UnitPrice = fd.UnitPrice,
Count = fd.Count,
UnitDiscountPrice = fd.UnitDiscountPrice
}).ToList() ?? new List<FactorDetailModel>()
}).ToList();
return new GetCustomerOrdersResponseDto
{
MetaData = metaData,
Models = models
};
}
}
@@ -0,0 +1,42 @@
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders;
public class GetCustomerOrdersResponseDto
{
public MetaData MetaData { get; set; }
public List<CustomerOrderModel> Models { get; set; } = new();
}
public class CustomerOrderModel
{
public long Id { get; set; }
public long Amount { get; set; }
public long? PackageId { get; set; }
public long? TransactionId { get; set; }
public PaymentStatus PaymentStatus { get; set; }
public DateTime? PaymentDate { get; set; }
public long UserId { get; set; }
public long UserAddressId { get; set; }
public PaymentMethod? PaymentMethod { get; set; }
public string UserAddressText { get; set; }
public List<FactorDetailModel> FactorDetails { get; set; } = new();
public DeliveryStatus DeliveryStatus { get; set; }
public string TrackingCode { get; set; }
public string DeliveryDescription { get; set; }
public string UserFullName { get; set; }
public string UserNationalCode { get; set; }
public long VatAmount { get; set; }
public double VatPercentage { get; set; }
}
public class FactorDetailModel
{
public long ProductId { get; set; }
public string ProductTitle { get; set; }
public string ProductThumbnailPath { get; set; }
public long? UnitPrice { get; set; }
public int? Count { get; set; }
public long? UnitDiscountPrice { get; set; }
}
@@ -0,0 +1,14 @@
namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog;
public class GetCustomerWalletChangeLogQuery : IRequest<List<GetCustomerWalletChangeLogResponseDto>>
{
/// <summary>
/// فیلتر بر اساس شناسه ارجاع (اختیاری)
/// </summary>
public long? ReferenceId { get; set; }
/// <summary>
/// فیلتر بر اساس نوع تغییر - افزایشی یا کاهشی (اختیاری)
/// </summary>
public bool? IsIncrease { get; set; }
}
@@ -0,0 +1,61 @@
namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog;
public class GetCustomerWalletChangeLogQueryHandler : IRequestHandler<GetCustomerWalletChangeLogQuery, List<GetCustomerWalletChangeLogResponseDto>>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerWalletChangeLogQueryHandler(
IApplicationDbContext context,
ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<List<GetCustomerWalletChangeLogResponseDto>> Handle(
GetCustomerWalletChangeLogQuery request,
CancellationToken cancellationToken)
{
// Get current user's ID from JWT
if (!long.TryParse(_currentUser.UserId, out var currentUserId))
{
throw new UnauthorizedAccessException("User ID not found in token");
}
// Get user's wallet
var userWallet = await _context.UserWallets
.AsNoTracking()
.Where(x => x.UserId == currentUserId)
.FirstOrDefaultAsync(cancellationToken);
if (userWallet == null)
{
throw new NotFoundException(nameof(UserWallet), currentUserId);
}
// Build query for wallet change logs
var query = _context.UserWalletChangeLogs
.AsNoTracking()
.Where(x => x.WalletId == userWallet.Id);
// Apply optional filters
if (request.ReferenceId.HasValue)
{
query = query.Where(x => x.RefrenceId == request.ReferenceId.Value);
}
if (request.IsIncrease.HasValue)
{
query = query.Where(x => x.IsIncrease == request.IsIncrease.Value);
}
// Order by newest first
var result = await query
.OrderByDescending(x => x.Created)
.ProjectToType<GetCustomerWalletChangeLogResponseDto>()
.ToListAsync(cancellationToken);
return result;
}
}
@@ -0,0 +1,39 @@
namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog;
public class GetCustomerWalletChangeLogResponseDto
{
/// <summary>
/// موجودی جاری
/// </summary>
public long CurrentBalance { get; set; }
/// <summary>
/// مقدار تغییر
/// </summary>
public long ChangeValue { get; set; }
/// <summary>
/// موجودی جاری شبکه
/// </summary>
public long CurrentNetworkBalance { get; set; }
/// <summary>
/// مقدار تغییر شبکه
/// </summary>
public long ChangeNerworkValue { get; set; }
/// <summary>
/// افزایشی است؟
/// </summary>
public bool IsIncrease { get; set; }
/// <summary>
/// شناسه ارجاع
/// </summary>
public long? RefrenceId { get; set; }
/// <summary>
/// تاریخ ایجاد
/// </summary>
public DateTime Created { get; set; }
}
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings;
public class GetCustomerWithdrawalSettingsQuery : IRequest<GetCustomerWithdrawalSettingsResponseDto>
{
// No parameters needed - returns system-wide settings
}
@@ -0,0 +1,19 @@
namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings;
public class GetCustomerWithdrawalSettingsQueryHandler : IRequestHandler<GetCustomerWithdrawalSettingsQuery, GetCustomerWithdrawalSettingsResponseDto>
{
// TODO: In future, read from SystemConfiguration table
private const long MIN_WITHDRAWAL_AMOUNT = 50000; // 50,000 Rials
public Task<GetCustomerWithdrawalSettingsResponseDto> Handle(
GetCustomerWithdrawalSettingsQuery request,
CancellationToken cancellationToken)
{
var response = new GetCustomerWithdrawalSettingsResponseDto
{
MinWithdrawalAmount = MIN_WITHDRAWAL_AMOUNT
};
return Task.FromResult(response);
}
}
@@ -0,0 +1,9 @@
namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings;
public class GetCustomerWithdrawalSettingsResponseDto
{
/// <summary>
/// حداقل مبلغ برداشت (ریال)
/// </summary>
public long MinWithdrawalAmount { get; set; }
}
@@ -0,0 +1,10 @@
namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals;
public class GetCustomerWithdrawalsQuery : IRequest<List<GetCustomerWithdrawalsResponseDto>>
{
/// <summary>
/// فیلتر بر اساس وضعیت (اختیاری)
/// 0: Pending, 1: Paid, 2: WithdrawRequested, 3: Withdrawn, 4: PaymentFailed, 5: Cancelled
/// </summary>
public int? Status { get; set; }
}
@@ -0,0 +1,56 @@
namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals;
public class GetCustomerWithdrawalsQueryHandler : IRequestHandler<GetCustomerWithdrawalsQuery, List<GetCustomerWithdrawalsResponseDto>>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetCustomerWithdrawalsQueryHandler(
IApplicationDbContext context,
ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<List<GetCustomerWithdrawalsResponseDto>> Handle(
GetCustomerWithdrawalsQuery request,
CancellationToken cancellationToken)
{
// Get current user's ID from JWT
if (!long.TryParse(_currentUser.UserId, out var currentUserId))
{
throw new UnauthorizedAccessException("User ID not found in token");
}
// Build query for user's commission payouts (withdrawals)
var query = _context.UserCommissionPayouts
.AsNoTracking()
.Include(x => x.WeekDefinition)
.Where(x => x.UserId == currentUserId);
// Apply status filter if provided
if (request.Status.HasValue)
{
query = query.Where(x => (int)x.Status == request.Status.Value);
}
// Order by newest first and map to DTO
var result = await query
.OrderByDescending(x => x.Created)
.Select(x => new GetCustomerWithdrawalsResponseDto
{
Id = x.Id,
WeekDefinitionId = x.WeekDefinitionId,
WeekDisplayName = x.WeekDefinition.DisplayName ?? "",
TotalAmount = x.TotalAmount,
Status = (int)x.Status,
WithdrawalMethod = x.WithdrawalMethod.HasValue ? (int)x.WithdrawalMethod.Value : null,
IbanNumber = x.IbanNumber ?? "",
Created = x.Created
})
.ToListAsync(cancellationToken);
return result;
}
}
@@ -0,0 +1,44 @@
namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals;
public class GetCustomerWithdrawalsResponseDto
{
/// <summary>
/// شناسه
/// </summary>
public long Id { get; set; }
/// <summary>
/// شناسه تعریف هفته
/// </summary>
public long WeekDefinitionId { get; set; }
/// <summary>
/// نام نمایشی هفته
/// </summary>
public string WeekDisplayName { get; set; }
/// <summary>
/// مبلغ کل
/// </summary>
public long TotalAmount { get; set; }
/// <summary>
/// وضعیت
/// </summary>
public int Status { get; set; }
/// <summary>
/// روش برداشت
/// </summary>
public int? WithdrawalMethod { get; set; }
/// <summary>
/// شماره شبا
/// </summary>
public string IbanNumber { get; set; }
/// <summary>
/// تاریخ ایجاد
/// </summary>
public DateTime Created { get; set; }
}
@@ -2,21 +2,28 @@ namespace CMSMicroservice.Application.UserWalletCQ.Queries.GetUserWallet;
public class GetUserWalletQueryHandler : IRequestHandler<GetUserWalletQuery, GetUserWalletResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
public GetUserWalletQueryHandler(IApplicationDbContext context)
public GetUserWalletQueryHandler(IApplicationDbContext context, ICurrentUserService currentUser)
{
_context = context;
_currentUser = currentUser;
}
public async Task<GetUserWalletResponseDto> Handle(GetUserWalletQuery 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.UserWallets
.AsNoTracking()
.Where(x => x.Id == request.Id)
.Where(x => x.UserId == userId) // Changed from x.Id to x.UserId
.ProjectToType<GetUserWalletResponseDto>()
.FirstOrDefaultAsync(cancellationToken);
return response ?? throw new NotFoundException(nameof(UserWallet), request.Id);
return response ?? throw new NotFoundException(nameof(UserWallet), userId);
}
}
@@ -9,5 +9,6 @@ public class GetUserWalletResponseDto
public long Balance { get; set; }
//موجودی شبکه
public long NetworkBalance { get; set; }
//موجودی تخفیف
public long DiscountBalance { get; set; }
}