Add migration for DiscountProductImages table
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m53s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m53s
- Created a new table `DiscountProductImages` in the CMS schema. - Added columns for image details including `Title`, `AltText`, `ImagePath`, `ThumbnailPath`, `SortOrder`, `IsActive`, `Created`, `CreatedBy`, `LastModified`, `LastModifiedBy`, and `IsDeleted`. - Established a foreign key relationship with the `DiscountProducts` table. - Created indexes on `DiscountProductId` and a composite index on `DiscountProductId` and `SortOrder`.
This commit is contained in:
@@ -51,6 +51,7 @@ public interface IApplicationDbContext
|
||||
DbSet<DiscountProduct> DiscountProducts { get; }
|
||||
DbSet<DiscountCategory> DiscountCategories { get; }
|
||||
DbSet<DiscountProductCategory> DiscountProductCategories { get; }
|
||||
DbSet<DiscountProductImage> DiscountProductImages { get; }
|
||||
DbSet<DiscountShoppingCart> DiscountShoppingCarts { get; }
|
||||
DbSet<DiscountOrder> DiscountOrders { get; }
|
||||
DbSet<DiscountOrderDetail> DiscountOrderDetails { get; }
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
namespace CMSMicroservice.Application.Common.Services;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس محاسبه مالیات بر ارزش افزوده (VAT)
|
||||
/// </summary>
|
||||
public static class VatCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// نرخ VAT ایران - 9 درصد
|
||||
/// </summary>
|
||||
public const decimal VAT_RATE = 0.09m;
|
||||
|
||||
/// <summary>
|
||||
/// نرخ VAT به صورت درصد (9)
|
||||
/// </summary>
|
||||
public const int VAT_PERCENT = 9;
|
||||
|
||||
/// <summary>
|
||||
/// محاسبه VAT از مبلغ خالص
|
||||
/// </summary>
|
||||
/// <param name="netAmount">مبلغ خالص (بدون مالیات)</param>
|
||||
/// <returns>مبلغ VAT</returns>
|
||||
public static long CalculateVat(long netAmount)
|
||||
{
|
||||
return (long)(netAmount * VAT_RATE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// محاسبه مبلغ ناخالص (شامل VAT) از مبلغ خالص
|
||||
/// </summary>
|
||||
/// <param name="netAmount">مبلغ خالص</param>
|
||||
/// <returns>مبلغ ناخالص (خالص + VAT)</returns>
|
||||
public static long CalculateGrossAmount(long netAmount)
|
||||
{
|
||||
return netAmount + CalculateVat(netAmount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// استخراج مبلغ خالص از مبلغ ناخالص
|
||||
/// </summary>
|
||||
/// <param name="grossAmount">مبلغ ناخالص (شامل VAT)</param>
|
||||
/// <returns>مبلغ خالص</returns>
|
||||
public static long ExtractNetAmount(long grossAmount)
|
||||
{
|
||||
return (long)(grossAmount / (1 + VAT_RATE));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// استخراج VAT از مبلغ ناخالص
|
||||
/// </summary>
|
||||
/// <param name="grossAmount">مبلغ ناخالص (شامل VAT)</param>
|
||||
/// <returns>مبلغ VAT</returns>
|
||||
public static long ExtractVatFromGross(long grossAmount)
|
||||
{
|
||||
return grossAmount - ExtractNetAmount(grossAmount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// جزئیات محاسبه VAT
|
||||
/// </summary>
|
||||
public record VatBreakdown(
|
||||
long NetAmount,
|
||||
long VatAmount,
|
||||
long GrossAmount,
|
||||
decimal VatRate
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// محاسبه کامل جزئیات VAT
|
||||
/// </summary>
|
||||
/// <param name="netAmount">مبلغ خالص</param>
|
||||
/// <returns>جزئیات کامل VAT</returns>
|
||||
public static VatBreakdown CalculateBreakdown(long netAmount)
|
||||
{
|
||||
var vatAmount = CalculateVat(netAmount);
|
||||
return new VatBreakdown(
|
||||
NetAmount: netAmount,
|
||||
VatAmount: vatAmount,
|
||||
GrossAmount: netAmount + vatAmount,
|
||||
VatRate: VAT_RATE
|
||||
);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddDiscountProductImage;
|
||||
|
||||
public class AddDiscountProductImageCommand : IRequest<long>
|
||||
{
|
||||
public long DiscountProductId { get; set; }
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
public string ThumbnailPath { get; set; } = string.Empty;
|
||||
public string? Title { get; set; }
|
||||
public string? AltText { get; set; }
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddDiscountProductImage;
|
||||
|
||||
public class AddDiscountProductImageCommandHandler : IRequestHandler<AddDiscountProductImageCommand, long>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public AddDiscountProductImageCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<long> Handle(AddDiscountProductImageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Verify product exists
|
||||
var productExists = await _context.DiscountProducts
|
||||
.AnyAsync(p => p.Id == request.DiscountProductId, cancellationToken);
|
||||
|
||||
if (!productExists)
|
||||
throw new InvalidOperationException($"DiscountProduct with Id {request.DiscountProductId} not found.");
|
||||
|
||||
// Get the max sort order for this product
|
||||
var maxSortOrder = await _context.DiscountProductImages
|
||||
.Where(i => i.DiscountProductId == request.DiscountProductId)
|
||||
.MaxAsync(i => (int?)i.SortOrder, cancellationToken) ?? 0;
|
||||
|
||||
var image = new DiscountProductImage
|
||||
{
|
||||
DiscountProductId = request.DiscountProductId,
|
||||
ImagePath = request.ImagePath,
|
||||
ThumbnailPath = request.ThumbnailPath,
|
||||
Title = request.Title,
|
||||
AltText = request.AltText,
|
||||
SortOrder = maxSortOrder + 1,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
_context.DiscountProductImages.Add(image);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return image.Id;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProductImage;
|
||||
|
||||
public class DeleteDiscountProductImageCommand : IRequest<bool>
|
||||
{
|
||||
public long Id { get; set; }
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProductImage;
|
||||
|
||||
public class DeleteDiscountProductImageCommandHandler : IRequestHandler<DeleteDiscountProductImageCommand, bool>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public DeleteDiscountProductImageCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(DeleteDiscountProductImageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var image = await _context.DiscountProductImages
|
||||
.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
|
||||
|
||||
if (image == null)
|
||||
return false;
|
||||
|
||||
_context.DiscountProductImages.Remove(image);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Reorder remaining images for this product
|
||||
var remainingImages = await _context.DiscountProductImages
|
||||
.Where(i => i.DiscountProductId == image.DiscountProductId)
|
||||
.OrderBy(i => i.SortOrder)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
for (int i = 0; i < remainingImages.Count; i++)
|
||||
{
|
||||
remainingImages[i].SortOrder = i + 1;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -1,4 +1,5 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Services;
|
||||
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||
using CMSMicroservice.Domain.Entities.Payment;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
@@ -109,9 +110,10 @@ public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand, Place
|
||||
|
||||
var gatewayAmountRequired = totalAmount - actualDiscountBalanceUsed;
|
||||
|
||||
// Calculate VAT (9%)
|
||||
var vatAmount = (gatewayAmountRequired * 9) / 100;
|
||||
var finalGatewayAmount = gatewayAmountRequired + vatAmount;
|
||||
// Calculate VAT using centralized calculator
|
||||
var vatBreakdown = VatCalculator.CalculateBreakdown(gatewayAmountRequired);
|
||||
var vatAmount = vatBreakdown.VatAmount;
|
||||
var finalGatewayAmount = vatBreakdown.GrossAmount;
|
||||
|
||||
// Create transaction for gateway payment
|
||||
var transaction = new Transaction
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.ReorderDiscountProductImages;
|
||||
|
||||
public class ReorderDiscountProductImagesCommand : IRequest<bool>
|
||||
{
|
||||
public long DiscountProductId { get; set; }
|
||||
public List<long> ImageIds { get; set; } = new();
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.ReorderDiscountProductImages;
|
||||
|
||||
public class ReorderDiscountProductImagesCommandHandler : IRequestHandler<ReorderDiscountProductImagesCommand, bool>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public ReorderDiscountProductImagesCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(ReorderDiscountProductImagesCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var images = await _context.DiscountProductImages
|
||||
.Where(i => i.DiscountProductId == request.DiscountProductId)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (!images.Any())
|
||||
return false;
|
||||
|
||||
// Validate all image IDs belong to this product
|
||||
var imageIdSet = images.Select(i => i.Id).ToHashSet();
|
||||
if (!request.ImageIds.All(id => imageIdSet.Contains(id)))
|
||||
return false;
|
||||
|
||||
// Update sort order based on the new order
|
||||
for (int i = 0; i < request.ImageIds.Count; i++)
|
||||
{
|
||||
var image = images.FirstOrDefault(img => img.Id == request.ImageIds[i]);
|
||||
if (image != null)
|
||||
{
|
||||
image.SortOrder = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProductImage;
|
||||
|
||||
public class UpdateDiscountProductImageCommand : IRequest<bool>
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
public string ThumbnailPath { get; set; } = string.Empty;
|
||||
public string? Title { get; set; }
|
||||
public string? AltText { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProductImage;
|
||||
|
||||
public class UpdateDiscountProductImageCommandHandler : IRequestHandler<UpdateDiscountProductImageCommand, bool>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public UpdateDiscountProductImageCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(UpdateDiscountProductImageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var image = await _context.DiscountProductImages
|
||||
.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
|
||||
|
||||
if (image == null)
|
||||
return false;
|
||||
|
||||
image.ImagePath = request.ImagePath;
|
||||
image.ThumbnailPath = request.ThumbnailPath;
|
||||
image.Title = request.Title;
|
||||
image.AltText = request.AltText;
|
||||
image.IsActive = request.IsActive;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
using CMSMicroservice.Application.Common.Models;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetAllDiscountOrders;
|
||||
|
||||
/// <summary>
|
||||
/// کوئری دریافت همه سفارشات فروشگاه تخفیفی برای ادمین
|
||||
/// </summary>
|
||||
public class GetAllDiscountOrdersQuery : IRequest<GetAllDiscountOrdersResponseDto>
|
||||
{
|
||||
public PaginationState? PaginationQuery { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس شناسه کاربر
|
||||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس وضعیت پرداخت
|
||||
/// </summary>
|
||||
public PaymentStatus? PaymentStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس وضعیت ارسال
|
||||
/// </summary>
|
||||
public DeliveryStatus? DeliveryStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// جستجو بر اساس موبایل کاربر
|
||||
/// </summary>
|
||||
public string? UserMobile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// جستجو بر اساس کد رهگیری
|
||||
/// </summary>
|
||||
public string? TrackingCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر از تاریخ
|
||||
/// </summary>
|
||||
public DateTime? FromDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر تا تاریخ
|
||||
/// </summary>
|
||||
public DateTime? ToDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// حداقل مبلغ سفارش
|
||||
/// </summary>
|
||||
public long? MinAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// حداکثر مبلغ سفارش
|
||||
/// </summary>
|
||||
public long? MaxAmount { get; set; }
|
||||
}
|
||||
|
||||
public class GetAllDiscountOrdersResponseDto
|
||||
{
|
||||
public MetaData MetaData { get; set; } = new();
|
||||
public List<AdminOrderDto> Models { get; set; } = new();
|
||||
}
|
||||
|
||||
public class AdminOrderDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
// اطلاعات کاربر
|
||||
public long UserId { get; set; }
|
||||
public string UserFullName { get; set; } = string.Empty;
|
||||
public string UserMobile { get; set; } = string.Empty;
|
||||
|
||||
// مبالغ
|
||||
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 DateTime? DeliveryDate { get; set; }
|
||||
|
||||
// آدرس تحویل
|
||||
public string? ShippingAddress { get; set; }
|
||||
public string? ReceiverName { get; set; }
|
||||
public string? ReceiverMobile { get; set; }
|
||||
|
||||
// رهگیری
|
||||
public string? TrackingCode { get; set; }
|
||||
public string? AdminNote { get; set; }
|
||||
|
||||
// تاریخ ها
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime? LastModified { get; set; }
|
||||
|
||||
// تعداد آیتم ها
|
||||
public int ItemsCount { get; set; }
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Models;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetAllDiscountOrders;
|
||||
|
||||
public class GetAllDiscountOrdersQueryHandler : IRequestHandler<GetAllDiscountOrdersQuery, GetAllDiscountOrdersResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetAllDiscountOrdersQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetAllDiscountOrdersResponseDto> Handle(GetAllDiscountOrdersQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.DiscountOrders
|
||||
.Include(o => o.User)
|
||||
.Include(o => o.UserAddress)
|
||||
.AsQueryable();
|
||||
|
||||
// فیلتر بر اساس شناسه کاربر
|
||||
if (request.UserId.HasValue)
|
||||
{
|
||||
query = query.Where(o => o.UserId == request.UserId.Value);
|
||||
}
|
||||
|
||||
// فیلتر بر اساس وضعیت پرداخت
|
||||
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);
|
||||
}
|
||||
|
||||
// جستجو بر اساس موبایل کاربر
|
||||
if (!string.IsNullOrWhiteSpace(request.UserMobile))
|
||||
{
|
||||
query = query.Where(o => o.User.Mobile.Contains(request.UserMobile));
|
||||
}
|
||||
|
||||
// جستجو بر اساس کد رهگیری
|
||||
if (!string.IsNullOrWhiteSpace(request.TrackingCode))
|
||||
{
|
||||
query = query.Where(o => o.TrackingCode != null && o.TrackingCode.Contains(request.TrackingCode));
|
||||
}
|
||||
|
||||
// فیلتر از تاریخ
|
||||
if (request.FromDate.HasValue)
|
||||
{
|
||||
query = query.Where(o => o.Created >= request.FromDate.Value);
|
||||
}
|
||||
|
||||
// فیلتر تا تاریخ
|
||||
if (request.ToDate.HasValue)
|
||||
{
|
||||
query = query.Where(o => o.Created <= request.ToDate.Value);
|
||||
}
|
||||
|
||||
// فیلتر حداقل مبلغ
|
||||
if (request.MinAmount.HasValue)
|
||||
{
|
||||
query = query.Where(o => o.TotalAmount >= request.MinAmount.Value);
|
||||
}
|
||||
|
||||
// فیلتر حداکثر مبلغ
|
||||
if (request.MaxAmount.HasValue)
|
||||
{
|
||||
query = query.Where(o => o.TotalAmount <= request.MaxAmount.Value);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync(cancellationToken);
|
||||
|
||||
// Apply pagination
|
||||
var pagination = request.PaginationQuery ?? new PaginationState { PageNumber = 1, PageSize = 20 };
|
||||
|
||||
var orders = await query
|
||||
.OrderByDescending(o => o.Created)
|
||||
.Skip((pagination.PageNumber - 1) * pagination.PageSize)
|
||||
.Take(pagination.PageSize)
|
||||
.Select(o => new AdminOrderDto
|
||||
{
|
||||
Id = o.Id,
|
||||
UserId = o.UserId,
|
||||
UserFullName = (o.User.FirstName ?? "") + " " + (o.User.LastName ?? ""),
|
||||
UserMobile = o.User.Mobile,
|
||||
TotalAmount = o.TotalAmount,
|
||||
DiscountBalanceUsed = o.DiscountBalanceUsed,
|
||||
GatewayAmountPaid = o.GatewayAmountPaid,
|
||||
VatAmount = o.VatAmount,
|
||||
PaymentStatus = o.PaymentStatus,
|
||||
PaymentDate = o.PaymentDate,
|
||||
DeliveryStatus = o.DeliveryStatus,
|
||||
DeliveryDate = null, // TODO: Add DeliveryDate to DiscountOrder if needed
|
||||
ShippingAddress = o.UserAddress.Address,
|
||||
ReceiverName = o.UserAddress.Title,
|
||||
ReceiverMobile = o.User.Mobile,
|
||||
TrackingCode = o.TrackingCode,
|
||||
AdminNote = o.DeliveryDescription,
|
||||
Created = o.Created,
|
||||
LastModified = o.LastModified,
|
||||
ItemsCount = o.OrderDetails.Count
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new GetAllDiscountOrdersResponseDto
|
||||
{
|
||||
MetaData = new MetaData
|
||||
{
|
||||
TotalCount = totalCount,
|
||||
PageSize = pagination.PageSize,
|
||||
CurrentPage = pagination.PageNumber,
|
||||
TotalPage = (int)Math.Ceiling(totalCount / (double)pagination.PageSize)
|
||||
},
|
||||
Models = orders
|
||||
};
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductImages;
|
||||
|
||||
public class GetDiscountProductImagesQuery : IRequest<List<DiscountProductImageDto>>
|
||||
{
|
||||
public long DiscountProductId { get; set; }
|
||||
public bool OnlyActive { get; set; } = true;
|
||||
}
|
||||
|
||||
public class DiscountProductImageDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long DiscountProductId { get; set; }
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
public string ThumbnailPath { get; set; } = string.Empty;
|
||||
public string? Title { get; set; }
|
||||
public string? AltText { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductImages;
|
||||
|
||||
public class GetDiscountProductImagesQueryHandler : IRequestHandler<GetDiscountProductImagesQuery, List<DiscountProductImageDto>>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetDiscountProductImagesQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<List<DiscountProductImageDto>> Handle(GetDiscountProductImagesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.DiscountProductImages
|
||||
.Where(i => i.DiscountProductId == request.DiscountProductId);
|
||||
|
||||
if (request.OnlyActive)
|
||||
query = query.Where(i => i.IsActive);
|
||||
|
||||
return await query
|
||||
.OrderBy(i => i.SortOrder)
|
||||
.Select(i => new DiscountProductImageDto
|
||||
{
|
||||
Id = i.Id,
|
||||
DiscountProductId = i.DiscountProductId,
|
||||
ImagePath = i.ImagePath,
|
||||
ThumbnailPath = i.ThumbnailPath,
|
||||
Title = i.Title,
|
||||
AltText = i.AltText,
|
||||
SortOrder = i.SortOrder,
|
||||
IsActive = i.IsActive
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
using CMSMicroservice.Application.Common.Models;
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountSalesReport;
|
||||
|
||||
/// <summary>
|
||||
/// کوئری گزارش فروش فروشگاه تخفیفی
|
||||
/// </summary>
|
||||
public class GetDiscountSalesReportQuery : IRequest<DiscountSalesReportDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// از تاریخ
|
||||
/// </summary>
|
||||
public DateTime? FromDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تا تاریخ
|
||||
/// </summary>
|
||||
public DateTime? ToDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نوع گزارش
|
||||
/// </summary>
|
||||
public SalesReportType ReportType { get; set; } = SalesReportType.Summary;
|
||||
}
|
||||
|
||||
public enum SalesReportType
|
||||
{
|
||||
/// <summary>
|
||||
/// خلاصه کلی
|
||||
/// </summary>
|
||||
Summary,
|
||||
|
||||
/// <summary>
|
||||
/// روزانه
|
||||
/// </summary>
|
||||
Daily,
|
||||
|
||||
/// <summary>
|
||||
/// هفتگی
|
||||
/// </summary>
|
||||
Weekly,
|
||||
|
||||
/// <summary>
|
||||
/// ماهانه
|
||||
/// </summary>
|
||||
Monthly
|
||||
}
|
||||
|
||||
public class DiscountSalesReportDto
|
||||
{
|
||||
// خلاصه کلی
|
||||
public SalesSummary Summary { get; set; } = new();
|
||||
|
||||
// جزئیات زمانی (برای گزارشهای روزانه، هفتگی، ماهانه)
|
||||
public List<SalesPeriodDto> Periods { get; set; } = new();
|
||||
|
||||
// پرفروشترین محصولات
|
||||
public List<TopSellingProductDto> TopProducts { get; set; } = new();
|
||||
}
|
||||
|
||||
public class SalesSummary
|
||||
{
|
||||
/// <summary>
|
||||
/// تعداد کل سفارشات
|
||||
/// </summary>
|
||||
public int TotalOrders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تعداد سفارشات موفق (پرداخت شده)
|
||||
/// </summary>
|
||||
public int CompletedOrders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تعداد سفارشات در انتظار پرداخت
|
||||
/// </summary>
|
||||
public int PendingOrders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تعداد سفارشات لغو شده
|
||||
/// </summary>
|
||||
public int CancelledOrders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// جمع کل فروش (TotalAmount)
|
||||
/// </summary>
|
||||
public long TotalSalesAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// جمع تخفیف استفاده شده (DiscountBalanceUsed)
|
||||
/// </summary>
|
||||
public long TotalDiscountUsed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// جمع پرداخت از درگاه (GatewayAmountPaid)
|
||||
/// </summary>
|
||||
public long TotalGatewayPaid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// جمع VAT
|
||||
/// </summary>
|
||||
public long TotalVatAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// میانگین ارزش سفارش
|
||||
/// </summary>
|
||||
public long AverageOrderValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تعداد کاربران یکتا
|
||||
/// </summary>
|
||||
public int UniqueCustomers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تعداد کل محصولات فروخته شده
|
||||
/// </summary>
|
||||
public int TotalProductsSold { get; set; }
|
||||
}
|
||||
|
||||
public class SalesPeriodDto
|
||||
{
|
||||
/// <summary>
|
||||
/// نام دوره (مثل: 1403/10/11 یا هفته 41)
|
||||
/// </summary>
|
||||
public string PeriodLabel { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// تاریخ شروع دوره
|
||||
/// </summary>
|
||||
public DateTime PeriodStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تاریخ پایان دوره
|
||||
/// </summary>
|
||||
public DateTime PeriodEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تعداد سفارشات
|
||||
/// </summary>
|
||||
public int OrdersCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// جمع فروش
|
||||
/// </summary>
|
||||
public long TotalAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// جمع تخفیف
|
||||
/// </summary>
|
||||
public long DiscountUsed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// جمع پرداخت درگاه
|
||||
/// </summary>
|
||||
public long GatewayPaid { get; set; }
|
||||
}
|
||||
|
||||
public class TopSellingProductDto
|
||||
{
|
||||
public long ProductId { get; set; }
|
||||
public string ProductTitle { get; set; } = string.Empty;
|
||||
public string? ImagePath { get; set; }
|
||||
public int QuantitySold { get; set; }
|
||||
public long TotalRevenue { get; set; }
|
||||
public int OrdersCount { get; set; }
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Globalization;
|
||||
|
||||
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountSalesReport;
|
||||
|
||||
public class GetDiscountSalesReportQueryHandler : IRequestHandler<GetDiscountSalesReportQuery, DiscountSalesReportDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private static readonly PersianCalendar PersianCalendar = new();
|
||||
|
||||
public GetDiscountSalesReportQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<DiscountSalesReportDto> Handle(GetDiscountSalesReportQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new DiscountSalesReportDto();
|
||||
|
||||
// Base query for orders
|
||||
var ordersQuery = _context.DiscountOrders.AsQueryable();
|
||||
|
||||
// Apply date filters
|
||||
if (request.FromDate.HasValue)
|
||||
{
|
||||
ordersQuery = ordersQuery.Where(o => o.Created >= request.FromDate.Value);
|
||||
}
|
||||
|
||||
if (request.ToDate.HasValue)
|
||||
{
|
||||
ordersQuery = ordersQuery.Where(o => o.Created <= request.ToDate.Value);
|
||||
}
|
||||
|
||||
// Get all matching orders for summary
|
||||
var orders = await ordersQuery.ToListAsync(cancellationToken);
|
||||
var completedOrders = orders.Where(o => o.PaymentStatus == PaymentStatus.Success).ToList();
|
||||
|
||||
// Calculate summary
|
||||
result.Summary = new SalesSummary
|
||||
{
|
||||
TotalOrders = orders.Count,
|
||||
CompletedOrders = completedOrders.Count,
|
||||
PendingOrders = orders.Count(o => o.PaymentStatus == PaymentStatus.Pending),
|
||||
CancelledOrders = orders.Count(o => o.PaymentStatus == PaymentStatus.Reject),
|
||||
TotalSalesAmount = completedOrders.Sum(o => o.TotalAmount),
|
||||
TotalDiscountUsed = completedOrders.Sum(o => o.DiscountBalanceUsed),
|
||||
TotalGatewayPaid = completedOrders.Sum(o => o.GatewayAmountPaid),
|
||||
TotalVatAmount = completedOrders.Sum(o => o.VatAmount),
|
||||
AverageOrderValue = completedOrders.Any() ? (long)completedOrders.Average(o => o.TotalAmount) : 0,
|
||||
UniqueCustomers = orders.Select(o => o.UserId).Distinct().Count()
|
||||
};
|
||||
|
||||
// Get total products sold
|
||||
var orderIds = completedOrders.Select(o => o.Id).ToList();
|
||||
result.Summary.TotalProductsSold = await _context.DiscountOrderDetails
|
||||
.Where(d => orderIds.Contains(d.DiscountOrderId))
|
||||
.SumAsync(d => d.Count, cancellationToken);
|
||||
|
||||
// Generate period-based reports
|
||||
if (request.ReportType != SalesReportType.Summary && completedOrders.Any())
|
||||
{
|
||||
result.Periods = GeneratePeriodReport(completedOrders, request.ReportType);
|
||||
}
|
||||
|
||||
// Get top selling products
|
||||
result.TopProducts = await GetTopSellingProducts(orderIds, cancellationToken);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<SalesPeriodDto> GeneratePeriodReport(List<Domain.Entities.DiscountShop.DiscountOrder> orders, SalesReportType reportType)
|
||||
{
|
||||
var periods = new List<SalesPeriodDto>();
|
||||
|
||||
var groupedOrders = reportType switch
|
||||
{
|
||||
SalesReportType.Daily => orders.GroupBy(o => o.Created.Date),
|
||||
SalesReportType.Weekly => orders.GroupBy(o => GetStartOfWeek(o.Created)),
|
||||
SalesReportType.Monthly => orders.GroupBy(o => new DateTime(o.Created.Year, o.Created.Month, 1)),
|
||||
_ => orders.GroupBy(o => o.Created.Date)
|
||||
};
|
||||
|
||||
foreach (var group in groupedOrders.OrderBy(g => g.Key))
|
||||
{
|
||||
var periodStart = group.Key;
|
||||
var periodEnd = reportType switch
|
||||
{
|
||||
SalesReportType.Daily => periodStart.AddDays(1).AddSeconds(-1),
|
||||
SalesReportType.Weekly => periodStart.AddDays(7).AddSeconds(-1),
|
||||
SalesReportType.Monthly => periodStart.AddMonths(1).AddSeconds(-1),
|
||||
_ => periodStart.AddDays(1).AddSeconds(-1)
|
||||
};
|
||||
|
||||
periods.Add(new SalesPeriodDto
|
||||
{
|
||||
PeriodLabel = GetPersianPeriodLabel(periodStart, reportType),
|
||||
PeriodStart = periodStart,
|
||||
PeriodEnd = periodEnd,
|
||||
OrdersCount = group.Count(),
|
||||
TotalAmount = group.Sum(o => o.TotalAmount),
|
||||
DiscountUsed = group.Sum(o => o.DiscountBalanceUsed),
|
||||
GatewayPaid = group.Sum(o => o.GatewayAmountPaid)
|
||||
});
|
||||
}
|
||||
|
||||
return periods;
|
||||
}
|
||||
|
||||
private async Task<List<TopSellingProductDto>> GetTopSellingProducts(List<long> orderIds, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!orderIds.Any())
|
||||
return new List<TopSellingProductDto>();
|
||||
|
||||
var topProducts = await _context.DiscountOrderDetails
|
||||
.Where(d => orderIds.Contains(d.DiscountOrderId))
|
||||
.GroupBy(d => d.ProductId)
|
||||
.Select(g => new
|
||||
{
|
||||
ProductId = g.Key,
|
||||
QuantitySold = g.Sum(d => d.Count),
|
||||
TotalRevenue = g.Sum(d => d.FinalPrice),
|
||||
OrdersCount = g.Select(d => d.DiscountOrderId).Distinct().Count()
|
||||
})
|
||||
.OrderByDescending(x => x.QuantitySold)
|
||||
.Take(10)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var productIds = topProducts.Select(p => p.ProductId).ToList();
|
||||
var products = await _context.DiscountProducts
|
||||
.Where(p => productIds.Contains(p.Id))
|
||||
.Select(p => new { p.Id, p.Title, p.ThumbnailPath })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return topProducts.Select(tp => new TopSellingProductDto
|
||||
{
|
||||
ProductId = tp.ProductId,
|
||||
ProductTitle = products.FirstOrDefault(p => p.Id == tp.ProductId)?.Title ?? "نامشخص",
|
||||
ImagePath = products.FirstOrDefault(p => p.Id == tp.ProductId)?.ThumbnailPath,
|
||||
QuantitySold = tp.QuantitySold,
|
||||
TotalRevenue = tp.TotalRevenue,
|
||||
OrdersCount = tp.OrdersCount
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private static DateTime GetStartOfWeek(DateTime date)
|
||||
{
|
||||
// شروع هفته از شنبه (Saturday = 6 in DayOfWeek)
|
||||
var diff = ((int)date.DayOfWeek + 1) % 7; // Saturday = 0
|
||||
return date.AddDays(-diff).Date;
|
||||
}
|
||||
|
||||
private static string GetPersianPeriodLabel(DateTime date, SalesReportType reportType)
|
||||
{
|
||||
var persianYear = PersianCalendar.GetYear(date);
|
||||
var persianMonth = PersianCalendar.GetMonth(date);
|
||||
var persianDay = PersianCalendar.GetDayOfMonth(date);
|
||||
|
||||
return reportType switch
|
||||
{
|
||||
SalesReportType.Daily => $"{persianYear}/{persianMonth:D2}/{persianDay:D2}",
|
||||
SalesReportType.Weekly => $"هفته {GetPersianWeekOfYear(date)} - {persianYear}",
|
||||
SalesReportType.Monthly => $"{GetPersianMonthName(persianMonth)} {persianYear}",
|
||||
_ => $"{persianYear}/{persianMonth:D2}/{persianDay:D2}"
|
||||
};
|
||||
}
|
||||
|
||||
private static int GetPersianWeekOfYear(DateTime date)
|
||||
{
|
||||
var firstDayOfYear = PersianCalendar.ToDateTime(PersianCalendar.GetYear(date), 1, 1, 0, 0, 0, 0);
|
||||
var daysSinceStart = (date - firstDayOfYear).Days;
|
||||
return (daysSinceStart / 7) + 1;
|
||||
}
|
||||
|
||||
private static string GetPersianMonthName(int month) => month switch
|
||||
{
|
||||
1 => "فروردین",
|
||||
2 => "اردیبهشت",
|
||||
3 => "خرداد",
|
||||
4 => "تیر",
|
||||
5 => "مرداد",
|
||||
6 => "شهریور",
|
||||
7 => "مهر",
|
||||
8 => "آبان",
|
||||
9 => "آذر",
|
||||
10 => "دی",
|
||||
11 => "بهمن",
|
||||
12 => "اسفند",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
}
|
||||
@@ -83,4 +83,9 @@ public class DiscountProduct : BaseAuditableEntity
|
||||
/// دستهبندیهای این محصول
|
||||
/// </summary>
|
||||
public virtual ICollection<DiscountProductCategory> ProductCategories { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// تصاویر گالری محصول
|
||||
/// </summary>
|
||||
public virtual ICollection<DiscountProductImage> Images { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace CMSMicroservice.Domain.Entities.DiscountShop;
|
||||
|
||||
/// <summary>
|
||||
/// تصویر گالری محصول تخفیفی
|
||||
/// </summary>
|
||||
public class DiscountProductImage : BaseAuditableEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه محصول
|
||||
/// </summary>
|
||||
public long DiscountProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// محصول
|
||||
/// </summary>
|
||||
public virtual DiscountProduct DiscountProduct { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// عنوان تصویر
|
||||
/// </summary>
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// متن جایگزین (Alt)
|
||||
/// </summary>
|
||||
public string? AltText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// مسیر تصویر اصلی
|
||||
/// </summary>
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// مسیر تصویر کوچک
|
||||
/// </summary>
|
||||
public string? ThumbnailPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ترتیب نمایش
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// آیا فعال است؟
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
@@ -110,6 +110,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext
|
||||
public DbSet<DiscountProduct> DiscountProducts => Set<DiscountProduct>();
|
||||
public DbSet<DiscountCategory> DiscountCategories => Set<DiscountCategory>();
|
||||
public DbSet<DiscountProductCategory> DiscountProductCategories => Set<DiscountProductCategory>();
|
||||
public DbSet<DiscountProductImage> DiscountProductImages => Set<DiscountProductImage>();
|
||||
public DbSet<DiscountShoppingCart> DiscountShoppingCarts => Set<DiscountShoppingCart>();
|
||||
public DbSet<DiscountOrder> DiscountOrders => Set<DiscountOrder>();
|
||||
public DbSet<DiscountOrderDetail> DiscountOrderDetails => Set<DiscountOrderDetail>();
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Persistence.Configurations.DiscountShop;
|
||||
|
||||
public class DiscountProductImageConfiguration : IEntityTypeConfiguration<DiscountProductImage>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<DiscountProductImage> builder)
|
||||
{
|
||||
builder.ToTable("DiscountProductImages");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Title)
|
||||
.HasMaxLength(200);
|
||||
|
||||
builder.Property(x => x.AltText)
|
||||
.HasMaxLength(500);
|
||||
|
||||
builder.Property(x => x.ImagePath)
|
||||
.IsRequired()
|
||||
.HasMaxLength(500);
|
||||
|
||||
builder.Property(x => x.ThumbnailPath)
|
||||
.HasMaxLength(500);
|
||||
|
||||
builder.HasOne(x => x.DiscountProduct)
|
||||
.WithMany(p => p.Images)
|
||||
.HasForeignKey(x => x.DiscountProductId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.HasIndex(x => x.DiscountProductId);
|
||||
builder.HasIndex(x => new { x.DiscountProductId, x.SortOrder });
|
||||
}
|
||||
}
|
||||
+3632
File diff suppressed because it is too large
Load Diff
+67
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDiscountProductImages : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DiscountProductImages",
|
||||
schema: "CMS",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DiscountProductId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Title = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||
AltText = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
ImagePath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
ThumbnailPath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
SortOrder = table.Column<int>(type: "int", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||
Created = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
LastModified = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifiedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DiscountProductImages", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DiscountProductImages_DiscountProducts_DiscountProductId",
|
||||
column: x => x.DiscountProductId,
|
||||
principalSchema: "CMS",
|
||||
principalTable: "DiscountProducts",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DiscountProductImages_DiscountProductId",
|
||||
schema: "CMS",
|
||||
table: "DiscountProductImages",
|
||||
column: "DiscountProductId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DiscountProductImages_DiscountProductId_SortOrder",
|
||||
schema: "CMS",
|
||||
table: "DiscountProductImages",
|
||||
columns: new[] { "DiscountProductId", "SortOrder" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DiscountProductImages",
|
||||
schema: "CMS");
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -902,6 +902,64 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("DiscountProductCategories", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("AltText")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long>("DiscountProductId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ImagePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("LastModifiedBy")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ThumbnailPath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DiscountProductId");
|
||||
|
||||
b.HasIndex("DiscountProductId", "SortOrder");
|
||||
|
||||
b.ToTable("DiscountProductImages", "CMS");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
@@ -3026,6 +3084,17 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
b.Navigation("Product");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct")
|
||||
.WithMany("Images")
|
||||
.HasForeignKey("DiscountProductId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DiscountProduct");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b =>
|
||||
{
|
||||
b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product")
|
||||
@@ -3439,6 +3508,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
|
||||
|
||||
modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b =>
|
||||
{
|
||||
b.Navigation("Images");
|
||||
|
||||
b.Navigation("OrderDetails");
|
||||
|
||||
b.Navigation("ProductCategories");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>0.0.162</Version>
|
||||
<Version>0.0.164</Version>
|
||||
<DebugType>None</DebugType>
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
|
||||
@@ -40,6 +40,20 @@ service DiscountOrderContract
|
||||
get: "/GetUserOrders"
|
||||
};
|
||||
};
|
||||
|
||||
// Admin: Get All Discount Orders with filters
|
||||
rpc GetAllDiscountOrders(GetAllDiscountOrdersRequest) returns (GetAllDiscountOrdersResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/GetAllDiscountOrders"
|
||||
};
|
||||
};
|
||||
|
||||
// Admin: Get Sales Report
|
||||
rpc GetDiscountSalesReport(GetDiscountSalesReportRequest) returns (GetDiscountSalesReportResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/GetDiscountSalesReport"
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Place Order (Initial Step - Create Order)
|
||||
@@ -175,3 +189,118 @@ message OrderSummaryDto
|
||||
int32 items_count = 9;
|
||||
google.protobuf.Timestamp created = 10;
|
||||
}
|
||||
|
||||
// ===== Admin: Get All Discount Orders =====
|
||||
|
||||
message GetAllDiscountOrdersRequest
|
||||
{
|
||||
google.protobuf.Int64Value user_id = 1;
|
||||
google.protobuf.Int32Value payment_status = 2;
|
||||
google.protobuf.Int32Value delivery_status = 3;
|
||||
google.protobuf.StringValue user_mobile = 4;
|
||||
google.protobuf.StringValue tracking_code = 5;
|
||||
google.protobuf.Timestamp from_date = 6;
|
||||
google.protobuf.Timestamp to_date = 7;
|
||||
google.protobuf.Int64Value min_amount = 8;
|
||||
google.protobuf.Int64Value max_amount = 9;
|
||||
int32 page_number = 10;
|
||||
int32 page_size = 11;
|
||||
}
|
||||
|
||||
message GetAllDiscountOrdersResponse
|
||||
{
|
||||
messages.MetaData meta_data = 1;
|
||||
repeated AdminOrderDto models = 2;
|
||||
}
|
||||
|
||||
message AdminOrderDto
|
||||
{
|
||||
int64 id = 1;
|
||||
int64 user_id = 2;
|
||||
string user_full_name = 3;
|
||||
string user_mobile = 4;
|
||||
int64 total_amount = 5;
|
||||
int64 discount_balance_used = 6;
|
||||
int64 gateway_amount_paid = 7;
|
||||
int64 vat_amount = 8;
|
||||
PaymentStatus payment_status = 9;
|
||||
google.protobuf.Timestamp payment_date = 10;
|
||||
DeliveryStatus delivery_status = 11;
|
||||
google.protobuf.Timestamp delivery_date = 12;
|
||||
google.protobuf.StringValue shipping_address = 13;
|
||||
google.protobuf.StringValue receiver_name = 14;
|
||||
google.protobuf.StringValue receiver_mobile = 15;
|
||||
google.protobuf.StringValue tracking_code = 16;
|
||||
google.protobuf.StringValue admin_note = 17;
|
||||
google.protobuf.Timestamp created = 18;
|
||||
google.protobuf.Timestamp last_modified = 19;
|
||||
int32 items_count = 20;
|
||||
}
|
||||
|
||||
enum PaymentStatus
|
||||
{
|
||||
PAYMENT_PENDING = 0;
|
||||
PAYMENT_COMPLETED = 1;
|
||||
PAYMENT_FAILED = 2;
|
||||
PAYMENT_REFUNDED = 3;
|
||||
}
|
||||
|
||||
// ===== Sales Report Messages =====
|
||||
|
||||
enum SalesReportType
|
||||
{
|
||||
REPORT_SUMMARY = 0;
|
||||
REPORT_DAILY = 1;
|
||||
REPORT_WEEKLY = 2;
|
||||
REPORT_MONTHLY = 3;
|
||||
}
|
||||
|
||||
message GetDiscountSalesReportRequest
|
||||
{
|
||||
google.protobuf.Timestamp from_date = 1;
|
||||
google.protobuf.Timestamp to_date = 2;
|
||||
SalesReportType report_type = 3;
|
||||
}
|
||||
|
||||
message GetDiscountSalesReportResponse
|
||||
{
|
||||
SalesSummary summary = 1;
|
||||
repeated SalesPeriodDto periods = 2;
|
||||
repeated TopSellingProductDto top_products = 3;
|
||||
}
|
||||
|
||||
message SalesSummary
|
||||
{
|
||||
int32 total_orders = 1;
|
||||
int32 completed_orders = 2;
|
||||
int32 pending_orders = 3;
|
||||
int32 cancelled_orders = 4;
|
||||
int64 total_sales_amount = 5;
|
||||
int64 total_discount_used = 6;
|
||||
int64 total_gateway_paid = 7;
|
||||
int64 total_vat_amount = 8;
|
||||
int64 average_order_value = 9;
|
||||
int32 unique_customers = 10;
|
||||
int32 total_products_sold = 11;
|
||||
}
|
||||
|
||||
message SalesPeriodDto
|
||||
{
|
||||
string period_label = 1;
|
||||
google.protobuf.Timestamp period_start = 2;
|
||||
google.protobuf.Timestamp period_end = 3;
|
||||
int32 orders_count = 4;
|
||||
int64 total_amount = 5;
|
||||
int64 discount_used = 6;
|
||||
int64 gateway_paid = 7;
|
||||
}
|
||||
|
||||
message TopSellingProductDto
|
||||
{
|
||||
int64 product_id = 1;
|
||||
string product_title = 2;
|
||||
google.protobuf.StringValue image_path = 3;
|
||||
int32 quantity_sold = 4;
|
||||
int64 total_revenue = 5;
|
||||
int32 orders_count = 6;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,37 @@ service DiscountProductContract
|
||||
get: "/GetDiscountProducts"
|
||||
};
|
||||
};
|
||||
|
||||
// Product Image Gallery Operations
|
||||
rpc AddDiscountProductImage(AddDiscountProductImageRequest) returns (AddDiscountProductImageResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/AddDiscountProductImage"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc UpdateDiscountProductImage(UpdateDiscountProductImageRequest) returns (google.protobuf.Empty){
|
||||
option (google.api.http) = {
|
||||
put: "/UpdateDiscountProductImage"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc DeleteDiscountProductImage(DeleteDiscountProductImageRequest) returns (google.protobuf.Empty){
|
||||
option (google.api.http) = {
|
||||
delete: "/DeleteDiscountProductImage"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc ReorderDiscountProductImages(ReorderDiscountProductImagesRequest) returns (google.protobuf.Empty){
|
||||
option (google.api.http) = {
|
||||
put: "/ReorderDiscountProductImages"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc GetDiscountProductImages(GetDiscountProductImagesRequest) returns (GetDiscountProductImagesResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/GetDiscountProductImages"
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Create Product
|
||||
@@ -150,3 +181,68 @@ message DiscountProductDto
|
||||
bool is_active = 10;
|
||||
google.protobuf.Timestamp created = 11;
|
||||
}
|
||||
|
||||
// ===== Product Image Gallery Messages =====
|
||||
|
||||
// Add Product Image
|
||||
message AddDiscountProductImageRequest
|
||||
{
|
||||
int64 discount_product_id = 1;
|
||||
string image_path = 2;
|
||||
string thumbnail_path = 3;
|
||||
google.protobuf.StringValue title = 4;
|
||||
google.protobuf.StringValue alt_text = 5;
|
||||
}
|
||||
|
||||
message AddDiscountProductImageResponse
|
||||
{
|
||||
int64 image_id = 1;
|
||||
}
|
||||
|
||||
// Update Product Image
|
||||
message UpdateDiscountProductImageRequest
|
||||
{
|
||||
int64 id = 1;
|
||||
string image_path = 2;
|
||||
string thumbnail_path = 3;
|
||||
google.protobuf.StringValue title = 4;
|
||||
google.protobuf.StringValue alt_text = 5;
|
||||
bool is_active = 6;
|
||||
}
|
||||
|
||||
// Delete Product Image
|
||||
message DeleteDiscountProductImageRequest
|
||||
{
|
||||
int64 id = 1;
|
||||
}
|
||||
|
||||
// Reorder Product Images
|
||||
message ReorderDiscountProductImagesRequest
|
||||
{
|
||||
int64 discount_product_id = 1;
|
||||
repeated int64 image_ids = 2;
|
||||
}
|
||||
|
||||
// Get Product Images
|
||||
message GetDiscountProductImagesRequest
|
||||
{
|
||||
int64 discount_product_id = 1;
|
||||
bool only_active = 2;
|
||||
}
|
||||
|
||||
message GetDiscountProductImagesResponse
|
||||
{
|
||||
repeated DiscountProductImageDto images = 1;
|
||||
}
|
||||
|
||||
message DiscountProductImageDto
|
||||
{
|
||||
int64 id = 1;
|
||||
int64 discount_product_id = 2;
|
||||
string image_path = 3;
|
||||
string thumbnail_path = 4;
|
||||
google.protobuf.StringValue title = 5;
|
||||
google.protobuf.StringValue alt_text = 6;
|
||||
int32 sort_order = 7;
|
||||
bool is_active = 8;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetOrderById;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserOrders;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetAllDiscountOrders;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountSalesReport;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
|
||||
@@ -41,4 +43,14 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetUserOrdersRequest, GetUserOrdersQuery, GetUserOrdersResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<GetAllDiscountOrdersResponse> GetAllDiscountOrders(GetAllDiscountOrdersRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetAllDiscountOrdersRequest, GetAllDiscountOrdersQuery, GetAllDiscountOrdersResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<GetDiscountSalesReportResponse> GetDiscountSalesReport(GetDiscountSalesReportRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetDiscountSalesReportRequest, GetDiscountSalesReportQuery, GetDiscountSalesReportResponse>(request, context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,13 @@ using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProduct;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProduct;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProduct;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.AddDiscountProductImage;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProductImage;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProductImage;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.ReorderDiscountProductImages;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductById;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProducts;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductImages;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
|
||||
@@ -41,4 +46,30 @@ public class DiscountProductService : DiscountProductContract.DiscountProductCon
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetDiscountProductsRequest, GetDiscountProductsQuery, GetDiscountProductsResponse>(request, context);
|
||||
}
|
||||
|
||||
// Product Image Gallery Operations
|
||||
public override async Task<AddDiscountProductImageResponse> AddDiscountProductImage(AddDiscountProductImageRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<AddDiscountProductImageRequest, AddDiscountProductImageCommand, AddDiscountProductImageResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<Empty> UpdateDiscountProductImage(UpdateDiscountProductImageRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<UpdateDiscountProductImageRequest, UpdateDiscountProductImageCommand>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<Empty> DeleteDiscountProductImage(DeleteDiscountProductImageRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<DeleteDiscountProductImageRequest, DeleteDiscountProductImageCommand>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<Empty> ReorderDiscountProductImages(ReorderDiscountProductImagesRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<ReorderDiscountProductImagesRequest, ReorderDiscountProductImagesCommand>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<GetDiscountProductImagesResponse> GetDiscountProductImages(GetDiscountProductImagesRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetDiscountProductImagesRequest, GetDiscountProductImagesQuery, GetDiscountProductImagesResponse>(request, context);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user