Complete FrontOffice BFF to CMS Migration

- Migrated all 9 services from FrontOffice.BFF to CMS architecture
- Enhanced user.proto with 7 additional Customer API endpoints:
  * UpdateCustomerProfile, GetCustomerProfile
  * ChangeCustomerPassword with validation
  * GetCustomerReferrals with commission stats
  * UploadCustomerAvatar with file validation
  * GetCustomerSettings, UpdateCustomerSettings
- All services now support Customer endpoints with /Customer/ prefix
- Mock implementations with realistic Persian data
- Fixed namespace conflicts and compilation issues
- Comprehensive testing completed for all endpoints
- Services migrated: Categories, City, UserCarts, Products, UserWallet,
  Transaction, UserOrder, Package, User (enhanced)
This commit is contained in:
masoodafar-web
2026-01-30 08:53:09 +03:30
parent 96daf899c7
commit 658d076bdf
170 changed files with 3770 additions and 4364 deletions
@@ -1,39 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder;
/// <summary>
/// اعمال تخفیف به سفارش
/// </summary>
public record ApplyDiscountToOrderCommand : IRequest<ApplyDiscountToOrderResponseDto>
{
/// <summary>
/// شناسه سفارش
/// </summary>
public long OrderId { get; init; }
/// <summary>
/// مبلغ تخفیف (ریال)
/// </summary>
public long DiscountAmount { get; init; }
/// <summary>
/// دلیل تخفیف
/// </summary>
public string Reason { get; init; } = string.Empty;
/// <summary>
/// کد تخفیف (اختیاری)
/// </summary>
public string? DiscountCode { get; init; }
}
/// <summary>
/// پاسخ اعمال تخفیف
/// </summary>
public class ApplyDiscountToOrderResponseDto
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public long OriginalAmount { get; set; }
public long DiscountAmount { get; set; }
public long FinalAmount { get; set; }
}
@@ -1,74 +0,0 @@
using CMSMicroservice.Application.Common.Interfaces;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder;
public class ApplyDiscountToOrderCommandHandler : IRequestHandler<ApplyDiscountToOrderCommand, ApplyDiscountToOrderResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ILogger<ApplyDiscountToOrderCommandHandler> _logger;
public ApplyDiscountToOrderCommandHandler(
IApplicationDbContext context,
ILogger<ApplyDiscountToOrderCommandHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<ApplyDiscountToOrderResponseDto> Handle(ApplyDiscountToOrderCommand request, CancellationToken cancellationToken)
{
// TODO: پیاده‌سازی اعمال تخفیف به سفارش
// 1. پیدا کردن سفارش:
// - var order = await _context.UserOrders.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken)
// - بررسی null و پرتاب NotFoundException
//
// 2. بررسی شرایط اعمال تخفیف:
// - سفارش نباید Delivered یا Cancelled باشد
// - مبلغ تخفیف نباید بیشتر از Amount باشد
// - if (order.DeliveryStatus == DeliveryStatus.Delivered || order.DeliveryStatus == DeliveryStatus.Cancelled)
// throw new InvalidOperationException("نمی‌توان به این سفارش تخفیف اعمال کرد")
// - if (request.DiscountAmount > order.Amount)
// throw new InvalidOperationException("مبلغ تخفیف نمی‌تواند بیشتر از مبلغ سفارش باشد")
//
// 3. محاسبه مبلغ نهایی:
// - var originalAmount = order.Amount
// - var newDiscountedPrice = order.Amount - request.DiscountAmount
// - مطمئن شوید که منفی نشود: newDiscountedPrice = Math.Max(0, newDiscountedPrice)
//
// 4. به‌روزرسانی سفارش:
// - order.DiscountedPrice = newDiscountedPrice
// - اگر فیلد OrderDiscountAmount وجود دارد، آن را هم به‌روز کنید
// - order.OrderDiscountAmount = request.DiscountAmount
// - اضافه کردن به توضیحات:
// order.DeliveryDescription = (order.DeliveryDescription ?? "") +
// $"\nتخفیف اعمال شده: {request.DiscountAmount} ریال - دلیل: {request.Reason}"
//
// 5. ذخیره Log تخفیف (اختیاری - اگر جدول OrderDiscountLog دارید):
// - var discountLog = new OrderDiscountLog {
// OrderId = order.Id,
// DiscountAmount = request.DiscountAmount,
// Reason = request.Reason,
// DiscountCode = request.DiscountCode,
// AppliedAt = DateTime.Now
// }
// - await _context.OrderDiscountLogs.AddAsync(discountLog, cancellationToken)
//
// 6. ذخیره و Log:
// - await _context.SaveChangesAsync(cancellationToken)
// - _logger.LogInformation("Discount {Amount} applied to order {OrderId}: {Reason}",
// request.DiscountAmount, request.OrderId, request.Reason)
//
// 7. برگشت Response:
// - return new ApplyDiscountToOrderResponseDto {
// Success = true,
// Message = "تخفیف با موفقیت اعمال شد",
// OriginalAmount = originalAmount,
// DiscountAmount = request.DiscountAmount,
// FinalAmount = newDiscountedPrice
// }
//
// نکته: این تخفیف برای تخفیفات دستی Admin است و جدا از تخفیف‌های محصول
throw new NotImplementedException("ApplyDiscountToOrder needs implementation");
}
}
@@ -1,21 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder;
public class ApplyDiscountToOrderCommandValidator : AbstractValidator<ApplyDiscountToOrderCommand>
{
public ApplyDiscountToOrderCommandValidator()
{
RuleFor(x => x.OrderId)
.GreaterThan(0)
.WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
RuleFor(x => x.DiscountAmount)
.GreaterThan(0)
.WithMessage("مبلغ تخفیف باید بزرگتر از 0 باشد");
RuleFor(x => x.Reason)
.NotEmpty()
.WithMessage("دلیل تخفیف الزامی است")
.MaximumLength(500)
.WithMessage("دلیل تخفیف نمی‌تواند بیشتر از 500 کاراکتر باشد");
}
}
@@ -1,24 +0,0 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder;
/// <summary>
/// Command برای لغو سفارش
/// </summary>
public record CancelOrderCommand : IRequest<CancelOrderResponseDto>
{
/// <summary>
/// شناسه سفارش
/// </summary>
public long OrderId { get; init; }
/// <summary>
/// دلیل لغو سفارش
/// </summary>
public string CancelReason { get; init; }
/// <summary>
/// آیا مبلغ باید بازگردانده شود؟
/// </summary>
public bool RefundPayment { get; init; }
}
@@ -1,93 +0,0 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Events;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder;
public class CancelOrderCommandHandler : IRequestHandler<CancelOrderCommand, CancelOrderResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly IInventoryService _inventoryService;
public CancelOrderCommandHandler(
IApplicationDbContext context,
IInventoryService inventoryService)
{
_context = context;
_inventoryService = inventoryService;
}
public async Task<CancelOrderResponseDto> Handle(CancelOrderCommand request, CancellationToken cancellationToken)
{
// پیدا کردن سفارش با جزئیات
var order = await _context.UserOrders
.Include(o => o.Transaction)
.Include(o => o.FactorDetails)
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
if (order == null)
{
throw new NotFoundException(nameof(UserOrder), request.OrderId);
}
// چک کردن که سفارش قابل لغو باشد
if (order.DeliveryStatus == DeliveryStatus.Delivered)
{
throw new InvalidOperationException("سفارش تحویل داده شده قابل لغو نیست");
}
if (order.DeliveryStatus == DeliveryStatus.Cancelled)
{
throw new InvalidOperationException("این سفارش قبلاً لغو شده است");
}
// برگشت موجودی محصولات به انبار
foreach (var factorDetail in order.FactorDetails)
{
await _inventoryService.ProcessReturnAsync(
factorDetail.ProductId,
ProductType.RegularProduct,
factorDetail.Count,
order.Id,
$"لغو سفارش: {request.CancelReason}",
null,
cancellationToken);
}
// تغییر وضعیت سفارش
order.DeliveryStatus = DeliveryStatus.Cancelled;
order.DeliveryDescription = $"لغو شده: {request.CancelReason}";
// اگر درخواست بازگشت پول داریم و پرداخت موفق بوده
if (request.RefundPayment &&
order.Transaction != null &&
order.Transaction.PaymentStatus == PaymentStatus.Success)
{
// ایجاد تراکنش استرداد
var refundTransaction = new Transaction
{
Amount = -order.Amount,
Description = $"بازگشت وجه سفارش {request.OrderId}: {request.CancelReason}",
PaymentStatus = PaymentStatus.Success,
PaymentDate = DateTime.Now,
RefId = $"REFUND-ORDER-{order.Id}",
Type = TransactionType.Buy
};
await _context.Transactions.AddAsync(refundTransaction, cancellationToken);
}
// ثبت Event
order.AddDomainEvent(new CancelOrderEvent(order, request.CancelReason));
await _context.SaveChangesAsync(cancellationToken);
return new CancelOrderResponseDto
{
OrderId = order.Id,
Status = order.DeliveryStatus,
Message = "سفارش با موفقیت لغو شد",
RefundProcessed = request.RefundPayment && order.Transaction != null
};
}
}
@@ -1,17 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder;
public class CancelOrderCommandValidator : AbstractValidator<CancelOrderCommand>
{
public CancelOrderCommandValidator()
{
RuleFor(v => v.OrderId)
.GreaterThan(0)
.WithMessage("شناسه سفارش باید بزرگتر از صفر باشد");
RuleFor(v => v.CancelReason)
.NotEmpty()
.WithMessage("دلیل لغو سفارش الزامی است")
.MaximumLength(500)
.WithMessage("دلیل لغو نباید بیش از 500 کاراکتر باشد");
}
}
@@ -1,11 +0,0 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder;
public class CancelOrderResponseDto
{
public long OrderId { get; set; }
public DeliveryStatus Status { get; set; }
public string Message { get; set; }
public bool RefundProcessed { get; set; }
}
@@ -1,23 +0,0 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder;
public record CreateNewUserOrderCommand : IRequest<CreateNewUserOrderResponseDto>
{
//قیمت
public long Amount { get; init; }
//شناسه پکیج
public long PackageId { get; init; }
//شناسه تراکنش
public long? TransactionId { get; init; }
//وضعیت پرداخت
public PaymentStatus PaymentStatus { get; init; }
//تاریخ پرداخت
public DateTime? PaymentDate { get; init; }
//شناسه کاربر
public long UserId { get; init; }
//شناسه آدرس کاربر
public long UserAddressId { get; init; }
//
public PaymentMethod? PaymentMethod { get; init; }
}
@@ -1,28 +0,0 @@
using CMSMicroservice.Domain.Events;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder;
public class CreateNewUserOrderCommandHandler : IRequestHandler<CreateNewUserOrderCommand, CreateNewUserOrderResponseDto>
{
private readonly IApplicationDbContext _context;
public CreateNewUserOrderCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<CreateNewUserOrderResponseDto> Handle(CreateNewUserOrderCommand request,
CancellationToken cancellationToken)
{
if (await _context.UserOrders.AnyAsync(x => x.UserId == request.UserId && x.PackageId == request.PackageId, cancellationToken: cancellationToken))
throw new Exception(message: "duplicate order!!");
var package = await _context.Packages
.FirstOrDefaultAsync(x => x.Id == request.PackageId, cancellationToken) ?? throw new NotFoundException(nameof(Package), request.PackageId);
var entity = request.Adapt<UserOrder>();
entity.Amount = package.Price;
await _context.UserOrders.AddAsync(entity, cancellationToken);
entity.AddDomainEvent(new CreateNewUserOrderEvent(entity));
await _context.SaveChangesAsync(cancellationToken);
return entity.Adapt<CreateNewUserOrderResponseDto>();
}
}
@@ -1,27 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder;
public class CreateNewUserOrderCommandValidator : AbstractValidator<CreateNewUserOrderCommand>
{
public CreateNewUserOrderCommandValidator()
{
RuleFor(model => model.Amount)
.NotNull();
RuleFor(model => model.PackageId)
.NotNull();
RuleFor(model => model.PaymentStatus)
.IsInEnum()
.NotNull();
RuleFor(model => model.UserId)
.NotNull();
RuleFor(model => model.UserAddressId)
.NotNull();
RuleFor(model => model.PaymentMethod)
.IsInEnum();
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<CreateNewUserOrderCommand>.CreateWithOptions((CreateNewUserOrderCommand)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
@@ -1,7 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder;
public class CreateNewUserOrderResponseDto
{
//شناسه
public long Id { get; set; }
}
@@ -1,7 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.DeleteUserOrder;
public record DeleteUserOrderCommand : IRequest<Unit>
{
//شناسه
public long Id { get; init; }
}
@@ -1,22 +0,0 @@
using CMSMicroservice.Domain.Events;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.DeleteUserOrder;
public class DeleteUserOrderCommandHandler : IRequestHandler<DeleteUserOrderCommand, Unit>
{
private readonly IApplicationDbContext _context;
public DeleteUserOrderCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteUserOrderCommand request, CancellationToken cancellationToken)
{
var entity = await _context.UserOrders
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserOrder), request.Id);
entity.IsDeleted = true;
_context.UserOrders.Update(entity);
entity.AddDomainEvent(new DeleteUserOrderEvent(entity));
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
@@ -1,16 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.DeleteUserOrder;
public class DeleteUserOrderCommandValidator : AbstractValidator<DeleteUserOrderCommand>
{
public DeleteUserOrderCommandValidator()
{
RuleFor(model => model.Id)
.NotNull();
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<DeleteUserOrderCommand>.CreateWithOptions((DeleteUserOrderCommand)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
@@ -1,9 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder;
public record SubmitShopBuyOrderCommand : IRequest<SubmitShopBuyOrderResponseDto>
{
//کل مبلغ ورودی
public long TotalAmount { get; init; }
//شناسه کاربر
public long UserId { get; init; }
}
@@ -1,241 +0,0 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Common;
using CMSMicroservice.Domain.Enums;
using CMSMicroservice.Domain.Events;
using CMSMicroservice.Domain.Entities.Order;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder;
public class
SubmitShopBuyOrderCommandHandler : IRequestHandler<SubmitShopBuyOrderCommand, SubmitShopBuyOrderResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly IInventoryService _inventoryService;
private readonly ILogger<SubmitShopBuyOrderCommandHandler> _logger;
private float _vatRate;
public SubmitShopBuyOrderCommandHandler(
IApplicationDbContext context,
IInventoryService inventoryService,
ILogger<SubmitShopBuyOrderCommandHandler> logger)
{
_context = context;
_inventoryService = inventoryService;
_logger = logger;
}
public async Task<SubmitShopBuyOrderResponseDto> Handle(SubmitShopBuyOrderCommand request,
CancellationToken cancellationToken)
{
var user = await _context.Users
.Include(i => i.UserAddresses)
.Include(i => i.UserWallets)
.ThenInclude(i => i.UserWalletChangeLogs)
.Include(i => i.UserCarts)
.ThenInclude(i => i.Product)
.FirstOrDefaultAsync(w => w.Id == request.UserId, cancellationToken: cancellationToken);
if (user.UserCarts.Count == 0)
throw new NotFoundException("UserCart", request.UserId);
// چک موجودی محصولات
var outOfStockProducts = new List<string>();
var insufficientStockProducts = new List<(string Title, int Requested, int Available)>();
foreach (var cartItem in user.UserCarts)
{
if (cartItem.Product.RemainingCount <= 0)
{
outOfStockProducts.Add(cartItem.Product.Title);
}
else if (cartItem.Product.RemainingCount < cartItem.Count)
{
insufficientStockProducts.Add((cartItem.Product.Title, cartItem.Count,
cartItem.Product.RemainingCount));
}
}
if (outOfStockProducts.Any())
{
throw new Exception($"محصولات زیر ناموجود شده‌اند: {string.Join("، ", outOfStockProducts)}");
}
if (insufficientStockProducts.Any())
{
var messages = insufficientStockProducts.Select(p =>
$"«{p.Title}»: درخواست {p.Requested} عدد، موجودی {p.Available} عدد");
throw new Exception($"موجودی محصولات زیر کافی نیست: {string.Join(" | ", messages)}");
}
long finalAmount = 0;
// استفاده از SystemConstants برای VAT
if (SystemConstants.ShopVATEnabled)
{
_vatRate = (float)SystemConstants.ShopVAT;
finalAmount = AddVAT(user.UserCarts.Sum(s => s.Count * s.Product.Price));
_logger.LogInformation(
"Calculating final amount with VAT. Base Amount: {BaseAmount}, VAT Rate: {VATRate}, Final Amount: {FinalAmount}",
user.UserCarts.Sum(s => s.Count * s.Product.Price), _vatRate, finalAmount);
}
else
{
finalAmount = user.UserCarts.Sum(s => s.Count * s.Product.Price);
}
if (finalAmount != request.TotalAmount)
throw new Exception("مبلغ سفارش با مجموع سبد خرید مطابقت ندارد.");
var userWallet = user.UserWallets.FirstOrDefault();
if (userWallet == null)
throw new Exception("کیف پول کاربر یافت نشد.");
if (userWallet.Balance <= 0 || userWallet.Balance < request.TotalAmount)
throw new Exception("موجودی کیف پول کاربر برای انجام این تراکنش کافی نیست.");
var newTransaction = new Transaction()
{
Amount = request.TotalAmount,
Description = "خرید از فروشگاه",
PaymentStatus = PaymentStatus.Success,
PaymentDate = DateTime.Now,
Type = TransactionType.Buy,
RefId = "localwallet-" + Guid.NewGuid().ToString()
};
await _context.Transactions.AddAsync(newTransaction, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
var newWalletLog = new UserWalletChangeLog()
{
CurrentBalance = userWallet.Balance,
CurrentNetworkBalance = userWallet.NetworkBalance,
WalletId = userWallet.Id,
ChangeValue = -1 * request.TotalAmount,
IsIncrease = false,
RefrenceId = newTransaction.Id,
};
userWallet.Balance -= request.TotalAmount;
await _context.UserWalletChangeLogs.AddAsync(newWalletLog, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
var newOrder = new UserOrder()
{
Amount = request.TotalAmount,
PaymentStatus = PaymentStatus.Success,
PaymentMethod = PaymentMethod.Wallet,
PaymentDate = DateTime.Now,
UserId = request.UserId,
UserAddressId = user.UserAddresses.First(f => f.IsDefault).Id,
TransactionId = newTransaction.Id,
// سفارش فروشگاهی فیزیکی است، پس در ابتدا در انتظار ارسال است
DeliveryStatus = DeliveryStatus.Pending
};
await _context.UserOrders.AddAsync(newOrder, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
// محاسبه و ثبت VAT (اگر فعال باشد)
var vatCreated = await CalculateAndSaveVAT(newOrder.Id, request.TotalAmount, cancellationToken);
if (vatCreated)
{
newOrder.HasVAT = true;
await _context.SaveChangesAsync(cancellationToken);
}
var factorDetailsList = user.UserCarts.Select(s => new FactorDetails()
{
ProductId = s.ProductId,
Count = s.Count,
UnitPrice = SystemConstants.ShopVATEnabled ? AddVAT(s.Product.Price) : s.Product.Price,
OrderId = newOrder.Id
});
await _context.FactorDetails.AddRangeAsync(factorDetailsList, cancellationToken);
// کاهش موجودی محصولات و افزایش تعداد فروش از طریق InventoryService
foreach (var cartItem in user.UserCarts)
{
// استفاده از سرویس انبارداری برای کسر موجودی
await _inventoryService.ConfirmSaleAsync(
cartItem.ProductId,
ProductType.RegularProduct,
cartItem.Count,
newOrder.Id,
cancellationToken);
// افزایش تعداد فروش
cartItem.Product.SaleCount += cartItem.Count;
}
user.UserCarts.Clear();
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation("Order {OrderId} completed. Stock updated for {ProductCount} products.",
newOrder.Id, factorDetailsList.Count());
var finalResult = new SubmitShopBuyOrderResponseDto()
{
Id = newOrder.Id,
};
return finalResult;
}
public long CalculateVAT(long amount) => (long)(amount * _vatRate);
/// <summary>
/// محاسبه قیمت با احتساب مالیات
/// </summary>
public long AddVAT(long amount) => amount + CalculateVAT(amount);
private async Task<bool> CalculateAndSaveVAT(long orderId, long orderAmount, CancellationToken cancellationToken)
{
try
{
// بررسی فعال بودن VAT از SystemConstants
if (!SystemConstants.ShopVATEnabled)
{
_logger.LogInformation("VAT is disabled. Skipping VAT calculation for order {OrderId}", orderId);
return false;
}
// دریافت نرخ VAT از SystemConstants
var vatRate = SystemConstants.ShopVAT;
// محاسبه مالیات
var vatAmount = (long)(orderAmount * vatRate);
var totalAmount = orderAmount + vatAmount;
// ثبت VAT
var orderVAT = new OrderVAT
{
OrderId = orderId,
VATRate = vatRate,
BaseAmount = orderAmount,
VATAmount = vatAmount,
TotalAmount = totalAmount,
IsPaid = true,
PaidAt = DateTime.Now
};
await _context.OrderVATs.AddAsync(orderVAT, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"VAT calculated and saved for order {OrderId}. Rate: {Rate}%, Base: {Base}, VAT: {VAT}, Total: {Total}",
orderId,
vatRate * 100,
orderAmount,
vatAmount,
totalAmount
);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error calculating VAT for order {OrderId}", orderId);
// عدم محاسبه VAT نباید مانع ثبت سفارش شود
return false;
}
}
}
@@ -1,18 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder;
public class SubmitShopBuyOrderCommandValidator : AbstractValidator<SubmitShopBuyOrderCommand>
{
public SubmitShopBuyOrderCommandValidator()
{
RuleFor(model => model.TotalAmount)
.NotNull();
RuleFor(model => model.UserId)
.NotNull();
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<SubmitShopBuyOrderCommand>.CreateWithOptions((SubmitShopBuyOrderCommand)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
@@ -1,7 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder;
public class SubmitShopBuyOrderResponseDto
{
//شناسه
public long Id { get; set; }
}
@@ -1,34 +0,0 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
/// <summary>
/// تغییر وضعیت سفارش
/// </summary>
public record UpdateOrderStatusCommand : IRequest<UpdateOrderStatusResponseDto>
{
/// <summary>
/// شناسه سفارش
/// </summary>
public long OrderId { get; init; }
/// <summary>
/// وضعیت تحویل جدید
/// </summary>
public DeliveryStatus NewStatus { get; init; }
/// <summary>
/// توضیحات (اختیاری)
/// </summary>
public string? Description { get; init; }
}
/// <summary>
/// پاسخ تغییر وضعیت سفارش
/// </summary>
public class UpdateOrderStatusResponseDto
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public DeliveryStatus CurrentStatus { get; set; }
}
@@ -1,56 +0,0 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Enums;
using Microsoft.EntityFrameworkCore;
using ValidationException = FluentValidation.ValidationException;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatusCommand, UpdateOrderStatusResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ILogger<UpdateOrderStatusCommandHandler> _logger;
public UpdateOrderStatusCommandHandler(
IApplicationDbContext context,
ILogger<UpdateOrderStatusCommandHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<UpdateOrderStatusResponseDto> Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
{
var order = await _context.UserOrders
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
if (order == null)
{
throw new NotFoundException(nameof(order), request.OrderId);
}
var oldStatus = order.DeliveryStatus;
// قوانین ساده انتقال وضعیت: از Cancelled نمی‌توان خارج شد
if (oldStatus == DeliveryStatus.Cancelled)
{
throw new ValidationException("امکان تغییر وضعیت سفارش لغو شده وجود ندارد");
}
order.DeliveryStatus = request.NewStatus;
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Order {OrderId} status changed from {OldStatus} to {NewStatus}",
request.OrderId,
oldStatus,
request.NewStatus);
return new UpdateOrderStatusResponseDto
{
Success = true,
Message = "وضعیت سفارش با موفقیت تغییر کرد",
CurrentStatus = order.DeliveryStatus
};
}
}
@@ -1,17 +0,0 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
public class UpdateOrderStatusCommandValidator : AbstractValidator<UpdateOrderStatusCommand>
{
public UpdateOrderStatusCommandValidator()
{
RuleFor(x => x.OrderId)
.GreaterThan(0)
.WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
RuleFor(x => x.NewStatus)
.IsInEnum()
.WithMessage("وضعیت تحویل نامعتبر است");
}
}
@@ -1,30 +0,0 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateUserOrder;
public record UpdateUserOrderCommand : IRequest<Unit>
{
//شناسه
public long Id { get; init; }
//قیمت
public long? Amount { get; init; }
//شناسه پکیج
public long? PackageId { get; init; }
//شناسه تراکنش
public long? TransactionId { get; init; }
//وضعیت پرداخت
public PaymentStatus? PaymentStatus { get; init; }
//تاریخ پرداخت
public DateTime? PaymentDate { get; init; }
//شناسه کاربر
public long? UserId { get; init; }
//شناسه آدرس کاربر
public long? UserAddressId { get; init; }
//
public PaymentMethod? PaymentMethod { get; init; }
// وضعیت ارسال سفارش
public DeliveryStatus? DeliveryStatus { get; init; }
// کد رهگیری مرسوله
public string? TrackingCode { get; init; }
// توضیحات ارسال
public string? DeliveryDescription { get; init; }
}
@@ -1,22 +0,0 @@
using CMSMicroservice.Domain.Events;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateUserOrder;
public class UpdateUserOrderCommandHandler : IRequestHandler<UpdateUserOrderCommand, Unit>
{
private readonly IApplicationDbContext _context;
public UpdateUserOrderCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(UpdateUserOrderCommand request, CancellationToken cancellationToken)
{
var entity = await _context.UserOrders
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(UserOrder), request.Id);
request.Adapt(entity);
_context.UserOrders.Update(entity);
entity.AddDomainEvent(new UpdateUserOrderEvent(entity));
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
@@ -1,29 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateUserOrder;
public class UpdateUserOrderCommandValidator : AbstractValidator<UpdateUserOrderCommand>
{
public UpdateUserOrderCommandValidator()
{
RuleFor(model => model.Id)
.NotNull();
// RuleFor(model => model.Amount)
// .NotNull();
// RuleFor(model => model.PackageId)
// .NotNull();
// RuleFor(model => model.PaymentStatus)
// .IsInEnum()
// .NotNull();
// RuleFor(model => model.UserId)
// .NotNull();
// RuleFor(model => model.UserAddressId)
// .NotNull();
// RuleFor(model => model.PaymentMethod)
// .IsInEnum();
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<UpdateUserOrderCommand>.CreateWithOptions((UpdateUserOrderCommand)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
@@ -1,26 +0,0 @@
using Microsoft.Extensions.Logging;
using CMSMicroservice.Domain.Events;
namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers.CancelOrderEventHandlers;
public class CancelOrderEventHandler : INotificationHandler<CancelOrderEvent>
{
private readonly ILogger<CancelOrderEventHandler> _logger;
public CancelOrderEventHandler(ILogger<CancelOrderEventHandler> logger)
{
_logger = logger;
}
public Task Handle(CancelOrderEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("Order {OrderId} cancelled. Reason: {Reason}",
notification.Order.Id,
notification.CancelReason);
// اینجا می‌تونیم اعلان به کاربر بفرستیم
// یا موجودی محصولات رو بازگردانیم به انبار
return Task.CompletedTask;
}
}
@@ -1,21 +0,0 @@
using CMSMicroservice.Domain.Events;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers;
public class CreateNewUserOrderEventHandler : INotificationHandler<CreateNewUserOrderEvent>
{
private readonly ILogger<CreateNewUserOrderEventHandler> _logger;
public CreateNewUserOrderEventHandler(ILogger<CreateNewUserOrderEventHandler> logger)
{
_logger = logger;
}
public Task Handle(CreateNewUserOrderEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name);
return Task.CompletedTask;
}
}
@@ -1,21 +0,0 @@
using CMSMicroservice.Domain.Events;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers;
public class DeleteUserOrderEventHandler : INotificationHandler<DeleteUserOrderEvent>
{
private readonly ILogger<DeleteUserOrderEventHandler> _logger;
public DeleteUserOrderEventHandler(ILogger<DeleteUserOrderEventHandler> logger)
{
_logger = logger;
}
public Task Handle(DeleteUserOrderEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name);
return Task.CompletedTask;
}
}
@@ -1,22 +0,0 @@
using CMSMicroservice.Domain.Events;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers;
public class SubmitShopBuyOrderEventHandler : INotificationHandler<SubmitShopBuyOrderEvent>
{
private readonly ILogger<
SubmitShopBuyOrderEventHandler> _logger;
public SubmitShopBuyOrderEventHandler(ILogger<SubmitShopBuyOrderEventHandler> logger)
{
_logger = logger;
}
public Task Handle(SubmitShopBuyOrderEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name);
return Task.CompletedTask;
}
}
@@ -1,21 +0,0 @@
using CMSMicroservice.Domain.Events;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.UserOrderCQ.EventHandlers;
public class UpdateUserOrderEventHandler : INotificationHandler<UpdateUserOrderEvent>
{
private readonly ILogger<UpdateUserOrderEventHandler> _logger;
public UpdateUserOrderEventHandler(ILogger<UpdateUserOrderEventHandler> logger)
{
_logger = logger;
}
public Task Handle(UpdateUserOrderEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("Domain Event: {DomainEvent}", notification.GetType().Name);
return Task.CompletedTask;
}
}
@@ -1,46 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
/// <summary>
/// محاسبه امتیاز PV سفارش
/// </summary>
public record CalculateOrderPVQuery : IRequest<CalculateOrderPVResponseDto>
{
/// <summary>
/// شناسه سفارش
/// </summary>
public long OrderId { get; init; }
}
/// <summary>
/// پاسخ محاسبه PV سفارش
/// </summary>
public class CalculateOrderPVResponseDto
{
/// <summary>
/// مجموع امتیاز PV سفارش
/// </summary>
public decimal TotalPV { get; set; }
/// <summary>
/// جزئیات PV هر محصول
/// </summary>
public List<ProductPVDto> ProductPVs { get; set; } = new();
/// <summary>
/// مبلغ قابل پرداخت
/// </summary>
public long PayableAmount { get; set; }
}
/// <summary>
/// جزئیات PV یک محصول در سفارش
/// </summary>
public class ProductPVDto
{
public long ProductId { get; set; }
public string ProductTitle { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPV { get; set; }
public decimal TotalPV { get; set; }
public long UnitPrice { get; set; }
}
@@ -1,80 +0,0 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
public class CalculateOrderPVQueryHandler : IRequestHandler<CalculateOrderPVQuery, CalculateOrderPVResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ILogger<CalculateOrderPVQueryHandler> _logger;
// نسبت PV به قیمت بر اساس مثال‌های بیزینسی:
// محصول ۱: قیمت 100,000 → PV = 50
// محصول ۲: قیمت 200,000 → PV = 100
// یعنی: PV = Price / 2000
private const decimal PvPerRial = 1m / 2000m;
public CalculateOrderPVQueryHandler(
IApplicationDbContext context,
ILogger<CalculateOrderPVQueryHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<CalculateOrderPVResponseDto> Handle(CalculateOrderPVQuery request, CancellationToken cancellationToken)
{
var order = await _context.UserOrders
.Include(o => o.FactorDetails)
.ThenInclude(fd => fd.Product)
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
if (order == null)
{
throw new NotFoundException(nameof(order), request.OrderId);
}
var productPVs = new List<ProductPVDto>();
decimal totalPV = 0;
foreach (var detail in order.FactorDetails)
{
if (detail.Product == null)
{
continue;
}
var unitPrice = detail.Product.Price;
var unitPV = Math.Round(unitPrice * PvPerRial, 2, MidpointRounding.AwayFromZero);
var itemTotalPV = unitPV * detail.Count;
productPVs.Add(new ProductPVDto
{
ProductId = detail.ProductId,
ProductTitle = detail.Product.Title,
Quantity = detail.Count,
UnitPV = unitPV,
TotalPV = itemTotalPV,
UnitPrice = unitPrice
});
totalPV += itemTotalPV;
}
var response = new CalculateOrderPVResponseDto
{
TotalPV = totalPV,
ProductPVs = productPVs,
// فعلاً مبلغ قابل پرداخت همان Amount است؛ در آینده می‌توان تخفیف را هم اعمال کرد
PayableAmount = order.Amount
};
_logger.LogInformation(
"Calculated PV for order {OrderId}: {TotalPV}",
request.OrderId,
totalPV);
return response;
}
}
@@ -1,11 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
public class CalculateOrderPVQueryValidator : AbstractValidator<CalculateOrderPVQuery>
{
public CalculateOrderPVQueryValidator()
{
RuleFor(x => x.OrderId)
.GreaterThan(0)
.WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
}
}
@@ -1,35 +0,0 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter;
public record GetAllUserOrderByFilterQuery : IRequest<GetAllUserOrderByFilterResponseDto>
{
//موقعیت صفحه بندی
public PaginationState? PaginationState { get; init; }
//مرتب سازی بر اساس
public string? SortBy { get; init; }
//فیلتر
public GetAllUserOrderByFilterFilter? Filter { get; init; }
}public class GetAllUserOrderByFilterFilter
{
//شناسه
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 DeliveryStatus? DeliveryStatus { get; set; }
}
@@ -1,77 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter;
public class GetAllUserOrderByFilterQueryHandler : IRequestHandler<GetAllUserOrderByFilterQuery, GetAllUserOrderByFilterResponseDto>
{
private readonly IApplicationDbContext _context;
public GetAllUserOrderByFilterQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetAllUserOrderByFilterResponseDto> Handle(GetAllUserOrderByFilterQuery request, CancellationToken cancellationToken)
{
var query = _context.UserOrders
.Include(i => i.UserAddress)
.Include(i => i.User)
.Include(i => i.FactorDetails)
.ThenInclude(t => t.Product)
.Include(i => i.OrderVAT)
.ApplyOrder(sortBy: request.SortBy)
.AsNoTracking()
.AsQueryable();
if (request.Filter is not null)
{
query = query
.Where(x => request.Filter.Id == null || x.Id == request.Filter.Id)
.Where(x => request.Filter.Amount == null || x.Amount == request.Filter.Amount)
.Where(x => request.Filter.PackageId == null || x.PackageId == request.Filter.PackageId)
.Where(x => request.Filter.TransactionId == null || x.TransactionId == request.Filter.TransactionId)
.Where(x => request.Filter.PaymentStatus == null || x.PaymentStatus == request.Filter.PaymentStatus.Value)
.Where(x => request.Filter.PaymentDate == null || x.PaymentDate >= request.Filter.PaymentDate)
.Where(x => request.Filter.UserId == null || x.UserId == request.Filter.UserId)
.Where(x => request.Filter.UserAddressId == null || x.UserAddressId == request.Filter.UserAddressId)
.Where(x => request.Filter.PaymentMethod == null || x.PaymentMethod == request.Filter.PaymentMethod)
.Where(x => request.Filter.DeliveryStatus == null || x.DeliveryStatus== request.Filter.DeliveryStatus);
}
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
var models = await query
.PaginatedListAsync(paginationState: request.PaginationState)
.Select(x => new GetAllUserOrderByFilterResponseModel
{
Id = x.Id,
Amount = x.Amount,
PackageId = x.PackageId ?? 0,
TransactionId = x.TransactionId,
PaymentStatus = x.PaymentStatus,
PaymentDate = x.PaymentDate,
UserId = x.UserId,
UserAddressId = x.UserAddressId,
PaymentMethod = x.PaymentMethod,
UserAddressText = x.UserAddress.Address,
FactorDetails = x.FactorDetails.Select(fd => new GetAllUserOrderByFilterResponseModelFactorDetail
{
ProductId = fd.ProductId,
ProductTitle = fd.Product.Title,
ProductThumbnailPath = fd.Product.ThumbnailPath,
UnitPrice = fd.UnitPrice,
Count = fd.Count,
UnitDiscountPrice = fd.UnitDiscountPrice
}).ToList(),
DeliveryStatus = x.DeliveryStatus,
TrackingCode = x.TrackingCode,
DeliveryDescription = x.DeliveryDescription,
UserFullName = (x.User.FirstName ?? string.Empty) + " " + (x.User.LastName ?? string.Empty),
UserNationalCode = x.User.NationalCode,
VatAmount = x.OrderVAT != null ? x.OrderVAT.VATAmount : 0,
VatPercentage = x.OrderVAT != null ? (double)(x.OrderVAT.VATRate * 100) : 0
})
.ToListAsync(cancellationToken);
return new GetAllUserOrderByFilterResponseDto
{
MetaData = meta,
Models = models
};
}
}
@@ -1,14 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter;
public class GetAllUserOrderByFilterQueryValidator : AbstractValidator<GetAllUserOrderByFilterQuery>
{
public GetAllUserOrderByFilterQueryValidator()
{
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<GetAllUserOrderByFilterQuery>.CreateWithOptions((GetAllUserOrderByFilterQuery)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
@@ -1,64 +0,0 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter;
public class GetAllUserOrderByFilterResponseDto
{
//متادیتا
public MetaData MetaData { get; set; }
//مدل خروجی
public List<GetAllUserOrderByFilterResponseModel>? Models { get; set; }
}public class GetAllUserOrderByFilterResponseModel
{
//شناسه
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<GetAllUserOrderByFilterResponseModelFactorDetail>? FactorDetails { get; set; }
// وضعیت ارسال سفارش
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; }
// درصد مالیات بر ارزش افزوده (مثلاً 9 برای 9٪)
public double VatPercentage { get; set; }
}
public class GetAllUserOrderByFilterResponseModelFactorDetail
{
//شناسه
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; }
}
@@ -1,66 +0,0 @@
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
/// <summary>
/// دریافت سفارشات بر اساس بازه زمانی
/// </summary>
public record GetOrdersByDateRangeQuery : IRequest<GetOrdersByDateRangeResponseDto>
{
/// <summary>
/// تاریخ شروع (UTC)
/// </summary>
public DateTime StartDate { get; init; }
/// <summary>
/// تاریخ پایان (UTC)
/// </summary>
public DateTime EndDate { get; init; }
/// <summary>
/// فیلتر وضعیت تحویل (اختیاری)
/// </summary>
public DeliveryStatus? Status { get; init; }
/// <summary>
/// شناسه کاربر (اختیاری - برای فیلتر بر اساس کاربر)
/// </summary>
public long? UserId { get; init; }
/// <summary>
/// شماره صفحه
/// </summary>
public int PageIndex { get; init; } = 1;
/// <summary>
/// تعداد در صفحه
/// </summary>
public int PageSize { get; init; } = 20;
}
/// <summary>
/// پاسخ لیست سفارشات
/// </summary>
public class GetOrdersByDateRangeResponseDto
{
public MetaData MetaData { get; set; } = new();
public List<OrderSummaryDto> Orders { get; set; } = new();
}
/// <summary>
/// خلاصه اطلاعات سفارش
/// </summary>
public class OrderSummaryDto
{
public long Id { get; set; }
public long UserId { get; set; }
public string UserFullName { get; set; } = string.Empty;
public long Amount { get; set; }
public long DiscountedPrice { get; set; }
public DeliveryStatus Status { get; set; }
public DateTime Created { get; set; }
public DateTime? ShippedAt { get; set; }
public DateTime? DeliveredAt { get; set; }
public int ItemsCount { get; set; }
}
@@ -1,96 +0,0 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
public class GetOrdersByDateRangeQueryHandler : IRequestHandler<GetOrdersByDateRangeQuery, GetOrdersByDateRangeResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ILogger<GetOrdersByDateRangeQueryHandler> _logger;
public GetOrdersByDateRangeQueryHandler(
IApplicationDbContext context,
ILogger<GetOrdersByDateRangeQueryHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<GetOrdersByDateRangeResponseDto> Handle(GetOrdersByDateRangeQuery request, CancellationToken cancellationToken)
{
var query = _context.UserOrders
.AsNoTracking()
.Include(o => o.User)
.Include(o => o.FactorDetails)
.AsQueryable();
query = query.Where(o => o.Created >= request.StartDate && o.Created <= request.EndDate);
if (request.Status.HasValue)
{
query = query.Where(o => o.DeliveryStatus == request.Status.Value);
}
if (request.UserId.HasValue)
{
query = query.Where(o => o.UserId == request.UserId.Value);
}
var totalCount = await query.CountAsync(cancellationToken);
var response = new GetOrdersByDateRangeResponseDto
{
MetaData = new MetaData
{
CurrentPage = request.PageIndex,
TotalPage = totalCount == 0 ? 0 : (int)Math.Ceiling(totalCount / (double)request.PageSize),
PageSize = request.PageSize,
TotalCount = totalCount,
HasNext = totalCount > 0 && request.PageIndex * request.PageSize < totalCount,
HasPrevious = request.PageIndex > 1
}
};
if (totalCount == 0)
{
return response;
}
var orders = await query
.OrderByDescending(o => o.Created)
.Skip((request.PageIndex - 1) * request.PageSize)
.Take(request.PageSize)
.ToListAsync(cancellationToken);
response.Orders = orders.Select(o =>
{
var firstName = o.User?.FirstName ?? string.Empty;
var lastName = o.User?.LastName ?? string.Empty;
var fullName = $"{firstName} {lastName}".Trim();
return new OrderSummaryDto
{
Id = o.Id,
UserId = o.UserId,
UserFullName = fullName,
Amount = o.Amount,
// در حال حاضر فیلد DiscountedPrice در UserOrder وجود ندارد، پس همان Amount برگردانده می‌شود
DiscountedPrice = o.Amount,
Status = o.DeliveryStatus,
Created = o.Created,
ShippedAt = null,
DeliveredAt = null,
ItemsCount = o.FactorDetails?.Count ?? 0
};
}).ToList();
_logger.LogInformation(
"Retrieved {Count} orders for date range {Start} to {End}",
response.Orders.Count,
request.StartDate,
request.EndDate);
return response;
}
}
@@ -1,28 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
public class GetOrdersByDateRangeQueryValidator : AbstractValidator<GetOrdersByDateRangeQuery>
{
public GetOrdersByDateRangeQueryValidator()
{
RuleFor(x => x.StartDate)
.LessThanOrEqualTo(x => x.EndDate)
.WithMessage("تاریخ شروع باید کوچکتر یا مساوی تاریخ پایان باشد");
RuleFor(x => x.EndDate)
.LessThanOrEqualTo(DateTime.Now.AddDays(1))
.WithMessage("تاریخ پایان نمی‌تواند در آینده باشد");
RuleFor(x => x.PageIndex)
.GreaterThan(0)
.WithMessage("شماره صفحه باید بزرگتر از 0 باشد");
RuleFor(x => x.PageSize)
.InclusiveBetween(1, 100)
.WithMessage("تعداد در صفحه باید بین 1 تا 100 باشد");
// بازه زمانی نباید بیش از 1 سال باشد
RuleFor(x => x)
.Must(x => (x.EndDate - x.StartDate).TotalDays <= 365)
.WithMessage("بازه زمانی نمی‌تواند بیش از 1 سال باشد");
}
}
@@ -1,7 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder;
public record GetUserOrderQuery : IRequest<GetUserOrderResponseDto>
{
//شناسه
public long Id { get; init; }
}
@@ -1,61 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder;
public class GetUserOrderQueryHandler : IRequestHandler<GetUserOrderQuery, GetUserOrderResponseDto>
{
private readonly IApplicationDbContext _context;
public GetUserOrderQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetUserOrderResponseDto> Handle(GetUserOrderQuery request,
CancellationToken cancellationToken)
{
var response = await _context.UserOrders
.Include(i => i.UserAddress)
.Include(i => i.User)
.Include(i => i.FactorDetails)
.ThenInclude(t => t.Product)
.Include(i => i.OrderVAT)
.AsNoTracking()
.Where(x => x.Id == request.Id)
.Select(x => new GetUserOrderResponseDto
{
Id = x.Id,
Amount = x.Amount,
PackageId = x.PackageId ?? 0,
TransactionId = x.TransactionId,
PaymentStatus = x.PaymentStatus,
PaymentDate = x.PaymentDate,
UserId = x.UserId,
UserAddressId = x.UserAddressId,
PaymentMethod = x.PaymentMethod,
UserAddressText = x.UserAddress.Address,
FactorDetails = x.FactorDetails.Select(fd => new GetUserOrderResponseFactorDetail
{
ProductId = fd.ProductId,
ProductTitle = fd.Product.Title,
ProductThumbnailPath = fd.Product.ThumbnailPath,
UnitPrice = fd.UnitPrice,
Count = fd.Count,
UnitDiscountPrice = fd.UnitDiscountPrice
}).ToList(),
DeliveryStatus = x.DeliveryStatus,
TrackingCode = x.TrackingCode,
DeliveryDescription = x.DeliveryDescription,
UserFullName = (x.User.FirstName ?? string.Empty) + " " + (x.User.LastName ?? string.Empty),
UserNationalCode = x.User.NationalCode,
VatInfo = x.OrderVAT != null ? new OrderVATInfoDto
{
VatRate = x.OrderVAT.VATRate,
BaseAmount = x.OrderVAT.BaseAmount,
VatAmount = x.OrderVAT.VATAmount,
TotalAmount = x.OrderVAT.TotalAmount,
IsPaid = x.OrderVAT.IsPaid
} : null
})
.FirstOrDefaultAsync(cancellationToken);
return response ?? throw new NotFoundException(nameof(UserOrder), request.Id);
}
}
@@ -1,16 +0,0 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder;
public class GetUserOrderQueryValidator : AbstractValidator<GetUserOrderQuery>
{
public GetUserOrderQueryValidator()
{
RuleFor(model => model.Id)
.NotNull();
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<GetUserOrderQuery>.CreateWithOptions((GetUserOrderQuery)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
@@ -1,83 +0,0 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder;
public class GetUserOrderResponseDto
{
//شناسه
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<GetUserOrderResponseFactorDetail>? FactorDetails { get; set; }
// وضعیت ارسال سفارش
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 OrderVATInfoDto? VatInfo { get; set; }
}
/// <summary>
/// اطلاعات مالیات بر ارزش افزوده
/// </summary>
public class OrderVATInfoDto
{
/// <summary>
/// نرخ مالیات (مثلاً 0.09 = 9%)
/// </summary>
public decimal VatRate { get; set; }
/// <summary>
/// مبلغ پایه (قبل از مالیات)
/// </summary>
public long BaseAmount { get; set; }
/// <summary>
/// مبلغ مالیات
/// </summary>
public long VatAmount { get; set; }
/// <summary>
/// مبلغ کل (پایه + مالیات)
/// </summary>
public long TotalAmount { get; set; }
/// <summary>
/// آیا پرداخت شده
/// </summary>
public bool IsPaid { get; set; }
}
public class GetUserOrderResponseFactorDetail
{
//شناسه
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; }
}