feat: Implement withdrawal reports query and service integration

This commit is contained in:
masoodafar-web
2025-12-04 17:29:10 +03:30
parent abda623519
commit 5e3112d71f
7 changed files with 515 additions and 276 deletions

View File

@@ -1,5 +1,7 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Enums;
using Microsoft.EntityFrameworkCore;
using ValidationException = FluentValidation.ValidationException;
namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
@@ -18,36 +20,37 @@ public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatus
public async Task<UpdateOrderStatusResponseDto> Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
{
// TODO: پیاده‌سازی تغییر وضعیت سفارش
// 1. پیدا کردن سفارش:
// - await _context.UserOrders.FirstOrDefaultAsync(o => o.Id == request.OrderId)
// - بررسی null و پرتاب NotFoundException
//
// 2. بررسی‌های انتقال وضعیت (State Transition Validation):
// - نمی‌توان از Delivered به Cancelled رفت
// - نمی‌توان از Cancelled به سایر وضعیت‌ها رفت
// - الگوی معمول: Pending → Processing → Shipped → Delivered
// - Cancelled می‌تواند از Pending, Processing, Shipped باشد
//
// 3. تغییر وضعیت:
// - order.DeliveryStatus = request.NewStatus
// - اگر Description داریم: order.DeliveryDescription = request.Description
// - تنظیم تاریخ‌های مربوطه:
// * اگر NewStatus == Delivered → order.DeliveredAt = DateTime.UtcNow
// * اگر NewStatus == Shipped → order.ShippedAt = DateTime.UtcNow
// * اگر NewStatus == Processing → order.ProcessedAt = DateTime.UtcNow
//
// 4. ذخیره و Log:
// - await _context.SaveChangesAsync(cancellationToken)
// - _logger.LogInformation("Order {OrderId} status changed to {NewStatus}", request.OrderId, request.NewStatus)
//
// 5. برگشت Response:
// - Success = true
// - Message = "وضعیت سفارش با موفقیت تغییر کرد"
// - CurrentStatus = order.DeliveryStatus
//
// نکته: برای validation دقیق‌تر، می‌توان یک State Machine برای انتقال‌های مجاز تعریف کرد
var order = await _context.UserOrders
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
throw new NotImplementedException("UpdateOrderStatus needs implementation");
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
};
}
}

View File

@@ -1,4 +1,6 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
@@ -7,6 +9,12 @@ public class CalculateOrderPVQueryHandler : IRequestHandler<CalculateOrderPVQuer
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)
@@ -17,46 +25,56 @@ public class CalculateOrderPVQueryHandler : IRequestHandler<CalculateOrderPVQuer
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 استفاده کنید
var order = await _context.UserOrders
.Include(o => o.FactorDetails)
.ThenInclude(fd => fd.Product)
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
throw new NotImplementedException("CalculateOrderPV needs implementation");
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;
}
}

View File

@@ -1,5 +1,6 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
@@ -18,63 +19,78 @@ public class GetOrdersByDateRangeQueryHandler : IRequestHandler<GetOrdersByDateR
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() استفاده کرد
var query = _context.UserOrders
.AsNoTracking()
.Include(o => o.User)
.Include(o => o.FactorDetails)
.AsQueryable();
throw new NotImplementedException("GetOrdersByDateRange needs implementation");
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;
}
}