Add validators and services for Product Galleries and Product Tags

- Implemented Create, Delete, Get, and Update validators for Product Galleries.
- Added Create, Delete, Get, and Update validators for Product Tags.
- Created service classes for handling Discount Categories, Discount Orders, Discount Products, Discount Shopping Cart, Product Categories, Product Galleries, and Product Tags.
- Each service class integrates with CQRS for command and query handling.
- Established mapping profiles for Product Galleries.
This commit is contained in:
masoodafar-web
2025-12-04 02:40:49 +03:30
parent 40d54d08fc
commit f0f48118e7
436 changed files with 33159 additions and 2005 deletions
@@ -0,0 +1,17 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCart;
public class AddToCartCommand : IRequest<AddToCartResponseDto>
{
public long UserId { get; set; }
public long ProductId { get; set; }
public int Count { get; set; }
}
public class AddToCartResponseDto
{
public bool Success { get; set; }
public string Message { get; set; }
public long CartItemId { get; set; }
}
@@ -0,0 +1,89 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities.DiscountShop;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCart;
public class AddToCartCommandHandler : IRequestHandler<AddToCartCommand, AddToCartResponseDto>
{
private readonly IApplicationDbContext _context;
public AddToCartCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<AddToCartResponseDto> Handle(AddToCartCommand request, CancellationToken cancellationToken)
{
// Check if product exists and is active
var product = await _context.DiscountProducts
.FirstOrDefaultAsync(p => p.Id == request.ProductId && p.IsActive, cancellationToken);
if (product == null)
{
return new AddToCartResponseDto
{
Success = false,
Message = "محصول یافت نشد یا غیرفعال است"
};
}
// Check stock availability
if (product.RemainingCount < request.Count)
{
return new AddToCartResponseDto
{
Success = false,
Message = $"موجودی کافی نیست. موجودی فعلی: {product.RemainingCount}"
};
}
// Check if item already exists in cart
var existingCartItem = await _context.DiscountShoppingCarts
.FirstOrDefaultAsync(c => c.UserId == request.UserId && c.ProductId == request.ProductId, cancellationToken);
if (existingCartItem != null)
{
// Update quantity
var newCount = existingCartItem.Count + request.Count;
if (product.RemainingCount < newCount)
{
return new AddToCartResponseDto
{
Success = false,
Message = $"موجودی کافی نیست. موجودی فعلی: {product.RemainingCount}"
};
}
existingCartItem.Count = newCount;
await _context.SaveChangesAsync(cancellationToken);
return new AddToCartResponseDto
{
Success = true,
Message = "تعداد محصول در سبد خرید به‌روزرسانی شد",
CartItemId = existingCartItem.Id
};
}
// Add new item to cart
var cartItem = new DiscountShoppingCart
{
UserId = request.UserId,
ProductId = request.ProductId,
Count = request.Count
};
_context.DiscountShoppingCarts.Add(cartItem);
await _context.SaveChangesAsync(cancellationToken);
return new AddToCartResponseDto
{
Success = true,
Message = "محصول به سبد خرید اضافه شد",
CartItemId = cartItem.Id
};
}
}
@@ -0,0 +1,18 @@
using FluentValidation;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCart;
public class AddToCartCommandValidator : AbstractValidator<AddToCartCommand>
{
public AddToCartCommandValidator()
{
RuleFor(v => v.UserId)
.GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است");
RuleFor(v => v.ProductId)
.GreaterThan(0).WithMessage("شناسه محصول نامعتبر است");
RuleFor(v => v.Count)
.GreaterThan(0).WithMessage("تعداد باید بیشتر از صفر باشد");
}
}
@@ -0,0 +1,8 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.ClearCart;
public class ClearCartCommand : IRequest<bool>
{
public long UserId { get; set; }
}
@@ -0,0 +1,33 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.ClearCart;
public class ClearCartCommandHandler : IRequestHandler<ClearCartCommand, bool>
{
private readonly IApplicationDbContext _context;
public ClearCartCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<bool> Handle(ClearCartCommand request, CancellationToken cancellationToken)
{
var cartItems = await _context.DiscountShoppingCarts
.Where(c => c.UserId == request.UserId)
.ToListAsync(cancellationToken);
if (!cartItems.Any())
{
return true; // Cart already empty
}
_context.DiscountShoppingCarts.RemoveRange(cartItems);
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
@@ -0,0 +1,18 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment;
public class CompleteOrderPaymentCommand : IRequest<CompleteOrderPaymentResponseDto>
{
public long OrderId { get; set; }
public long TransactionId { get; set; }
public bool PaymentSuccess { get; set; }
public string? RefId { get; set; }
}
public class CompleteOrderPaymentResponseDto
{
public bool Success { get; set; }
public string Message { get; set; }
public long? OrderId { get; set; }
}
@@ -0,0 +1,99 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment;
public class CompleteOrderPaymentCommandHandler : IRequestHandler<CompleteOrderPaymentCommand, CompleteOrderPaymentResponseDto>
{
private readonly IApplicationDbContext _context;
public CompleteOrderPaymentCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<CompleteOrderPaymentResponseDto> Handle(CompleteOrderPaymentCommand request, CancellationToken cancellationToken)
{
var order = await _context.DiscountOrders
.Include(o => o.OrderDetails)
.ThenInclude(od => od.Product)
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
if (order == null)
{
return new CompleteOrderPaymentResponseDto
{
Success = false,
Message = "سفارش یافت نشد"
};
}
var transaction = await _context.Transactions
.FirstOrDefaultAsync(t => t.Id == request.TransactionId, cancellationToken);
if (transaction == null)
{
return new CompleteOrderPaymentResponseDto
{
Success = false,
Message = "تراکنش یافت نشد"
};
}
if (request.PaymentSuccess)
{
// Update transaction
transaction.PaymentStatus = PaymentStatus.Success;
transaction.PaymentDate = DateTime.UtcNow;
transaction.RefId = request.RefId;
// Update order
order.PaymentStatus = PaymentStatus.Success;
order.PaymentDate = DateTime.UtcNow;
order.DeliveryStatus = DeliveryStatus.InTransit;
// Deduct discount balance from user wallet
var userWallet = await _context.UserWallets
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
if (userWallet != null)
{
userWallet.DiscountBalance -= order.DiscountBalanceUsed;
}
// Update product stock and sale count
foreach (var orderDetail in order.OrderDetails)
{
var product = orderDetail.Product;
product.RemainingCount -= orderDetail.Count;
product.SaleCount += orderDetail.Count;
}
await _context.SaveChangesAsync(cancellationToken);
return new CompleteOrderPaymentResponseDto
{
Success = true,
Message = "پرداخت با موفقیت انجام شد",
OrderId = order.Id
};
}
else
{
// Payment failed
transaction.PaymentStatus = PaymentStatus.Reject;
order.PaymentStatus = PaymentStatus.Reject;
await _context.SaveChangesAsync(cancellationToken);
return new CompleteOrderPaymentResponseDto
{
Success = false,
Message = "پرداخت ناموفق بود",
OrderId = order.Id
};
}
}
}
@@ -0,0 +1,14 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountCategory;
public class CreateDiscountCategoryCommand : IRequest<long>
{
public string Name { get; set; }
public string Title { get; set; }
public string? Description { get; set; }
public string? ImagePath { get; set; }
public long? ParentCategoryId { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; } = true;
}
@@ -0,0 +1,58 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Entities.DiscountShop;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountCategory;
public class CreateDiscountCategoryCommandHandler : IRequestHandler<CreateDiscountCategoryCommand, long>
{
private readonly IApplicationDbContext _context;
public CreateDiscountCategoryCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<long> Handle(CreateDiscountCategoryCommand request, CancellationToken cancellationToken)
{
// بررسی وجود دسته‌بندی با همین نام
var existingCategory = await _context.DiscountCategories
.FirstOrDefaultAsync(c => c.Name == request.Name, cancellationToken);
if (existingCategory != null)
{
throw new InvalidOperationException("دسته‌بندی با این نام قبلاً ثبت شده است");
}
// بررسی وجود دسته‌بندی والد
if (request.ParentCategoryId.HasValue)
{
var parentExists = await _context.DiscountCategories
.AnyAsync(c => c.Id == request.ParentCategoryId.Value, cancellationToken);
if (!parentExists)
{
throw new InvalidOperationException("دسته‌بندی والد یافت نشد");
}
}
var category = new DiscountCategory
{
Name = request.Name,
Title = request.Title,
Description = request.Description,
ImagePath = request.ImagePath,
ParentCategoryId = request.ParentCategoryId,
SortOrder = request.SortOrder,
IsActive = request.IsActive,
Created = DateTime.UtcNow
};
_context.DiscountCategories.Add(category);
await _context.SaveChangesAsync(cancellationToken);
return category.Id;
}
}
@@ -0,0 +1,32 @@
using FluentValidation;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountCategory;
public class CreateDiscountCategoryCommandValidator : AbstractValidator<CreateDiscountCategoryCommand>
{
public CreateDiscountCategoryCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("نام دسته‌بندی الزامی است")
.MaximumLength(100).WithMessage("نام دسته‌بندی نباید بیشتر از 100 کاراکتر باشد");
RuleFor(x => x.Title)
.NotEmpty().WithMessage("عنوان دسته‌بندی الزامی است")
.MaximumLength(200).WithMessage("عنوان دسته‌بندی نباید بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.Description)
.MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.Description));
RuleFor(x => x.ImagePath)
.MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.ImagePath));
RuleFor(x => x.ParentCategoryId)
.GreaterThan(0).WithMessage("شناسه دسته‌بندی والد باید مثبت باشد")
.When(x => x.ParentCategoryId.HasValue);
RuleFor(x => x.SortOrder)
.GreaterThanOrEqualTo(0).WithMessage("ترتیب نمایش نمی‌تواند منفی باشد");
}
}
@@ -0,0 +1,16 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProduct;
public class CreateDiscountProductCommand : IRequest<long>
{
public string Title { get; set; }
public string ShortInfomation { get; set; }
public string FullInformation { get; set; }
public long Price { get; set; }
public int MaxDiscountPercent { get; set; }
public string ImagePath { get; set; }
public string ThumbnailPath { get; set; }
public int RemainingCount { get; set; }
public List<long> CategoryIds { get; set; } = new();
}
@@ -0,0 +1,53 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities.DiscountShop;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProduct;
public class CreateDiscountProductCommandHandler : IRequestHandler<CreateDiscountProductCommand, long>
{
private readonly IApplicationDbContext _context;
public CreateDiscountProductCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<long> Handle(CreateDiscountProductCommand request, CancellationToken cancellationToken)
{
var product = new DiscountProduct
{
Title = request.Title,
ShortInfomation = request.ShortInfomation,
FullInformation = request.FullInformation,
Price = request.Price,
MaxDiscountPercent = request.MaxDiscountPercent,
ImagePath = request.ImagePath,
ThumbnailPath = request.ThumbnailPath,
RemainingCount = request.RemainingCount,
Rate = 0,
SaleCount = 0,
ViewCount = 0,
IsActive = true
};
_context.DiscountProducts.Add(product);
await _context.SaveChangesAsync(cancellationToken);
// Add product categories
if (request.CategoryIds.Any())
{
var productCategories = request.CategoryIds.Select(categoryId => new DiscountProductCategory
{
ProductId = product.Id,
CategoryId = categoryId
}).ToList();
_context.DiscountProductCategories.AddRange(productCategories);
await _context.SaveChangesAsync(cancellationToken);
}
return product.Id;
}
}
@@ -0,0 +1,36 @@
using FluentValidation;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProduct;
public class CreateDiscountProductCommandValidator : AbstractValidator<CreateDiscountProductCommand>
{
public CreateDiscountProductCommandValidator()
{
RuleFor(v => v.Title)
.NotEmpty().WithMessage("عنوان محصول الزامی است")
.MaximumLength(200).WithMessage("عنوان محصول نمی‌تواند بیشتر از 200 کاراکتر باشد");
RuleFor(v => v.ShortInfomation)
.NotEmpty().WithMessage("توضیحات کوتاه الزامی است")
.MaximumLength(500).WithMessage("توضیحات کوتاه نمی‌تواند بیشتر از 500 کاراکتر باشد");
RuleFor(v => v.FullInformation)
.NotEmpty().WithMessage("توضیحات کامل الزامی است")
.MaximumLength(2000).WithMessage("توضیحات کامل نمی‌تواند بیشتر از 2000 کاراکتر باشد");
RuleFor(v => v.Price)
.GreaterThan(0).WithMessage("قیمت باید بیشتر از صفر باشد");
RuleFor(v => v.MaxDiscountPercent)
.InclusiveBetween(0, 100).WithMessage("درصد تخفیف باید بین 0 تا 100 باشد");
RuleFor(v => v.RemainingCount)
.GreaterThanOrEqualTo(0).WithMessage("موجودی نمی‌تواند منفی باشد");
RuleFor(v => v.ImagePath)
.NotEmpty().WithMessage("تصویر محصول الزامی است");
RuleFor(v => v.ThumbnailPath)
.NotEmpty().WithMessage("تصویر بندانگشتی الزامی است");
}
}
@@ -0,0 +1,8 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountCategory;
public class DeleteDiscountCategoryCommand : IRequest<bool>
{
public long CategoryId { get; set; }
}
@@ -0,0 +1,46 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountCategory;
public class DeleteDiscountCategoryCommandHandler : IRequestHandler<DeleteDiscountCategoryCommand, bool>
{
private readonly IApplicationDbContext _context;
public DeleteDiscountCategoryCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<bool> Handle(DeleteDiscountCategoryCommand request, CancellationToken cancellationToken)
{
var category = await _context.DiscountCategories
.Include(c => c.ChildCategories)
.Include(c => c.ProductCategories)
.FirstOrDefaultAsync(c => c.Id == request.CategoryId, cancellationToken);
if (category == null)
{
throw new Exception($"Discount category with ID {request.CategoryId} not found");
}
// Check if category has child categories
if (category.ChildCategories.Any())
{
throw new Exception($"Cannot delete category. It has {category.ChildCategories.Count} child categories. Please delete child categories first.");
}
// Check if category has products
if (category.ProductCategories.Any())
{
throw new Exception($"Cannot delete category. It has {category.ProductCategories.Count} products. Please move or delete products first.");
}
_context.DiscountCategories.Remove(category);
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
@@ -0,0 +1,8 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProduct;
public class DeleteDiscountProductCommand : IRequest<bool>
{
public long ProductId { get; set; }
}
@@ -0,0 +1,39 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProduct;
public class DeleteDiscountProductCommandHandler : IRequestHandler<DeleteDiscountProductCommand, bool>
{
private readonly IApplicationDbContext _context;
public DeleteDiscountProductCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<bool> Handle(DeleteDiscountProductCommand request, CancellationToken cancellationToken)
{
var product = await _context.DiscountProducts
.FirstOrDefaultAsync(p => p.Id == request.ProductId, cancellationToken);
if (product == null)
{
return false;
}
// حذف رابطه‌های دسته‌بندی
var productCategories = await _context.DiscountProductCategories
.Where(pc => pc.ProductId == request.ProductId)
.ToListAsync(cancellationToken);
_context.DiscountProductCategories.RemoveRange(productCategories);
// حذف محصول
_context.DiscountProducts.Remove(product);
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
@@ -0,0 +1,21 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder;
public class PlaceOrderCommand : IRequest<PlaceOrderResponseDto>
{
public long UserId { get; set; }
public long UserAddressId { get; set; }
public long DiscountBalanceToUse { get; set; }
}
public class PlaceOrderResponseDto
{
public bool Success { get; set; }
public string Message { get; set; }
public long? OrderId { get; set; }
public long? TransactionId { get; set; }
public long TotalAmount { get; set; }
public long DiscountBalanceUsed { get; set; }
public long GatewayAmountRequired { get; set; }
}
@@ -0,0 +1,169 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities.DiscountShop;
using CMSMicroservice.Domain.Entities.Payment;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder;
public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand, PlaceOrderResponseDto>
{
private readonly IApplicationDbContext _context;
public PlaceOrderCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<PlaceOrderResponseDto> Handle(PlaceOrderCommand request, CancellationToken cancellationToken)
{
// Get user wallet
var userWallet = await _context.UserWallets
.FirstOrDefaultAsync(w => w.UserId == request.UserId, cancellationToken);
if (userWallet == null)
{
return new PlaceOrderResponseDto
{
Success = false,
Message = "کیف پول کاربر یافت نشد"
};
}
// Get cart items with products
var cartItems = await _context.DiscountShoppingCarts
.Where(c => c.UserId == request.UserId)
.Include(c => c.Product)
.ToListAsync(cancellationToken);
if (!cartItems.Any())
{
return new PlaceOrderResponseDto
{
Success = false,
Message = "سبد خرید خالی است"
};
}
// Validate stock and calculate totals
long totalAmount = 0;
long totalDiscountAmount = 0;
var orderDetails = new List<DiscountOrderDetail>();
foreach (var cartItem in cartItems)
{
var product = cartItem.Product;
// Check stock
if (product.RemainingCount < cartItem.Count)
{
return new PlaceOrderResponseDto
{
Success = false,
Message = $"موجودی محصول '{product.Title}' کافی نیست"
};
}
// Check if product is active
if (!product.IsActive)
{
return new PlaceOrderResponseDto
{
Success = false,
Message = $"محصول '{product.Title}' غیرفعال است"
};
}
// Calculate discount for this product
var itemTotal = product.Price * cartItem.Count;
var maxDiscountForItem = (itemTotal * product.MaxDiscountPercent) / 100;
totalAmount += itemTotal;
totalDiscountAmount += maxDiscountForItem;
orderDetails.Add(new DiscountOrderDetail
{
ProductId = product.Id,
Count = cartItem.Count,
UnitPrice = product.Price,
DiscountPercentUsed = product.MaxDiscountPercent,
DiscountAmount = maxDiscountForItem,
FinalPrice = itemTotal - maxDiscountForItem
});
}
// Validate discount balance usage
var maxDiscountBalanceUsable = totalDiscountAmount;
var actualDiscountBalanceUsed = Math.Min(request.DiscountBalanceToUse, maxDiscountBalanceUsable);
actualDiscountBalanceUsed = Math.Min(actualDiscountBalanceUsed, userWallet.DiscountBalance);
if (actualDiscountBalanceUsed < request.DiscountBalanceToUse)
{
return new PlaceOrderResponseDto
{
Success = false,
Message = $"موجودی تخفیف کافی نیست. حداکثر قابل استفاده: {maxDiscountBalanceUsable:N0} تومان، موجودی شما: {userWallet.DiscountBalance:N0} تومان"
};
}
var gatewayAmountRequired = totalAmount - actualDiscountBalanceUsed;
// Calculate VAT (9%)
var vatAmount = (gatewayAmountRequired * 9) / 100;
var finalGatewayAmount = gatewayAmountRequired + vatAmount;
// Create transaction for gateway payment
var transaction = new Transaction
{
Amount = finalGatewayAmount,
Description = $"خرید از فروشگاه تخفیف - مبلغ کل: {totalAmount:N0}، اعتبار تخفیف: {actualDiscountBalanceUsed:N0}",
PaymentStatus = PaymentStatus.Pending,
Type = TransactionType.DiscountShopPurchase
};
_context.Transactions.Add(transaction);
await _context.SaveChangesAsync(cancellationToken);
// Create order
var order = new DiscountOrder
{
UserId = request.UserId,
TotalAmount = totalAmount,
DiscountBalanceUsed = actualDiscountBalanceUsed,
GatewayAmountPaid = finalGatewayAmount,
VatAmount = vatAmount,
PaymentStatus = PaymentStatus.Pending,
TransactionId = transaction.Id,
UserAddressId = request.UserAddressId,
DeliveryStatus = DeliveryStatus.Pending
};
_context.DiscountOrders.Add(order);
await _context.SaveChangesAsync(cancellationToken);
// Add order details
foreach (var detail in orderDetails)
{
detail.DiscountOrderId = order.Id;
}
_context.DiscountOrderDetails.AddRange(orderDetails);
// Clear cart
_context.DiscountShoppingCarts.RemoveRange(cartItems);
await _context.SaveChangesAsync(cancellationToken);
return new PlaceOrderResponseDto
{
Success = true,
Message = "سفارش ایجاد شد. لطفا پرداخت را تکمیل کنید",
OrderId = order.Id,
TransactionId = transaction.Id,
TotalAmount = totalAmount,
DiscountBalanceUsed = actualDiscountBalanceUsed,
GatewayAmountRequired = finalGatewayAmount
};
}
}
@@ -0,0 +1,18 @@
using FluentValidation;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder;
public class PlaceOrderCommandValidator : AbstractValidator<PlaceOrderCommand>
{
public PlaceOrderCommandValidator()
{
RuleFor(x => x.UserId)
.GreaterThan(0).WithMessage("شناسه کاربر باید مثبت باشد");
RuleFor(x => x.UserAddressId)
.GreaterThan(0).WithMessage("آدرس تحویل باید انتخاب شود");
RuleFor(x => x.DiscountBalanceToUse)
.GreaterThanOrEqualTo(0).WithMessage("مبلغ استفاده از موجودی تخفیف نمی‌تواند منفی باشد");
}
}
@@ -0,0 +1,15 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCart;
public class RemoveFromCartCommand : IRequest<RemoveFromCartResponseDto>
{
public long UserId { get; set; }
public long ProductId { get; set; }
}
public class RemoveFromCartResponseDto
{
public bool Success { get; set; }
public string Message { get; set; }
}
@@ -0,0 +1,39 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCart;
public class RemoveFromCartCommandHandler : IRequestHandler<RemoveFromCartCommand, RemoveFromCartResponseDto>
{
private readonly IApplicationDbContext _context;
public RemoveFromCartCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<RemoveFromCartResponseDto> Handle(RemoveFromCartCommand request, CancellationToken cancellationToken)
{
var cartItem = await _context.DiscountShoppingCarts
.FirstOrDefaultAsync(c => c.UserId == request.UserId && c.ProductId == request.ProductId, cancellationToken);
if (cartItem == null)
{
return new RemoveFromCartResponseDto
{
Success = false,
Message = "محصول در سبد خرید یافت نشد"
};
}
_context.DiscountShoppingCarts.Remove(cartItem);
await _context.SaveChangesAsync(cancellationToken);
return new RemoveFromCartResponseDto
{
Success = true,
Message = "محصول از سبد خرید حذف شد"
};
}
}
@@ -0,0 +1,15 @@
using FluentValidation;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCart;
public class RemoveFromCartCommandValidator : AbstractValidator<RemoveFromCartCommand>
{
public RemoveFromCartCommandValidator()
{
RuleFor(x => x.UserId)
.GreaterThan(0).WithMessage("شناسه کاربر باید مثبت باشد");
RuleFor(x => x.ProductId)
.GreaterThan(0).WithMessage("شناسه محصول باید مثبت باشد");
}
}
@@ -0,0 +1,16 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCartItemCount;
public class UpdateCartItemCountCommand : IRequest<UpdateCartItemCountResponseDto>
{
public long UserId { get; set; }
public long ProductId { get; set; }
public int NewCount { get; set; }
}
public class UpdateCartItemCountResponseDto
{
public bool Success { get; set; }
public string Message { get; set; }
}
@@ -0,0 +1,65 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCartItemCount;
public class UpdateCartItemCountCommandHandler : IRequestHandler<UpdateCartItemCountCommand, UpdateCartItemCountResponseDto>
{
private readonly IApplicationDbContext _context;
public UpdateCartItemCountCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<UpdateCartItemCountResponseDto> Handle(UpdateCartItemCountCommand request, CancellationToken cancellationToken)
{
// پیدا کردن آیتم سبد خرید
var cartItem = await _context.DiscountShoppingCarts
.Include(c => c.Product)
.FirstOrDefaultAsync(c => c.UserId == request.UserId && c.ProductId == request.ProductId, cancellationToken);
if (cartItem == null)
{
return new UpdateCartItemCountResponseDto
{
Success = false,
Message = "آیتم در سبد خرید یافت نشد"
};
}
// بررسی موجودی محصول
if (request.NewCount > cartItem.Product.RemainingCount)
{
return new UpdateCartItemCountResponseDto
{
Success = false,
Message = $"موجودی محصول کافی نیست. موجودی فعلی: {cartItem.Product.RemainingCount}"
};
}
// اگر تعداد جدید صفر یا منفی باشد، آیتم را حذف کن
if (request.NewCount <= 0)
{
_context.DiscountShoppingCarts.Remove(cartItem);
await _context.SaveChangesAsync(cancellationToken);
return new UpdateCartItemCountResponseDto
{
Success = true,
Message = "محصول از سبد خرید حذف شد"
};
}
// به‌روزرسانی تعداد
cartItem.Count = request.NewCount;
await _context.SaveChangesAsync(cancellationToken);
return new UpdateCartItemCountResponseDto
{
Success = true,
Message = "تعداد محصول به‌روزرسانی شد"
};
}
}
@@ -0,0 +1,18 @@
using FluentValidation;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCartItemCount;
public class UpdateCartItemCountCommandValidator : AbstractValidator<UpdateCartItemCountCommand>
{
public UpdateCartItemCountCommandValidator()
{
RuleFor(x => x.UserId)
.GreaterThan(0).WithMessage("شناسه کاربر باید مثبت باشد");
RuleFor(x => x.ProductId)
.GreaterThan(0).WithMessage("شناسه محصول باید مثبت باشد");
RuleFor(x => x.NewCount)
.GreaterThanOrEqualTo(0).WithMessage("تعداد نمی‌تواند منفی باشد");
}
}
@@ -0,0 +1,15 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountCategory;
public class UpdateDiscountCategoryCommand : IRequest<bool>
{
public long CategoryId { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string? Description { get; set; }
public string? ImagePath { get; set; }
public long? ParentCategoryId { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; }
}
@@ -0,0 +1,90 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountCategory;
public class UpdateDiscountCategoryCommandHandler : IRequestHandler<UpdateDiscountCategoryCommand, bool>
{
private readonly IApplicationDbContext _context;
public UpdateDiscountCategoryCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<bool> Handle(UpdateDiscountCategoryCommand request, CancellationToken cancellationToken)
{
var category = await _context.DiscountCategories
.FirstOrDefaultAsync(c => c.Id == request.CategoryId, cancellationToken);
if (category == null)
{
return false;
}
// بررسی وجود دسته‌بندی دیگری با همین نام (به جز خودش)
var duplicateName = await _context.DiscountCategories
.AnyAsync(c => c.Name == request.Name && c.Id != request.CategoryId, cancellationToken);
if (duplicateName)
{
throw new InvalidOperationException("دسته‌بندی دیگری با این نام وجود دارد");
}
// بررسی عدم ایجاد حلقه در سلسله مراتب
if (request.ParentCategoryId.HasValue)
{
if (request.ParentCategoryId.Value == request.CategoryId)
{
throw new InvalidOperationException("دسته‌بندی نمی‌تواند والد خودش باشد");
}
// بررسی وجود دسته‌بندی والد
var parentExists = await _context.DiscountCategories
.AnyAsync(c => c.Id == request.ParentCategoryId.Value, cancellationToken);
if (!parentExists)
{
throw new InvalidOperationException("دسته‌بندی والد یافت نشد");
}
// بررسی اینکه والد جدید زیرمجموعه این دسته‌بندی نباشد
var isDescendant = await IsDescendant(request.ParentCategoryId.Value, request.CategoryId, cancellationToken);
if (isDescendant)
{
throw new InvalidOperationException("دسته‌بندی والد نمی‌تواند زیرمجموعه این دسته‌بندی باشد");
}
}
category.Name = request.Name;
category.Title = request.Title;
category.Description = request.Description;
category.ImagePath = request.ImagePath;
category.ParentCategoryId = request.ParentCategoryId;
category.SortOrder = request.SortOrder;
category.IsActive = request.IsActive;
await _context.SaveChangesAsync(cancellationToken);
return true;
}
private async Task<bool> IsDescendant(long potentialDescendantId, long ancestorId, CancellationToken cancellationToken)
{
var category = await _context.DiscountCategories
.FirstOrDefaultAsync(c => c.Id == potentialDescendantId, cancellationToken);
if (category == null || !category.ParentCategoryId.HasValue)
{
return false;
}
if (category.ParentCategoryId.Value == ancestorId)
{
return true;
}
return await IsDescendant(category.ParentCategoryId.Value, ancestorId, cancellationToken);
}
}
@@ -0,0 +1,35 @@
using FluentValidation;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountCategory;
public class UpdateDiscountCategoryCommandValidator : AbstractValidator<UpdateDiscountCategoryCommand>
{
public UpdateDiscountCategoryCommandValidator()
{
RuleFor(x => x.CategoryId)
.GreaterThan(0).WithMessage("شناسه دسته‌بندی باید مثبت باشد");
RuleFor(x => x.Name)
.NotEmpty().WithMessage("نام دسته‌بندی الزامی است")
.MaximumLength(100).WithMessage("نام دسته‌بندی نباید بیشتر از 100 کاراکتر باشد");
RuleFor(x => x.Title)
.NotEmpty().WithMessage("عنوان دسته‌بندی الزامی است")
.MaximumLength(200).WithMessage("عنوان دسته‌بندی نباید بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.Description)
.MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.Description));
RuleFor(x => x.ImagePath)
.MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.ImagePath));
RuleFor(x => x.ParentCategoryId)
.GreaterThan(0).WithMessage("شناسه دسته‌بندی والد باید مثبت باشد")
.When(x => x.ParentCategoryId.HasValue);
RuleFor(x => x.SortOrder)
.GreaterThanOrEqualTo(0).WithMessage("ترتیب نمایش نمی‌تواند منفی باشد");
}
}
@@ -0,0 +1,18 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProduct;
public class UpdateDiscountProductCommand : IRequest<Unit>
{
public long ProductId { get; set; }
public string Title { get; set; }
public string ShortInfomation { get; set; }
public string FullInformation { get; set; }
public long Price { get; set; }
public int MaxDiscountPercent { get; set; }
public string ImagePath { get; set; }
public string ThumbnailPath { get; set; }
public int RemainingCount { get; set; }
public bool IsActive { get; set; }
public List<long> CategoryIds { get; set; } = new();
}
@@ -0,0 +1,57 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities.DiscountShop;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProduct;
public class UpdateDiscountProductCommandHandler : IRequestHandler<UpdateDiscountProductCommand, Unit>
{
private readonly IApplicationDbContext _context;
public UpdateDiscountProductCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(UpdateDiscountProductCommand request, CancellationToken cancellationToken)
{
var product = await _context.DiscountProducts
.FirstOrDefaultAsync(p => p.Id == request.ProductId, cancellationToken);
if (product == null)
throw new Exception("محصول یافت نشد");
product.Title = request.Title;
product.ShortInfomation = request.ShortInfomation;
product.FullInformation = request.FullInformation;
product.Price = request.Price;
product.MaxDiscountPercent = request.MaxDiscountPercent;
product.ImagePath = request.ImagePath;
product.ThumbnailPath = request.ThumbnailPath;
product.RemainingCount = request.RemainingCount;
product.IsActive = request.IsActive;
// Update categories
var existingCategories = await _context.DiscountProductCategories
.Where(pc => pc.ProductId == request.ProductId)
.ToListAsync(cancellationToken);
_context.DiscountProductCategories.RemoveRange(existingCategories);
if (request.CategoryIds.Any())
{
var newCategories = request.CategoryIds.Select(categoryId => new DiscountProductCategory
{
ProductId = product.Id,
CategoryId = categoryId
}).ToList();
_context.DiscountProductCategories.AddRange(newCategories);
}
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
@@ -0,0 +1,45 @@
using FluentValidation;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProduct;
public class UpdateDiscountProductCommandValidator : AbstractValidator<UpdateDiscountProductCommand>
{
public UpdateDiscountProductCommandValidator()
{
RuleFor(x => x.ProductId)
.GreaterThan(0).WithMessage("شناسه محصول باید مثبت باشد");
RuleFor(x => x.Title)
.NotEmpty().WithMessage("عنوان محصول الزامی است")
.MaximumLength(200).WithMessage("عنوان محصول نباید بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.ShortInfomation)
.NotEmpty().WithMessage("توضیحات کوتاه الزامی است")
.MaximumLength(500).WithMessage("توضیحات کوتاه نباید بیشتر از 500 کاراکتر باشد");
RuleFor(x => x.FullInformation)
.NotEmpty().WithMessage("توضیحات کامل الزامی است")
.MaximumLength(5000).WithMessage("توضیحات کامل نباید بیشتر از 5000 کاراکتر باشد");
RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("قیمت باید بزرگتر از صفر باشد");
RuleFor(x => x.MaxDiscountPercent)
.InclusiveBetween(0, 100).WithMessage("درصد تخفیف باید بین 0 تا 100 باشد");
RuleFor(x => x.ImagePath)
.NotEmpty().WithMessage("مسیر تصویر اصلی الزامی است")
.MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد");
RuleFor(x => x.ThumbnailPath)
.NotEmpty().WithMessage("مسیر تصویر بندانگشتی الزامی است")
.MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد");
RuleFor(x => x.RemainingCount)
.GreaterThanOrEqualTo(0).WithMessage("موجودی نمی‌تواند منفی باشد");
RuleFor(x => x.CategoryIds)
.NotEmpty().WithMessage("حداقل یک دسته‌بندی باید انتخاب شود")
.Must(ids => ids.All(id => id > 0)).WithMessage("شناسه دسته‌بندی‌ها باید مثبت باشند");
}
}
@@ -0,0 +1,18 @@
using CMSMicroservice.Domain.Enums;
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus;
public class UpdateOrderStatusCommand : IRequest<UpdateOrderStatusResponseDto>
{
public long OrderId { get; set; }
public DeliveryStatus DeliveryStatus { get; set; }
public string? TrackingCode { get; set; }
public string? AdminNotes { get; set; }
}
public class UpdateOrderStatusResponseDto
{
public bool Success { get; set; }
public string Message { get; set; }
}
@@ -0,0 +1,46 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus;
public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatusCommand, UpdateOrderStatusResponseDto>
{
private readonly IApplicationDbContext _context;
public UpdateOrderStatusCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<UpdateOrderStatusResponseDto> Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
{
var order = await _context.DiscountOrders
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
if (order == null)
{
return new UpdateOrderStatusResponseDto
{
Success = false,
Message = "سفارش یافت نشد"
};
}
// به‌روزرسانی وضعیت
order.DeliveryStatus = request.DeliveryStatus;
if (!string.IsNullOrEmpty(request.TrackingCode))
{
order.TrackingCode = request.TrackingCode;
}
await _context.SaveChangesAsync(cancellationToken);
return new UpdateOrderStatusResponseDto
{
Success = true,
Message = "وضعیت سفارش به‌روزرسانی شد"
};
}
}
@@ -0,0 +1,19 @@
using FluentValidation;
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus;
public class UpdateOrderStatusCommandValidator : AbstractValidator<UpdateOrderStatusCommand>
{
public UpdateOrderStatusCommandValidator()
{
RuleFor(x => x.OrderId)
.GreaterThan(0).WithMessage("شناسه سفارش باید مثبت باشد");
RuleFor(x => x.DeliveryStatus)
.IsInEnum().WithMessage("وضعیت ارسال نامعتبر است");
RuleFor(x => x.TrackingCode)
.MaximumLength(50).WithMessage("کد رهگیری نباید بیشتر از 50 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.TrackingCode));
}
}
@@ -0,0 +1,28 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountCategories;
public class GetDiscountCategoriesQuery : IRequest<GetDiscountCategoriesResponseDto>
{
public long? ParentCategoryId { get; set; }
public bool? IsActive { get; set; }
}
public class GetDiscountCategoriesResponseDto
{
public List<DiscountCategoryDto> Categories { get; set; }
}
public class DiscountCategoryDto
{
public long Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string? Description { get; set; }
public string? ImagePath { get; set; }
public long? ParentCategoryId { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; }
public int ProductCount { get; set; }
public List<DiscountCategoryDto>? Children { get; set; }
}
@@ -0,0 +1,99 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountCategories;
public class GetDiscountCategoriesQueryHandler : IRequestHandler<GetDiscountCategoriesQuery, GetDiscountCategoriesResponseDto>
{
private readonly IApplicationDbContext _context;
public GetDiscountCategoriesQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetDiscountCategoriesResponseDto> Handle(GetDiscountCategoriesQuery request, CancellationToken cancellationToken)
{
var query = _context.DiscountCategories.AsQueryable();
// فیلتر بر اساس ParentCategoryId
if (request.ParentCategoryId.HasValue)
{
query = query.Where(c => c.ParentCategoryId == request.ParentCategoryId.Value);
}
else
{
// اگر ParentCategoryId مشخص نشده، فقط دسته‌های اصلی (بدون والد) را برگردان
query = query.Where(c => c.ParentCategoryId == null);
}
// فیلتر بر اساس وضعیت فعال
if (request.IsActive.HasValue)
{
query = query.Where(c => c.IsActive == request.IsActive.Value);
}
var categories = await query
.OrderBy(c => c.SortOrder)
.ThenBy(c => c.Title)
.Select(c => new DiscountCategoryDto
{
Id = c.Id,
Name = c.Name,
Title = c.Title,
Description = c.Description,
ImagePath = c.ImagePath,
ParentCategoryId = c.ParentCategoryId,
SortOrder = c.SortOrder,
IsActive = c.IsActive,
ProductCount = _context.DiscountProductCategories.Count(pc => pc.CategoryId == c.Id)
})
.ToListAsync(cancellationToken);
// بارگذاری زیرمجموعه‌ها به صورت بازگشتی
foreach (var category in categories)
{
category.Children = await LoadChildren(category.Id, request.IsActive, cancellationToken);
}
return new GetDiscountCategoriesResponseDto
{
Categories = categories
};
}
private async Task<List<DiscountCategoryDto>> LoadChildren(long parentId, bool? isActive, CancellationToken cancellationToken)
{
var query = _context.DiscountCategories.Where(c => c.ParentCategoryId == parentId);
if (isActive.HasValue)
{
query = query.Where(c => c.IsActive == isActive.Value);
}
var children = await query
.OrderBy(c => c.SortOrder)
.ThenBy(c => c.Title)
.Select(c => new DiscountCategoryDto
{
Id = c.Id,
Name = c.Name,
Title = c.Title,
Description = c.Description,
ImagePath = c.ImagePath,
ParentCategoryId = c.ParentCategoryId,
SortOrder = c.SortOrder,
IsActive = c.IsActive,
ProductCount = _context.DiscountProductCategories.Count(pc => pc.CategoryId == c.Id)
})
.ToListAsync(cancellationToken);
foreach (var child in children)
{
child.Children = await LoadChildren(child.Id, isActive, cancellationToken);
}
return children;
}
}
@@ -0,0 +1,33 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductById;
public class GetDiscountProductByIdQuery : IRequest<DiscountProductDetailDto?>
{
public long ProductId { get; set; }
}
public class DiscountProductDetailDto
{
public long Id { get; set; }
public string Title { get; set; }
public string ShortInfomation { get; set; }
public string FullInformation { get; set; }
public long Price { get; set; }
public int MaxDiscountPercent { 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 bool IsActive { get; set; }
public List<CategoryDto> Categories { get; set; } = new();
}
public class CategoryDto
{
public long Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
}
@@ -0,0 +1,64 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductById;
public class GetDiscountProductByIdQueryHandler : IRequestHandler<GetDiscountProductByIdQuery, DiscountProductDetailDto?>
{
private readonly IApplicationDbContext _context;
public GetDiscountProductByIdQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<DiscountProductDetailDto?> Handle(GetDiscountProductByIdQuery request, CancellationToken cancellationToken)
{
var product = await _context.DiscountProducts
.Where(p => p.Id == request.ProductId)
.Select(p => new DiscountProductDetailDto
{
Id = p.Id,
Title = p.Title,
ShortInfomation = p.ShortInfomation,
FullInformation = p.FullInformation,
Price = p.Price,
MaxDiscountPercent = p.MaxDiscountPercent,
Rate = p.Rate,
ImagePath = p.ImagePath,
ThumbnailPath = p.ThumbnailPath,
SaleCount = p.SaleCount,
ViewCount = p.ViewCount,
RemainingCount = p.RemainingCount,
IsActive = p.IsActive
})
.FirstOrDefaultAsync(cancellationToken);
if (product == null)
return null;
// Get categories
var categories = await _context.DiscountProductCategories
.Where(pc => pc.ProductId == request.ProductId)
.Select(pc => new CategoryDto
{
Id = pc.Category.Id,
Name = pc.Category.Name,
Title = pc.Category.Title
})
.ToListAsync(cancellationToken);
product.Categories = categories;
// Increment view count
var productEntity = await _context.DiscountProducts.FindAsync(new object[] { request.ProductId }, cancellationToken);
if (productEntity != null)
{
productEntity.ViewCount++;
await _context.SaveChangesAsync(cancellationToken);
}
return product;
}
}
@@ -0,0 +1,36 @@
using CMSMicroservice.Application.Common.Models;
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProducts;
public class GetDiscountProductsQuery : IRequest<GetDiscountProductsResponseDto>
{
public PaginationState? PaginationQuery { get; set; }
public long? CategoryId { get; set; }
public string? SearchTerm { get; set; }
public bool? IsActive { get; set; }
public int? MinPrice { get; set; }
public int? MaxPrice { get; set; }
}
public class GetDiscountProductsResponseDto
{
public MetaData MetaData { get; set; }
public List<DiscountProductDto> Models { get; set; }
}
public class DiscountProductDto
{
public long Id { get; set; }
public string Title { get; set; }
public string ShortInfomation { get; set; }
public long Price { get; set; }
public int MaxDiscountPercent { 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 bool IsActive { get; set; }
}
@@ -0,0 +1,92 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProducts;
public class GetDiscountProductsQueryHandler : IRequestHandler<GetDiscountProductsQuery, GetDiscountProductsResponseDto>
{
private readonly IApplicationDbContext _context;
public GetDiscountProductsQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetDiscountProductsResponseDto> Handle(GetDiscountProductsQuery request, CancellationToken cancellationToken)
{
var query = _context.DiscountProducts.AsQueryable();
// Apply filters
if (request.CategoryId.HasValue)
{
var productIds = await _context.DiscountProductCategories
.Where(pc => pc.CategoryId == request.CategoryId.Value)
.Select(pc => pc.ProductId)
.ToListAsync(cancellationToken);
query = query.Where(p => productIds.Contains(p.Id));
}
if (!string.IsNullOrWhiteSpace(request.SearchTerm))
{
query = query.Where(p =>
p.Title.Contains(request.SearchTerm) ||
p.ShortInfomation.Contains(request.SearchTerm));
}
if (request.IsActive.HasValue)
{
query = query.Where(p => p.IsActive == request.IsActive.Value);
}
if (request.MinPrice.HasValue)
{
query = query.Where(p => p.Price >= request.MinPrice.Value);
}
if (request.MaxPrice.HasValue)
{
query = query.Where(p => p.Price <= request.MaxPrice.Value);
}
var totalCount = await query.CountAsync(cancellationToken);
// Apply pagination
var pagination = request.PaginationQuery ?? new PaginationState { PageNumber = 1, PageSize = 10 };
var products = await query
.OrderByDescending(p => p.Created)
.Skip((pagination.PageNumber - 1) * pagination.PageSize)
.Take(pagination.PageSize)
.Select(p => new DiscountProductDto
{
Id = p.Id,
Title = p.Title,
ShortInfomation = p.ShortInfomation,
Price = p.Price,
MaxDiscountPercent = p.MaxDiscountPercent,
Rate = p.Rate,
ImagePath = p.ImagePath,
ThumbnailPath = p.ThumbnailPath,
SaleCount = p.SaleCount,
ViewCount = p.ViewCount,
RemainingCount = p.RemainingCount,
IsActive = p.IsActive
})
.ToListAsync(cancellationToken);
return new GetDiscountProductsResponseDto
{
MetaData = new MetaData
{
TotalCount = totalCount,
PageSize = pagination.PageSize,
CurrentPage = pagination.PageNumber,
TotalPage = (int)Math.Ceiling(totalCount / (double)pagination.PageSize)
},
Models = products
};
}
}
@@ -0,0 +1,46 @@
using CMSMicroservice.Domain.Enums;
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetOrderById;
public class GetOrderByIdQuery : IRequest<OrderDetailDto?>
{
public long OrderId { get; set; }
public long UserId { get; set; }
}
public class OrderDetailDto
{
public long Id { get; set; }
public long UserId { get; set; }
public long TotalAmount { get; set; }
public long DiscountBalanceUsed { get; set; }
public long GatewayAmountPaid { get; set; }
public long VatAmount { get; set; }
public PaymentStatus PaymentStatus { get; set; }
public DateTime? PaymentDate { get; set; }
public DeliveryStatus DeliveryStatus { get; set; }
public string? TrackingCode { get; set; }
public string? DeliveryDescription { get; set; }
public DateTime Created { get; set; }
public UserAddressDto Address { get; set; }
public List<OrderItemDto> Items { get; set; } = new();
}
public class UserAddressDto
{
public string Title { get; set; }
public string Address { get; set; }
public string PostalCode { get; set; }
}
public class OrderItemDto
{
public long ProductId { get; set; }
public string ProductTitle { get; set; }
public int Count { get; set; }
public long UnitPrice { get; set; }
public int DiscountPercentUsed { get; set; }
public long DiscountAmount { get; set; }
public long FinalPrice { get; set; }
}
@@ -0,0 +1,60 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetOrderById;
public class GetOrderByIdQueryHandler : IRequestHandler<GetOrderByIdQuery, OrderDetailDto?>
{
private readonly IApplicationDbContext _context;
public GetOrderByIdQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<OrderDetailDto?> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken)
{
var order = await _context.DiscountOrders
.Where(o => o.Id == request.OrderId && o.UserId == request.UserId)
.Include(o => o.UserAddress)
.Include(o => o.OrderDetails)
.ThenInclude(od => od.Product)
.FirstOrDefaultAsync(cancellationToken);
if (order == null)
return null;
return new OrderDetailDto
{
Id = order.Id,
UserId = order.UserId,
TotalAmount = order.TotalAmount,
DiscountBalanceUsed = order.DiscountBalanceUsed,
GatewayAmountPaid = order.GatewayAmountPaid,
VatAmount = order.VatAmount,
PaymentStatus = order.PaymentStatus,
PaymentDate = order.PaymentDate,
DeliveryStatus = order.DeliveryStatus,
TrackingCode = order.TrackingCode,
DeliveryDescription = order.DeliveryDescription,
Created = order.Created,
Address = new UserAddressDto
{
Title = order.UserAddress.Title,
Address = order.UserAddress.Address,
PostalCode = order.UserAddress.PostalCode
},
Items = order.OrderDetails.Select(od => new OrderItemDto
{
ProductId = od.ProductId,
ProductTitle = od.Product.Title,
Count = od.Count,
UnitPrice = od.UnitPrice,
DiscountPercentUsed = od.DiscountPercentUsed,
DiscountAmount = od.DiscountAmount,
FinalPrice = od.FinalPrice
}).ToList()
};
}
}
@@ -0,0 +1,32 @@
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserCart;
public class GetUserCartQuery : IRequest<UserCartDto>
{
public long UserId { get; set; }
}
public class UserCartDto
{
public List<CartItemDto> Items { get; set; } = new();
public long TotalAmount { get; set; }
public long MaxDiscountAmount { get; set; }
public long MinPayableAmount { get; set; }
}
public class CartItemDto
{
public long CartItemId { get; set; }
public long ProductId { get; set; }
public string ProductTitle { get; set; }
public string ProductImagePath { get; set; }
public long UnitPrice { get; set; }
public int Count { get; set; }
public long SubTotal { get; set; }
public int MaxDiscountPercent { get; set; }
public long MaxDiscountAmount { get; set; }
public long MinPayable { get; set; }
public int RemainingStock { get; set; }
public bool IsActive { get; set; }
}
@@ -0,0 +1,50 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserCart;
public class GetUserCartQueryHandler : IRequestHandler<GetUserCartQuery, UserCartDto>
{
private readonly IApplicationDbContext _context;
public GetUserCartQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<UserCartDto> Handle(GetUserCartQuery request, CancellationToken cancellationToken)
{
var cartItems = await _context.DiscountShoppingCarts
.Where(c => c.UserId == request.UserId)
.Include(c => c.Product)
.Select(c => new CartItemDto
{
CartItemId = c.Id,
ProductId = c.ProductId,
ProductTitle = c.Product.Title,
ProductImagePath = c.Product.ThumbnailPath,
UnitPrice = c.Product.Price,
Count = c.Count,
SubTotal = c.Product.Price * c.Count,
MaxDiscountPercent = c.Product.MaxDiscountPercent,
MaxDiscountAmount = (c.Product.Price * c.Count * c.Product.MaxDiscountPercent) / 100,
MinPayable = c.Product.Price * c.Count - ((c.Product.Price * c.Count * c.Product.MaxDiscountPercent) / 100),
RemainingStock = c.Product.RemainingCount,
IsActive = c.Product.IsActive
})
.ToListAsync(cancellationToken);
var totalAmount = cartItems.Sum(i => i.SubTotal);
var maxDiscountAmount = cartItems.Sum(i => i.MaxDiscountAmount);
var minPayableAmount = cartItems.Sum(i => i.MinPayable);
return new UserCartDto
{
Items = cartItems,
TotalAmount = totalAmount,
MaxDiscountAmount = maxDiscountAmount,
MinPayableAmount = minPayableAmount
};
}
}
@@ -0,0 +1,34 @@
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
using MediatR;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserOrders;
public class GetUserOrdersQuery : IRequest<GetUserOrdersResponseDto>
{
public long UserId { get; set; }
public PaginationState? PaginationQuery { get; set; }
public PaymentStatus? PaymentStatus { get; set; }
public DeliveryStatus? DeliveryStatus { get; set; }
}
public class GetUserOrdersResponseDto
{
public MetaData MetaData { get; set; }
public List<OrderSummaryDto> Models { get; set; }
}
public class OrderSummaryDto
{
public long Id { get; set; }
public long TotalAmount { get; set; }
public long DiscountBalanceUsed { get; set; }
public long GatewayAmountPaid { get; set; }
public long VatAmount { get; set; }
public PaymentStatus PaymentStatus { get; set; }
public DateTime? PaymentDate { get; set; }
public DeliveryStatus DeliveryStatus { get; set; }
public string? TrackingCode { get; set; }
public DateTime Created { get; set; }
public int ItemsCount { get; set; }
}
@@ -0,0 +1,70 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserOrders;
public class GetUserOrdersQueryHandler : IRequestHandler<GetUserOrdersQuery, GetUserOrdersResponseDto>
{
private readonly IApplicationDbContext _context;
public GetUserOrdersQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetUserOrdersResponseDto> Handle(GetUserOrdersQuery request, CancellationToken cancellationToken)
{
var query = _context.DiscountOrders
.Where(o => o.UserId == request.UserId);
// Apply filters
if (request.PaymentStatus.HasValue)
{
query = query.Where(o => o.PaymentStatus == request.PaymentStatus.Value);
}
if (request.DeliveryStatus.HasValue)
{
query = query.Where(o => o.DeliveryStatus == request.DeliveryStatus.Value);
}
var totalCount = await query.CountAsync(cancellationToken);
// Apply pagination
var pagination = request.PaginationQuery ?? new PaginationState { PageNumber = 1, PageSize = 10 };
var orders = await query
.OrderByDescending(o => o.Created)
.Skip((pagination.PageNumber - 1) * pagination.PageSize)
.Take(pagination.PageSize)
.Select(o => new OrderSummaryDto
{
Id = o.Id,
TotalAmount = o.TotalAmount,
DiscountBalanceUsed = o.DiscountBalanceUsed,
GatewayAmountPaid = o.GatewayAmountPaid,
VatAmount = o.VatAmount,
PaymentStatus = o.PaymentStatus,
PaymentDate = o.PaymentDate,
DeliveryStatus = o.DeliveryStatus,
TrackingCode = o.TrackingCode,
Created = o.Created,
ItemsCount = o.OrderDetails.Count
})
.ToListAsync(cancellationToken);
return new GetUserOrdersResponseDto
{
MetaData = new MetaData
{
TotalCount = totalCount,
PageSize = pagination.PageSize,
CurrentPage = pagination.PageNumber,
TotalPage = (int)Math.Ceiling(totalCount / (double)pagination.PageSize)
},
Models = orders
};
}
}