feat: Implement Public Message Management Commands and Queries

- Add GetUserPackageStatusQueryValidator for user package status validation.
- Create ArchiveMessageCommand and ArchiveMessageCommandHandler for archiving public messages.
- Implement ArchiveMessageCommandValidator to validate message ID.
- Introduce PublishMessageCommand and PublishMessageCommandHandler for publishing messages.
- Add PublishMessageCommandValidator for validating publish message requests.
- Implement GetPublicMessageQuery and GetPublicMessageQueryHandler for retrieving public messages.
- Create GetPublicMessageQueryValidator for validating public message requests.
- Add ApplyDiscountToOrderCommand and ApplyDiscountToOrderCommandHandler for applying discounts to orders.
- Implement ApplyDiscountToOrderCommandValidator for validating discount application requests.
- Create UpdateOrderStatusCommand and UpdateOrderStatusCommandHandler for changing order statuses.
- Implement UpdateOrderStatusCommandValidator for validating order status updates.
- Add CalculateOrderPVQuery and CalculateOrderPVQueryHandler for calculating order PV.
- Implement CalculateOrderPVQueryValidator for validating PV calculation requests.
- Create GetOrdersByDateRangeQuery and GetOrdersByDateRangeQueryHandler for retrieving orders by date range.
- Implement GetOrdersByDateRangeQueryValidator for validating date range queries.
- Add PublicMessage entity to represent public messages in the system.
- Implement PublicMessageService for handling public message operations via gRPC.
This commit is contained in:
masoodafar-web
2025-12-04 03:43:19 +03:30
parent 84f642e900
commit ba6d74fe35
45 changed files with 1777 additions and 22 deletions
@@ -0,0 +1,46 @@
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; }
}
@@ -0,0 +1,62 @@
using CMSMicroservice.Application.Common.Interfaces;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
public class CalculateOrderPVQueryHandler : IRequestHandler<CalculateOrderPVQuery, CalculateOrderPVResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ILogger<CalculateOrderPVQueryHandler> _logger;
public CalculateOrderPVQueryHandler(
IApplicationDbContext context,
ILogger<CalculateOrderPVQueryHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<CalculateOrderPVResponseDto> Handle(CalculateOrderPVQuery request, CancellationToken cancellationToken)
{
// TODO: پیاده‌سازی محاسبه PV سفارش
// 1. پیدا کردن سفارش و جزئیات:
// - var order = await _context.UserOrders
// .Include(o => o.FactorDetails)
// .ThenInclude(fd => fd.Product)
// .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken)
// - بررسی null و پرتاب NotFoundException
//
// 2. محاسبه PV هر محصول:
// - var productPVs = new List<ProductPVDto>()
// - decimal totalPV = 0
// - foreach (var detail in order.FactorDetails):
// * محاسبه PV واحد محصول (فرض: قیمت / 10000 یا از فیلد Product.PV اگر وجود دارد):
// decimal unitPV = detail.Product.PV ?? (detail.Product.Price / 10000m)
// * محاسبه PV کل این آیتم:
// decimal itemTotalPV = unitPV * detail.Count
// * اضافه به لیست:
// productPVs.Add(new ProductPVDto {
// ProductId = detail.ProductId,
// ProductTitle = detail.Product.Title,
// Quantity = detail.Count,
// UnitPV = unitPV,
// TotalPV = itemTotalPV,
// UnitPrice = detail.Product.Price
// })
// * اضافه به مجموع:
// totalPV += itemTotalPV
//
// 3. برگشت Response:
// - _logger.LogInformation("Calculated PV for order {OrderId}: {TotalPV}", request.OrderId, totalPV)
// - return new CalculateOrderPVResponseDto {
// TotalPV = totalPV,
// ProductPVs = productPVs,
// PayableAmount = order.DiscountedPrice
// }
//
// نکته: PV (Point Value) معمولاً برای سیستم‌های MLM و کمیسیون شبکه استفاده می‌شود
// نکته: فرمول محاسبه PV باید بر اساس business logic شما باشد (قیمت/10000 فقط مثال است)
// نکته: اگر entity Product فیلد PV ندارد، باید اضافه شود یا از Configuration استفاده کنید
throw new NotImplementedException("CalculateOrderPV needs implementation");
}
}
@@ -0,0 +1,11 @@
namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
public class CalculateOrderPVQueryValidator : AbstractValidator<CalculateOrderPVQuery>
{
public CalculateOrderPVQueryValidator()
{
RuleFor(x => x.OrderId)
.GreaterThan(0)
.WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
}
}
@@ -0,0 +1,66 @@
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; }
}
@@ -0,0 +1,80 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
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)
{
// TODO: پیاده‌سازی دریافت سفارشات بر اساس بازه زمانی
// 1. شروع Query:
// - var query = _context.UserOrders.AsQueryable()
// - Include User برای نام کاربر: .Include(o => o.User)
// - Include FactorDetails برای شمارش تعداد آیتم‌ها: .Include(o => o.FactorDetails)
//
// 2. اعمال فیلتر بازه زمانی:
// - query = query.Where(o => o.Created >= request.StartDate && o.Created <= request.EndDate)
//
// 3. اعمال فیلترهای اختیاری:
// - اگر request.Status.HasValue:
// query = query.Where(o => o.DeliveryStatus == request.Status.Value)
// - اگر request.UserId.HasValue:
// query = query.Where(o => o.UserId == request.UserId.Value)
//
// 4. محاسبه تعداد کل:
// - var totalCount = await query.CountAsync(cancellationToken)
//
// 5. مرتب‌سازی و Pagination:
// - query = query.OrderByDescending(o => o.Created)
// - query = query.Skip((request.PageIndex - 1) * request.PageSize)
// - query = query.Take(request.PageSize)
//
// 6. دریافت داده‌ها:
// - var orders = await query.ToListAsync(cancellationToken)
//
// 7. Mapping به DTO:
// - var orderDtos = orders.Select(o => new OrderSummaryDto {
// Id = o.Id,
// UserId = o.UserId,
// UserFullName = $"{o.User.Firstname} {o.User.Lastname}",
// Amount = o.Amount,
// DiscountedPrice = o.DiscountedPrice,
// Status = o.DeliveryStatus,
// Created = o.Created,
// ShippedAt = o.ShippedAt,
// DeliveredAt = o.DeliveredAt,
// ItemsCount = o.FactorDetails.Count
// }).ToList()
//
// 8. ساخت MetaData:
// - var totalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize)
// - MetaData = new MetaData {
// CurrentPage = request.PageIndex,
// TotalPage = totalPages,
// PageSize = request.PageSize,
// TotalCount = totalCount,
// HasNext = request.PageIndex < totalPages,
// HasPrevious = request.PageIndex > 1
// }
//
// 9. Log و برگشت:
// - _logger.LogInformation("Retrieved {Count} orders for date range {Start} to {End}", orders.Count, request.StartDate, request.EndDate)
// - return new GetOrdersByDateRangeResponseDto { MetaData = metaData, Orders = orderDtos }
//
// نکته: برای performance بهتر، می‌توان از AsNoTracking() استفاده کرد
throw new NotImplementedException("GetOrdersByDateRange needs implementation");
}
}
@@ -0,0 +1,28 @@
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.UtcNow.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 سال باشد");
}
}