From 4f400eabc51506fc4b13eee9d6bc43dde175a61a Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 4 Dec 2025 02:41:19 +0330 Subject: [PATCH] feat: Implement Discount Product and Shopping Cart functionalities - Added UpdateDiscountProductCommandValidator for validating discount product updates. - Created GetDiscountProductByIdQuery and its handler for retrieving discount product details by ID. - Implemented GetDiscountProductsQuery and handler for fetching a list of discount products with filtering options. - Developed AddToCartCommand and handler for adding products to the shopping cart. - Implemented ClearCartCommand and handler for clearing the shopping cart. - Created RemoveFromCartCommand and handler for removing items from the cart. - Added UpdateCartItemCountCommand and handler for updating the quantity of items in the cart. - Developed GetUserCartQuery and handler for retrieving the user's shopping cart details. - Implemented Product Tag functionalities including assigning tags to products, creating, updating, and deleting tags. - Added queries for fetching all tags and products by tag. --- .../GetWithdrawalReportsQuery.cs | 181 ++++++++++++++++++ .../GetWithdrawalReportsQueryHandler.cs | 78 ++++++++ .../Interfaces/IApplicationContractContext.cs | 24 ++- .../CreateDiscountCategoryCommand.cs | 37 ++++ .../CreateDiscountCategoryCommandHandler.cs | 53 +++++ .../CreateDiscountCategoryCommandValidator.cs | 19 ++ .../CreateDiscountCategoryResponseDto.cs | 7 + .../DeleteDiscountCategoryCommand.cs | 7 + .../DeleteDiscountCategoryCommandHandler.cs | 22 +++ .../DeleteDiscountCategoryCommandValidator.cs | 10 + .../UpdateDiscountCategoryCommand.cs | 40 ++++ .../UpdateDiscountCategoryCommandHandler.cs | 51 +++++ .../UpdateDiscountCategoryCommandValidator.cs | 26 +++ .../GetDiscountCategoriesQuery.cs | 10 + .../GetDiscountCategoriesQueryHandler.cs | 50 +++++ .../GetDiscountCategoriesResponseDto.cs | 24 +++ .../CompleteOrderPaymentCommand.cs | 13 ++ .../CompleteOrderPaymentCommandHandler.cs | 27 +++ .../CompleteOrderPaymentCommandValidator.cs | 16 ++ .../Commands/PlaceOrder/PlaceOrderCommand.cs | 16 ++ .../PlaceOrder/PlaceOrderCommandHandler.cs | 34 ++++ .../PlaceOrder/PlaceOrderCommandValidator.cs | 23 +++ .../PlaceOrder/PlaceOrderResponseDto.cs | 16 ++ .../UpdateOrderStatusCommand.cs | 13 ++ .../UpdateOrderStatusCommandHandler.cs | 27 +++ .../UpdateOrderStatusCommandValidator.cs | 13 ++ .../Queries/GetOrderById/GetOrderByIdQuery.cs | 7 + .../GetOrderById/GetOrderByIdQueryHandler.cs | 58 ++++++ .../GetOrderByIdQueryValidator.cs | 10 + .../GetOrderById/GetOrderByIdResponseDto.cs | 44 +++++ .../GetUserOrders/GetUserOrdersQuery.cs | 16 ++ .../GetUserOrdersQueryHandler.cs | 54 ++++++ .../GetUserOrdersQueryValidator.cs | 21 ++ .../GetUserOrders/GetUserOrdersResponseDto.cs | 21 ++ .../CreateDiscountProductCommand.cs | 49 +++++ .../CreateDiscountProductCommandHandler.cs | 70 +++++++ .../CreateDiscountProductCommandValidator.cs | 30 +++ .../CreateDiscountProductResponseDto.cs | 7 + .../DeleteDiscountProductCommand.cs | 7 + .../DeleteDiscountProductCommandHandler.cs | 22 +++ .../DeleteDiscountProductCommandValidator.cs | 10 + .../UpdateDiscountProductCommand.cs | 49 +++++ .../UpdateDiscountProductCommandHandler.cs | 67 +++++++ .../UpdateDiscountProductCommandValidator.cs | 30 +++ .../GetDiscountProductByIdQuery.cs | 7 + .../GetDiscountProductByIdQueryHandler.cs | 43 +++++ .../GetDiscountProductByIdQueryValidator.cs | 10 + .../GetDiscountProductByIdResponseDto.cs | 26 +++ .../GetDiscountProductsQuery.cs | 28 +++ .../GetDiscountProductsQueryHandler.cs | 71 +++++++ .../GetDiscountProductsQueryValidator.cs | 24 +++ .../GetDiscountProductsResponseDto.cs | 24 +++ .../Commands/AddToCart/AddToCartCommand.cs | 13 ++ .../AddToCart/AddToCartCommandHandler.cs | 27 +++ .../AddToCart/AddToCartCommandValidator.cs | 16 ++ .../Commands/ClearCart/ClearCartCommand.cs | 7 + .../ClearCart/ClearCartCommandHandler.cs | 22 +++ .../ClearCart/ClearCartCommandValidator.cs | 10 + .../RemoveFromCart/RemoveFromCartCommand.cs | 10 + .../RemoveFromCartCommandHandler.cs | 26 +++ .../RemoveFromCartCommandValidator.cs | 13 ++ .../UpdateCartItemCountCommand.cs | 13 ++ .../UpdateCartItemCountCommandHandler.cs | 27 +++ .../UpdateCartItemCountCommandValidator.cs | 16 ++ .../Queries/GetUserCart/GetUserCartQuery.cs | 7 + .../GetUserCart/GetUserCartQueryHandler.cs | 41 ++++ .../GetUserCart/GetUserCartQueryValidator.cs | 10 + .../GetUserCart/GetUserCartResponseDto.cs | 24 +++ .../AssignTagToProductCommand.cs | 9 + .../AssignTagToProductCommandHandler.cs | 26 +++ .../AddProductImageCommandHandler.cs | 4 +- .../RemoveProductImageCommandHandler.cs | 4 +- .../UpdateCategoryProductsCommandHandler.cs | 16 +- .../UpdateProductCategoriesCommandHandler.cs | 16 +- .../GetCategoriesQueryHandler.cs | 8 +- .../GetProductGalleryQueryHandler.cs | 8 +- .../GetProductsForCategoryQueryHandler.cs | 8 +- .../Commands/CreateTag/CreateTagCommand.cs | 12 ++ .../CreateTag/CreateTagCommandHandler.cs | 29 +++ .../Commands/DeleteTag/DeleteTagCommand.cs | 6 + .../DeleteTag/DeleteTagCommandHandler.cs | 25 +++ .../Commands/UpdateTag/UpdateTagCommand.cs | 13 ++ .../UpdateTag/UpdateTagCommandHandler.cs | 30 +++ .../Queries/GetAllTags/GetAllTagsQuery.cs | 11 ++ .../GetAllTags/GetAllTagsQueryHandler.cs | 41 ++++ .../GetProductsByTag/GetProductsByTagQuery.cs | 8 + .../GetProductsByTagQueryHandler.cs | 22 +++ .../TagCQ/Queries/GetTag/GetTagQuery.cs | 8 + .../Queries/GetTag/GetTagQueryHandler.cs | 22 +++ .../BackOffice.BFF.Domain.csproj | 6 +- .../Services/ApplicationContractContext.cs | 24 ++- .../Protos/commission.proto | 46 +++++ 92 files changed, 2285 insertions(+), 41 deletions(-) create mode 100644 src/BackOffice.BFF.Application/CommissionCQ/Queries/GetWithdrawalReports/GetWithdrawalReportsQuery.cs create mode 100644 src/BackOffice.BFF.Application/CommissionCQ/Queries/GetWithdrawalReports/GetWithdrawalReportsQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryResponseDto.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQuery.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesResponseDto.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderResponseDto.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQuery.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQueryValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdResponseDto.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQuery.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQueryValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersResponseDto.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductResponseDto.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQuery.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQueryValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdResponseDto.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQuery.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsResponseDto.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommand.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommandValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQuery.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQueryValidator.cs create mode 100644 src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartResponseDto.cs create mode 100644 src/BackOffice.BFF.Application/ProductTagCQ/Commands/AssignTagToProduct/AssignTagToProductCommand.cs create mode 100644 src/BackOffice.BFF.Application/ProductTagCQ/Commands/AssignTagToProduct/AssignTagToProductCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Commands/CreateTag/CreateTagCommand.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Commands/CreateTag/CreateTagCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Commands/DeleteTag/DeleteTagCommand.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Commands/DeleteTag/DeleteTagCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Commands/UpdateTag/UpdateTagCommand.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Commands/UpdateTag/UpdateTagCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Queries/GetAllTags/GetAllTagsQuery.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Queries/GetAllTags/GetAllTagsQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Queries/GetProductsByTag/GetProductsByTagQuery.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Queries/GetProductsByTag/GetProductsByTagQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Queries/GetTag/GetTagQuery.cs create mode 100644 src/BackOffice.BFF.Application/TagCQ/Queries/GetTag/GetTagQueryHandler.cs diff --git a/src/BackOffice.BFF.Application/CommissionCQ/Queries/GetWithdrawalReports/GetWithdrawalReportsQuery.cs b/src/BackOffice.BFF.Application/CommissionCQ/Queries/GetWithdrawalReports/GetWithdrawalReportsQuery.cs new file mode 100644 index 0000000..a79e7d9 --- /dev/null +++ b/src/BackOffice.BFF.Application/CommissionCQ/Queries/GetWithdrawalReports/GetWithdrawalReportsQuery.cs @@ -0,0 +1,181 @@ +namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetWithdrawalReports; + +/// +/// گزارش برداشت‌ها بر اساس بازه زمانی و دسته‌بندی +/// +public record GetWithdrawalReportsQuery : IRequest +{ + /// + /// تاریخ شروع (اختیاری - پیش‌فرض: 30 روز قبل) + /// + public DateTime? StartDate { get; init; } + + /// + /// تاریخ پایان (اختیاری - پیش‌فرض: امروز) + /// + public DateTime? EndDate { get; init; } + + /// + /// نوع دوره گزارش: 1=روزانه, 2=هفتگی, 3=ماهانه + /// + public ReportPeriodType PeriodType { get; init; } = ReportPeriodType.Daily; + + /// + /// فیلتر وضعیت (اختیاری) + /// + public int? Status { get; init; } + + /// + /// فیلتر شناسه کاربر (اختیاری) + /// + public long? UserId { get; init; } +} + +/// +/// نوع دسته‌بندی گزارش +/// +public enum ReportPeriodType +{ + /// + /// روزانه + /// + Daily = 1, + + /// + /// هفتگی + /// + Weekly = 2, + + /// + /// ماهانه + /// + Monthly = 3 +} + +/// +/// پاسخ گزارش برداشت‌ها +/// +public class GetWithdrawalReportsResponseDto +{ + /// + /// گزارش‌های هر دوره + /// + public List PeriodReports { get; set; } = new(); + + /// + /// خلاصه کل گزارش + /// + public WithdrawalSummaryDto Summary { get; set; } = new(); +} + +/// +/// گزارش یک دوره زمانی +/// +public class PeriodReportDto +{ + /// + /// برچسب دوره (مثلاً "2025-01-15" یا "هفته 3" یا "فروردین 1404") + /// + public string PeriodLabel { get; set; } = string.Empty; + + /// + /// تاریخ شروع دوره + /// + public DateTime StartDate { get; set; } + + /// + /// تاریخ پایان دوره + /// + public DateTime EndDate { get; set; } + + /// + /// تعداد کل درخواست‌ها + /// + public int TotalRequests { get; set; } + + /// + /// تعداد در انتظار + /// + public int PendingCount { get; set; } + + /// + /// تعداد تایید شده + /// + public int ApprovedCount { get; set; } + + /// + /// تعداد رد شده + /// + public int RejectedCount { get; set; } + + /// + /// تعداد تکمیل شده + /// + public int CompletedCount { get; set; } + + /// + /// تعداد ناموفق + /// + public int FailedCount { get; set; } + + /// + /// مبلغ کل (ریال) + /// + public long TotalAmount { get; set; } + + /// + /// مبلغ پرداخت شده (ریال) + /// + public long PaidAmount { get; set; } + + /// + /// مبلغ در انتظار (ریال) + /// + public long PendingAmount { get; set; } +} + +/// +/// خلاصه کل گزارش برداشت‌ها +/// +public class WithdrawalSummaryDto +{ + /// + /// تعداد کل درخواست‌ها + /// + public int TotalRequests { get; set; } + + /// + /// مبلغ کل درخواست‌ها (ریال) + /// + public long TotalAmount { get; set; } + + /// + /// مبلغ کل پرداخت شده (ریال) + /// + public long TotalPaid { get; set; } + + /// + /// مبلغ کل در انتظار (ریال) + /// + public long TotalPending { get; set; } + + /// + /// مبلغ کل رد شده (ریال) + /// + public long TotalRejected { get; set; } + + /// + /// میانگین مبلغ درخواست (ریال) + /// + public long AverageAmount { get; set; } + + /// + /// تعداد کاربران منحصر به فرد + /// + public int UniqueUsers { get; set; } + + /// + /// درصد موفقیت (0-100) + /// + public decimal SuccessRate { get; set; } +} diff --git a/src/BackOffice.BFF.Application/CommissionCQ/Queries/GetWithdrawalReports/GetWithdrawalReportsQueryHandler.cs b/src/BackOffice.BFF.Application/CommissionCQ/Queries/GetWithdrawalReports/GetWithdrawalReportsQueryHandler.cs new file mode 100644 index 0000000..8f202f3 --- /dev/null +++ b/src/BackOffice.BFF.Application/CommissionCQ/Queries/GetWithdrawalReports/GetWithdrawalReportsQueryHandler.cs @@ -0,0 +1,78 @@ +using BackOffice.BFF.Commission.Protobuf; +using Google.Protobuf.WellKnownTypes; + +namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetWithdrawalReports; + +public class GetWithdrawalReportsQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetWithdrawalReportsQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetWithdrawalReportsQuery request, CancellationToken cancellationToken) + { + var grpcRequest = new GetWithdrawalReportsRequest + { + PeriodType = (int)request.PeriodType + }; + + // تنظیم بازه زمانی + if (request.StartDate.HasValue) + { + grpcRequest.StartDate = Timestamp.FromDateTime(request.StartDate.Value.ToUniversalTime()); + } + + if (request.EndDate.HasValue) + { + grpcRequest.EndDate = Timestamp.FromDateTime(request.EndDate.Value.ToUniversalTime()); + } + + // فیلتر وضعیت + if (request.Status.HasValue) + { + grpcRequest.Status = request.Status.Value; + } + + // فیلتر کاربر + if (request.UserId.HasValue) + { + grpcRequest.UserId = request.UserId.Value; + } + + var response = await _context.Commissions.GetWithdrawalReportsAsync(grpcRequest, cancellationToken: cancellationToken); + + // تبدیل به DTO + return new GetWithdrawalReportsResponseDto + { + PeriodReports = response.PeriodReports.Select(p => new PeriodReportDto + { + PeriodLabel = p.PeriodLabel, + StartDate = p.StartDate.ToDateTime(), + EndDate = p.EndDate.ToDateTime(), + TotalRequests = p.TotalRequests, + PendingCount = p.PendingCount, + ApprovedCount = p.ApprovedCount, + RejectedCount = p.RejectedCount, + CompletedCount = p.CompletedCount, + FailedCount = p.FailedCount, + TotalAmount = p.TotalAmount, + PaidAmount = p.PaidAmount, + PendingAmount = p.PendingAmount + }).ToList(), + Summary = new WithdrawalSummaryDto + { + TotalRequests = response.Summary.TotalRequests, + TotalAmount = response.Summary.TotalAmount, + TotalPaid = response.Summary.TotalPaid, + TotalPending = response.Summary.TotalPending, + TotalRejected = response.Summary.TotalRejected, + AverageAmount = response.Summary.AverageAmount, + UniqueUsers = response.Summary.UniqueUsers, + SuccessRate = (decimal)response.Summary.SuccessRate + } + }; + } +} diff --git a/src/BackOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs b/src/BackOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs index 15dad85..326d191 100644 --- a/src/BackOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs +++ b/src/BackOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs @@ -6,14 +6,20 @@ using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserRole; using CMSMicroservice.Protobuf.Protos.Products; using CMSMicroservice.Protobuf.Protos.ProductImages; -using CMSMicroservice.Protobuf.Protos.ProductGallerys; +using CMSMicroservice.Protobuf.Protos.ProductGalleries; using CMSMicroservice.Protobuf.Protos.Category; -using CMSMicroservice.Protobuf.Protos.PruductCategory; +using CMSMicroservice.Protobuf.Protos.ProductCategory; using BackOffice.BFF.Commission.Protobuf; using BackOffice.BFF.NetworkMembership.Protobuf; using BackOffice.BFF.ClubMembership.Protobuf; using BackOffice.BFF.Configuration.Protobuf; using FMSMicroservice.Protobuf.Protos.FileInfo; +using CMSMicroservice.Protobuf.Protos.DiscountProduct; +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; +using CMSMicroservice.Protobuf.Protos.DiscountOrder; +using CMSMicroservice.Protobuf.Protos.Tag; +using CMSMicroservice.Protobuf.Protos.ProductTag; namespace BackOffice.BFF.Application.Common.Interfaces; @@ -26,10 +32,10 @@ public interface IApplicationContractContext PackageContract.PackageContractClient Packages { get; } ProductsContract.ProductsContractClient Products { get; } ProductImagesContract.ProductImagesContractClient ProductImages { get; } - ProductGallerysContract.ProductGallerysContractClient ProductGallerys { get; } + ProductGalleriesContract.ProductGalleriesContractClient ProductGalleries { get; } RoleContract.RoleContractClient Roles { get; } CategoryContract.CategoryContractClient Categories { get; } - PruductCategoryContract.PruductCategoryContractClient ProductCategories { get; } + ProductCategoryContract.ProductCategoryContractClient ProductCategories { get; } UserAddressContract.UserAddressContractClient UserAddress { get; } UserContract.UserContractClient Users { get; } UserOrderContract.UserOrderContractClient UserOrders { get; } @@ -40,6 +46,16 @@ public interface IApplicationContractContext NetworkMembershipContract.NetworkMembershipContractClient NetworkMemberships { get; } ClubMembershipContract.ClubMembershipContractClient ClubMemberships { get; } ConfigurationContract.ConfigurationContractClient Configurations { get; } + + // Discount Shop System (Phase 9) + DiscountProductContract.DiscountProductContractClient DiscountProducts { get; } + DiscountCategoryContract.DiscountCategoryContractClient DiscountCategories { get; } + DiscountShoppingCartContract.DiscountShoppingCartContractClient DiscountShoppingCarts { get; } + DiscountOrderContract.DiscountOrderContractClient DiscountOrders { get; } + + // Tag Management System + TagContract.TagContractClient Tags { get; } + ProductTagContract.ProductTagContractClient ProductTags { get; } #endregion } diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommand.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommand.cs new file mode 100644 index 0000000..52ef2ca --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommand.cs @@ -0,0 +1,37 @@ +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.CreateDiscountCategory; + +public record CreateDiscountCategoryCommand : IRequest +{ + /// نام دسته‌بندی (انگلیسی) + public string Name { get; init; } + + /// عنوان دسته‌بندی (فارسی) + public string Title { get; init; } + + /// توضیحات دسته‌بندی + public string Description { get; init; } + + /// شناسه دسته‌بندی والد (null برای دسته اصلی) + public long? ParentCategoryId { get; init; } + + /// ترتیب نمایش + public int SortOrder { get; init; } + + /// وضعیت فعال/غیرفعال + public bool IsActive { get; init; } + + /// فایل تصویر دسته‌بندی + public DiscountCategoryFileModel ImageFile { get; init; } +} + +public class DiscountCategoryFileModel +{ + /// فایل + public byte[] File { get; set; } + + /// نام فایل + public string FileName { get; set; } + + /// نوع فایل + public string Mime { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommandHandler.cs new file mode 100644 index 0000000..d2d28cf --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommandHandler.cs @@ -0,0 +1,53 @@ +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; + +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.CreateDiscountCategory; + +public class CreateDiscountCategoryCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CreateDiscountCategoryCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CreateDiscountCategoryCommand request, CancellationToken cancellationToken) + { + var createRequest = new CreateDiscountCategoryRequest + { + Name = request.Name, + Title = request.Title, + Description = request.Description ?? string.Empty, + SortOrder = request.SortOrder, + IsActive = request.IsActive + }; + + if (request.ParentCategoryId.HasValue) + createRequest.ParentCategoryId = new Int64Value { Value = request.ParentCategoryId.Value }; + + // Upload Image + if (request.ImageFile != null && request.ImageFile.File != null && request.ImageFile.File.Length > 0) + { + var imageFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new() + { + Directory = "Images/DiscountShop/Categories", + IsBase64 = false, + MIME = request.ImageFile.Mime, + FileName = request.ImageFile.FileName, + File = ByteString.CopyFrom(request.ImageFile.File) + }, cancellationToken: cancellationToken); + + if (imageFileInfo != null && !string.IsNullOrWhiteSpace(imageFileInfo.File)) + createRequest.ImagePath = imageFileInfo.File; + } + + var response = await _context.DiscountCategories.CreateDiscountCategoryAsync(createRequest, cancellationToken: cancellationToken); + + return new CreateDiscountCategoryResponseDto + { + CategoryId = response.CategoryId + }; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommandValidator.cs new file mode 100644 index 0000000..544c071 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryCommandValidator.cs @@ -0,0 +1,19 @@ +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.CreateDiscountCategory; + +public class CreateDiscountCategoryCommandValidator : AbstractValidator +{ + public CreateDiscountCategoryCommandValidator() + { + RuleFor(x => x.Name) + .NotEmpty().WithMessage("نام دسته‌بندی الزامی است") + .MaximumLength(100).WithMessage("نام دسته‌بندی نباید بیشتر از 100 کاراکتر باشد") + .Matches("^[a-zA-Z0-9_-]+$").WithMessage("نام دسته‌بندی فقط باید شامل حروف انگلیسی، اعداد، خط تیره و زیرخط باشد"); + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان دسته‌بندی الزامی است") + .MaximumLength(200).WithMessage("عنوان دسته‌بندی نباید بیشتر از 200 کاراکتر باشد"); + + RuleFor(x => x.Description) + .MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryResponseDto.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryResponseDto.cs new file mode 100644 index 0000000..75e85a3 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/CreateDiscountCategory/CreateDiscountCategoryResponseDto.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.CreateDiscountCategory; + +public class CreateDiscountCategoryResponseDto +{ + /// شناسه دسته‌بندی ایجاد شده + public long CategoryId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommand.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommand.cs new file mode 100644 index 0000000..02c588c --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommand.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.DeleteDiscountCategory; + +public record DeleteDiscountCategoryCommand : IRequest +{ + /// شناسه دسته‌بندی + public long CategoryId { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommandHandler.cs new file mode 100644 index 0000000..ab7eefd --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommandHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Protobuf.Protos.DiscountCategory; + +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.DeleteDiscountCategory; + +public class DeleteDiscountCategoryCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public DeleteDiscountCategoryCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(DeleteDiscountCategoryCommand request, CancellationToken cancellationToken) + { + await _context.DiscountCategories.DeleteDiscountCategoryAsync( + new DeleteDiscountCategoryRequest { CategoryId = request.CategoryId }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommandValidator.cs new file mode 100644 index 0000000..a800530 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/DeleteDiscountCategory/DeleteDiscountCategoryCommandValidator.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.DeleteDiscountCategory; + +public class DeleteDiscountCategoryCommandValidator : AbstractValidator +{ + public DeleteDiscountCategoryCommandValidator() + { + RuleFor(x => x.CategoryId) + .GreaterThan(0).WithMessage("شناسه دسته‌بندی نامعتبر است"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommand.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommand.cs new file mode 100644 index 0000000..7e47b09 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommand.cs @@ -0,0 +1,40 @@ +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.UpdateDiscountCategory; + +public record UpdateDiscountCategoryCommand : IRequest +{ + /// شناسه دسته‌بندی + public long CategoryId { get; init; } + + /// نام دسته‌بندی (انگلیسی) + public string Name { get; init; } + + /// عنوان دسته‌بندی (فارسی) + public string Title { get; init; } + + /// توضیحات دسته‌بندی + public string Description { get; init; } + + /// شناسه دسته‌بندی والد (null برای دسته اصلی) + public long? ParentCategoryId { get; init; } + + /// ترتیب نمایش + public int SortOrder { get; init; } + + /// وضعیت فعال/غیرفعال + public bool IsActive { get; init; } + + /// فایل تصویر دسته‌بندی (اختیاری - برای تغییر تصویر) + public DiscountCategoryFileModel ImageFile { get; init; } +} + +public class DiscountCategoryFileModel +{ + /// فایل + public byte[] File { get; set; } + + /// نام فایل + public string FileName { get; set; } + + /// نوع فایل + public string Mime { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommandHandler.cs new file mode 100644 index 0000000..42b2fd2 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommandHandler.cs @@ -0,0 +1,51 @@ +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; + +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.UpdateDiscountCategory; + +public class UpdateDiscountCategoryCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateDiscountCategoryCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateDiscountCategoryCommand request, CancellationToken cancellationToken) + { + var updateRequest = new UpdateDiscountCategoryRequest + { + CategoryId = request.CategoryId, + Name = request.Name, + Title = request.Title, + Description = request.Description ?? string.Empty, + SortOrder = request.SortOrder, + IsActive = request.IsActive + }; + + if (request.ParentCategoryId.HasValue) + updateRequest.ParentCategoryId = new Int64Value { Value = request.ParentCategoryId.Value }; + + // Upload new Image if provided + if (request.ImageFile != null && request.ImageFile.File != null && request.ImageFile.File.Length > 0) + { + var imageFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new() + { + Directory = "Images/DiscountShop/Categories", + IsBase64 = false, + MIME = request.ImageFile.Mime, + FileName = request.ImageFile.FileName, + File = ByteString.CopyFrom(request.ImageFile.File) + }, cancellationToken: cancellationToken); + + if (imageFileInfo != null && !string.IsNullOrWhiteSpace(imageFileInfo.File)) + updateRequest.ImagePath = imageFileInfo.File; + } + + await _context.DiscountCategories.UpdateDiscountCategoryAsync(updateRequest, cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommandValidator.cs new file mode 100644 index 0000000..4b57a41 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Commands/UpdateDiscountCategory/UpdateDiscountCategoryCommandValidator.cs @@ -0,0 +1,26 @@ +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Commands.UpdateDiscountCategory; + +public class UpdateDiscountCategoryCommandValidator : AbstractValidator +{ + public UpdateDiscountCategoryCommandValidator() + { + RuleFor(x => x.CategoryId) + .GreaterThan(0).WithMessage("شناسه دسته‌بندی نامعتبر است"); + + RuleFor(x => x.Name) + .NotEmpty().WithMessage("نام دسته‌بندی الزامی است") + .MaximumLength(100).WithMessage("نام دسته‌بندی نباید بیشتر از 100 کاراکتر باشد") + .Matches("^[a-zA-Z0-9_-]+$").WithMessage("نام دسته‌بندی فقط باید شامل حروف انگلیسی، اعداد، خط تیره و زیرخط باشد"); + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان دسته‌بندی الزامی است") + .MaximumLength(200).WithMessage("عنوان دسته‌بندی نباید بیشتر از 200 کاراکتر باشد"); + + RuleFor(x => x.Description) + .MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد"); + + RuleFor(x => x.ParentCategoryId) + .NotEqual(x => x.CategoryId).When(x => x.ParentCategoryId.HasValue) + .WithMessage("دسته‌بندی نمی‌تواند والد خودش باشد"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQuery.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQuery.cs new file mode 100644 index 0000000..d68b21b --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQuery.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Queries.GetDiscountCategories; + +public record GetDiscountCategoriesQuery : IRequest +{ + /// فیلتر براساس دسته والد (null برای ریشه) + public long? ParentCategoryId { get; init; } + + /// فقط دسته‌های فعال + public bool? IsActive { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQueryHandler.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQueryHandler.cs new file mode 100644 index 0000000..c805683 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQueryHandler.cs @@ -0,0 +1,50 @@ +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using Google.Protobuf.WellKnownTypes; + +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Queries.GetDiscountCategories; + +public class GetDiscountCategoriesQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetDiscountCategoriesQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetDiscountCategoriesQuery request, CancellationToken cancellationToken) + { + var grpcRequest = new GetDiscountCategoriesRequest(); + + if (request.ParentCategoryId.HasValue) + grpcRequest.ParentCategoryId = new Int64Value { Value = request.ParentCategoryId.Value }; + + if (request.IsActive.HasValue) + grpcRequest.IsActive = new BoolValue { Value = request.IsActive.Value }; + + var response = await _context.DiscountCategories.GetDiscountCategoriesAsync(grpcRequest, cancellationToken: cancellationToken); + + return new GetDiscountCategoriesResponseDto + { + Categories = response.Categories.Select(MapCategory).ToList() + }; + } + + private DiscountCategoryTreeDto MapCategory(DiscountCategoryDto node) + { + return new DiscountCategoryTreeDto + { + Id = node.Id, + Name = node.Name, + Title = node.Title, + Description = node.HasDescription ? node.Description.Value : string.Empty, + ImagePath = node.HasImagePath ? node.ImagePath.Value : string.Empty, + ParentCategoryId = node.HasParentCategoryId ? node.ParentCategoryId.Value : null, + SortOrder = node.SortOrder, + IsActive = node.IsActive, + ProductCount = node.ProductCount, + Created = DateTime.UtcNow, // Proto doesn't have created field + ChildCategories = node.Children.Select(MapCategory).ToList() + }; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesResponseDto.cs b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesResponseDto.cs new file mode 100644 index 0000000..36f8c3e --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountCategoryCQ/Queries/GetDiscountCategories/GetDiscountCategoriesResponseDto.cs @@ -0,0 +1,24 @@ +namespace BackOffice.BFF.Application.DiscountCategoryCQ.Queries.GetDiscountCategories; + +public class GetDiscountCategoriesResponseDto +{ + /// لیست درختی دسته‌بندی‌ها + public List Categories { get; set; } = new(); +} + +public class DiscountCategoryTreeDto +{ + 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 DateTime Created { get; set; } + + /// زیردسته‌های این دسته‌بندی + public List ChildCategories { get; set; } = new(); +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommand.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommand.cs new file mode 100644 index 0000000..0477a8b --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommand.cs @@ -0,0 +1,13 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.CompleteOrderPayment; + +public record CompleteOrderPaymentCommand : IRequest +{ + /// شناسه سفارش + public long OrderId { get; init; } + + /// کد تراکنش پرداخت + public string PaymentTransactionCode { get; init; } + + /// مبلغ پرداخت شده + public long PaidAmount { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs new file mode 100644 index 0000000..0f9f76a --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs @@ -0,0 +1,27 @@ +using CMSMicroservice.Protobuf.Protos.DiscountOrder; + +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.CompleteOrderPayment; + +public class CompleteOrderPaymentCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CompleteOrderPaymentCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CompleteOrderPaymentCommand request, CancellationToken cancellationToken) + { + await _context.DiscountOrders.CompleteOrderPaymentAsync( + new CompleteOrderPaymentRequest + { + OrderId = request.OrderId, + PaymentTransactionCode = request.PaymentTransactionCode, + PaidAmount = request.PaidAmount + }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandValidator.cs new file mode 100644 index 0000000..cc89e54 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.CompleteOrderPayment; + +public class CompleteOrderPaymentCommandValidator : AbstractValidator +{ + public CompleteOrderPaymentCommandValidator() + { + RuleFor(x => x.OrderId) + .GreaterThan(0).WithMessage("شناسه سفارش نامعتبر است"); + + RuleFor(x => x.PaymentTransactionCode) + .NotEmpty().WithMessage("کد تراکنش پرداخت الزامی است"); + + RuleFor(x => x.PaidAmount) + .GreaterThan(0).WithMessage("مبلغ پرداخت شده باید بیشتر از صفر باشد"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommand.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommand.cs new file mode 100644 index 0000000..2812106 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommand.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.PlaceOrder; + +public record PlaceOrderCommand : IRequest +{ + /// شناسه کاربر + public long UserId { get; init; } + + /// شناسه آدرس ارسال + public long AddressId { get; init; } + + /// مبلغ پرداخت از موجودی تخفیف + public long DiscountBalanceAmount { get; init; } + + /// مبلغ پرداخت از درگاه + public long GatewayAmount { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs new file mode 100644 index 0000000..06a20c7 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Protobuf.Protos.DiscountOrder; + +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.PlaceOrder; + +public class PlaceOrderCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public PlaceOrderCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(PlaceOrderCommand request, CancellationToken cancellationToken) + { + var response = await _context.DiscountOrders.PlaceOrderAsync( + new PlaceOrderRequest + { + UserId = request.UserId, + AddressId = request.AddressId, + DiscountBalanceAmount = request.DiscountBalanceAmount, + GatewayAmount = request.GatewayAmount + }, + cancellationToken: cancellationToken); + + return new PlaceOrderResponseDto + { + OrderId = response.OrderId, + TrackingCode = response.TrackingCode, + RequiresGatewayPayment = response.RequiresGatewayPayment, + GatewayPayableAmount = response.GatewayPayableAmount + }; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommandValidator.cs new file mode 100644 index 0000000..087e2f8 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderCommandValidator.cs @@ -0,0 +1,23 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.PlaceOrder; + +public class PlaceOrderCommandValidator : AbstractValidator +{ + public PlaceOrderCommandValidator() + { + RuleFor(x => x.UserId) + .GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است"); + + RuleFor(x => x.AddressId) + .GreaterThan(0).WithMessage("شناسه آدرس نامعتبر است"); + + RuleFor(x => x.DiscountBalanceAmount) + .GreaterThanOrEqualTo(0).WithMessage("مبلغ موجودی تخفیف نمی‌تواند منفی باشد"); + + RuleFor(x => x.GatewayAmount) + .GreaterThanOrEqualTo(0).WithMessage("مبلغ درگاه نمی‌تواند منفی باشد"); + + RuleFor(x => x) + .Must(x => x.DiscountBalanceAmount + x.GatewayAmount > 0) + .WithMessage("مجموع مبالغ پرداخت باید بیشتر از صفر باشد"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderResponseDto.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderResponseDto.cs new file mode 100644 index 0000000..4d8e1d5 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/PlaceOrder/PlaceOrderResponseDto.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.PlaceOrder; + +public class PlaceOrderResponseDto +{ + /// شناسه سفارش + public long OrderId { get; set; } + + /// کد پیگیری سفارش + public string TrackingCode { get; set; } + + /// آیا نیاز به پرداخت از درگاه دارد + public bool RequiresGatewayPayment { get; set; } + + /// مبلغ قابل پرداخت از درگاه + public long GatewayPayableAmount { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs new file mode 100644 index 0000000..6394d8f --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs @@ -0,0 +1,13 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.UpdateOrderStatus; + +public record UpdateOrderStatusCommand : IRequest +{ + /// شناسه سفارش + public long OrderId { get; init; } + + /// وضعیت جدید + public int NewStatus { get; init; } + + /// یادداشت مدیر (اختیاری) + public string AdminNote { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs new file mode 100644 index 0000000..d950a18 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs @@ -0,0 +1,27 @@ +using CMSMicroservice.Protobuf.Protos.DiscountOrder; + +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.UpdateOrderStatus; + +public class UpdateOrderStatusCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateOrderStatusCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken) + { + await _context.DiscountOrders.UpdateOrderStatusAsync( + new UpdateOrderStatusRequest + { + OrderId = request.OrderId, + NewStatus = request.NewStatus, + AdminNote = request.AdminNote ?? string.Empty + }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs new file mode 100644 index 0000000..3462fbb --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs @@ -0,0 +1,13 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.UpdateOrderStatus; + +public class UpdateOrderStatusCommandValidator : AbstractValidator +{ + public UpdateOrderStatusCommandValidator() + { + RuleFor(x => x.OrderId) + .GreaterThan(0).WithMessage("شناسه سفارش نامعتبر است"); + + RuleFor(x => x.NewStatus) + .InclusiveBetween(0, 6).WithMessage("وضعیت سفارش نامعتبر است"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQuery.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQuery.cs new file mode 100644 index 0000000..220eca9 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetOrderById; + +public record GetOrderByIdQuery : IRequest +{ + /// شناسه سفارش + public long OrderId { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs new file mode 100644 index 0000000..4bc89e7 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs @@ -0,0 +1,58 @@ +using CMSMicroservice.Protobuf.Protos.DiscountOrder; + +namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetOrderById; + +public class GetOrderByIdQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetOrderByIdQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) + { + var response = await _context.DiscountOrders.GetOrderByIdAsync( + new GetOrderByIdRequest { OrderId = request.OrderId }, + cancellationToken: cancellationToken); + + return new GetOrderByIdResponseDto + { + Id = response.Id, + UserId = response.UserId, + TrackingCode = response.TrackingCode, + Status = response.Status, + StatusTitle = response.StatusTitle, + TotalPrice = response.TotalPrice, + DiscountBalanceUsed = response.DiscountBalanceUsed, + GatewayPayment = response.GatewayPayment, + FinalPrice = response.FinalPrice, + PaymentTransactionCode = response.PaymentTransactionCode, + AdminNote = response.AdminNote, + CreatedAt = response.CreatedAt.ToDateTime(), + PaidAt = response.HasPaidAt ? response.PaidAt.ToDateTime() : null, + ShippingAddress = new AddressInfoDto + { + Id = response.ShippingAddress.Id, + RecipientName = response.ShippingAddress.RecipientName, + RecipientPhone = response.ShippingAddress.RecipientPhone, + Province = response.ShippingAddress.Province, + City = response.ShippingAddress.City, + PostalCode = response.ShippingAddress.PostalCode, + FullAddress = response.ShippingAddress.FullAddress + }, + Items = response.Items.Select(item => new OrderItemDto + { + Id = item.Id, + ProductId = item.ProductId, + ProductTitle = item.ProductTitle, + UnitPrice = item.UnitPrice, + DiscountPercent = item.DiscountPercent, + Quantity = item.Quantity, + TotalPrice = item.TotalPrice, + DiscountedPrice = item.DiscountedPrice + }).ToList() + }; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQueryValidator.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQueryValidator.cs new file mode 100644 index 0000000..bec3285 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdQueryValidator.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetOrderById; + +public class GetOrderByIdQueryValidator : AbstractValidator +{ + public GetOrderByIdQueryValidator() + { + RuleFor(x => x.OrderId) + .GreaterThan(0).WithMessage("شناسه سفارش نامعتبر است"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdResponseDto.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdResponseDto.cs new file mode 100644 index 0000000..ec00168 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetOrderById/GetOrderByIdResponseDto.cs @@ -0,0 +1,44 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetOrderById; + +public class GetOrderByIdResponseDto +{ + public long Id { get; set; } + public long UserId { get; set; } + public string TrackingCode { get; set; } + public int Status { get; set; } + public string StatusTitle { get; set; } + public long TotalPrice { get; set; } + public long DiscountBalanceUsed { get; set; } + public long GatewayPayment { get; set; } + public long FinalPrice { get; set; } + public string PaymentTransactionCode { get; set; } + public string AdminNote { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? PaidAt { get; set; } + + public AddressInfoDto ShippingAddress { get; set; } + public List Items { get; set; } = new(); +} + +public class AddressInfoDto +{ + public long Id { get; set; } + public string RecipientName { get; set; } + public string RecipientPhone { get; set; } + public string Province { get; set; } + public string City { get; set; } + public string PostalCode { get; set; } + public string FullAddress { get; set; } +} + +public class OrderItemDto +{ + public long Id { get; set; } + public long ProductId { get; set; } + public string ProductTitle { get; set; } + public long UnitPrice { get; set; } + public int DiscountPercent { get; set; } + public int Quantity { get; set; } + public long TotalPrice { get; set; } + public long DiscountedPrice { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQuery.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQuery.cs new file mode 100644 index 0000000..d8cb2fd --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQuery.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetUserOrders; + +public record GetUserOrdersQuery : IRequest +{ + /// شناسه کاربر + public long UserId { get; init; } + + /// فیلتر براساس وضعیت سفارش + public int? Status { get; init; } + + /// شماره صفحه + public int PageNumber { get; init; } = 1; + + /// تعداد در هر صفحه + public int PageSize { get; init; } = 20; +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQueryHandler.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQueryHandler.cs new file mode 100644 index 0000000..b05689c --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQueryHandler.cs @@ -0,0 +1,54 @@ +using BackOffice.BFF.Application.Common.Models; +using CMSMicroservice.Protobuf.Protos.DiscountOrder; +using Google.Protobuf.WellKnownTypes; + +namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetUserOrders; + +public class GetUserOrdersQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetUserOrdersQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetUserOrdersQuery request, CancellationToken cancellationToken) + { + var grpcRequest = new GetUserOrdersRequest + { + UserId = request.UserId, + PageNumber = request.PageNumber, + PageSize = request.PageSize + }; + + if (request.Status.HasValue) + grpcRequest.Status = new Int32Value { Value = request.Status.Value }; + + var response = await _context.DiscountOrders.GetUserOrdersAsync(grpcRequest, cancellationToken: cancellationToken); + + return new GetUserOrdersResponseDto + { + MetaData = new MetaData + { + CurrentPage = response.MetaData.CurrentPage, + TotalPage = response.MetaData.TotalPages, + PageSize = response.MetaData.PageSize, + TotalCount = response.MetaData.TotalCount, + HasPrevious = response.MetaData.HasPrevious, + HasNext = response.MetaData.HasNext + }, + Orders = response.Orders.Select(order => new UserOrderDto + { + Id = order.Id, + TrackingCode = order.TrackingCode, + Status = order.Status, + StatusTitle = order.StatusTitle, + TotalPrice = order.TotalPrice, + FinalPrice = order.FinalPrice, + ItemCount = order.ItemCount, + CreatedAt = order.CreatedAt.ToDateTime() + }).ToList() + }; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQueryValidator.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQueryValidator.cs new file mode 100644 index 0000000..6235271 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersQueryValidator.cs @@ -0,0 +1,21 @@ +namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetUserOrders; + +public class GetUserOrdersQueryValidator : AbstractValidator +{ + public GetUserOrdersQueryValidator() + { + RuleFor(x => x.UserId) + .GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است"); + + RuleFor(x => x.PageNumber) + .GreaterThan(0).WithMessage("شماره صفحه باید بیشتر از صفر باشد"); + + RuleFor(x => x.PageSize) + .GreaterThan(0).WithMessage("تعداد در صفحه باید بیشتر از صفر باشد") + .LessThanOrEqualTo(100).WithMessage("حداکثر تعداد در صفحه 100 است"); + + RuleFor(x => x.Status) + .InclusiveBetween(0, 6).When(x => x.Status.HasValue) + .WithMessage("وضعیت سفارش نامعتبر است"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersResponseDto.cs b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersResponseDto.cs new file mode 100644 index 0000000..f7af583 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountOrderCQ/Queries/GetUserOrders/GetUserOrdersResponseDto.cs @@ -0,0 +1,21 @@ +using BackOffice.BFF.Application.Common.Models; + +namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetUserOrders; + +public class GetUserOrdersResponseDto +{ + public MetaData MetaData { get; set; } + public List Orders { get; set; } = new(); +} + +public class UserOrderDto +{ + public long Id { get; set; } + public string TrackingCode { get; set; } + public int Status { get; set; } + public string StatusTitle { get; set; } + public long TotalPrice { get; set; } + public long FinalPrice { get; set; } + public int ItemCount { get; set; } + public DateTime CreatedAt { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommand.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommand.cs new file mode 100644 index 0000000..1c30b06 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommand.cs @@ -0,0 +1,49 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.CreateDiscountProduct; + +public record CreateDiscountProductCommand : IRequest +{ + /// عنوان محصول + public string Title { get; init; } + + /// اطلاعات کوتاه محصول + public string ShortInformation { get; init; } + + /// اطلاعات کامل محصول + public string FullInformation { get; init; } + + /// قیمت محصول + public long Price { get; init; } + + /// حداکثر درصد تخفیف (0-100) + public int MaxDiscountPercent { get; init; } + + /// تعداد اولیه موجودی + public int InitialCount { get; init; } + + /// ترتیب نمایش + public int SortOrder { get; init; } + + /// وضعیت فعال/غیرفعال + public bool IsActive { get; init; } + + /// آیدی دسته‌بندی‌ها + public List CategoryIds { get; init; } = new(); + + /// فایل تصویر اصلی + public DiscountProductFileModel ImageFile { get; init; } + + /// فایل تصویر کوچک + public DiscountProductFileModel ThumbnailFile { get; init; } +} + +public class DiscountProductFileModel +{ + /// فایل + public byte[] File { get; set; } + + /// نام فایل + public string FileName { get; set; } + + /// نوع فایل + public string Mime { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs new file mode 100644 index 0000000..d505af7 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs @@ -0,0 +1,70 @@ +using CMSMicroservice.Protobuf.Protos.DiscountProduct; +using Google.Protobuf; + +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.CreateDiscountProduct; + +public class CreateDiscountProductCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CreateDiscountProductCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CreateDiscountProductCommand request, CancellationToken cancellationToken) + { + var createRequest = new CreateDiscountProductRequest + { + Title = request.Title, + ShortInfomation = request.ShortInformation, + FullInformation = request.FullInformation, + Price = request.Price, + MaxDiscountPercent = request.MaxDiscountPercent, + InitialCount = request.InitialCount, + SortOrder = request.SortOrder, + IsActive = request.IsActive + }; + + createRequest.CategoryIds.AddRange(request.CategoryIds); + + // Upload Image + if (request.ImageFile != null && request.ImageFile.File != null && request.ImageFile.File.Length > 0) + { + var imageFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new() + { + Directory = "Images/DiscountShop/Products", + IsBase64 = false, + MIME = request.ImageFile.Mime, + FileName = request.ImageFile.FileName, + File = ByteString.CopyFrom(request.ImageFile.File) + }, cancellationToken: cancellationToken); + + if (imageFileInfo != null && !string.IsNullOrWhiteSpace(imageFileInfo.File)) + createRequest.ImagePath = imageFileInfo.File; + } + + // Upload Thumbnail + if (request.ThumbnailFile != null && request.ThumbnailFile.File != null && request.ThumbnailFile.File.Length > 0) + { + var thumbnailFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new() + { + Directory = "Images/DiscountShop/Products/Thumbnails", + IsBase64 = false, + MIME = request.ThumbnailFile.Mime, + FileName = request.ThumbnailFile.FileName, + File = ByteString.CopyFrom(request.ThumbnailFile.File) + }, cancellationToken: cancellationToken); + + if (thumbnailFileInfo != null && !string.IsNullOrWhiteSpace(thumbnailFileInfo.File)) + createRequest.ThumbnailPath = thumbnailFileInfo.File; + } + + var response = await _context.DiscountProducts.CreateDiscountProductAsync(createRequest, cancellationToken: cancellationToken); + + return new CreateDiscountProductResponseDto + { + ProductId = response.ProductId + }; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs new file mode 100644 index 0000000..55de238 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandValidator.cs @@ -0,0 +1,30 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.CreateDiscountProduct; + +public class CreateDiscountProductCommandValidator : AbstractValidator +{ + public CreateDiscountProductCommandValidator() + { + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان محصول الزامی است") + .MaximumLength(200).WithMessage("عنوان محصول نباید بیشتر از 200 کاراکتر باشد"); + + RuleFor(x => x.ShortInformation) + .NotEmpty().WithMessage("اطلاعات کوتاه محصول الزامی است") + .MaximumLength(500).WithMessage("اطلاعات کوتاه نباید بیشتر از 500 کاراکتر باشد"); + + RuleFor(x => x.FullInformation) + .NotEmpty().WithMessage("اطلاعات کامل محصول الزامی است"); + + RuleFor(x => x.Price) + .GreaterThan(0).WithMessage("قیمت محصول باید بیشتر از صفر باشد"); + + RuleFor(x => x.MaxDiscountPercent) + .InclusiveBetween(0, 100).WithMessage("درصد تخفیف باید بین 0 تا 100 باشد"); + + RuleFor(x => x.InitialCount) + .GreaterThanOrEqualTo(0).WithMessage("تعداد اولیه نمی‌تواند منفی باشد"); + + RuleFor(x => x.CategoryIds) + .NotEmpty().WithMessage("حداقل یک دسته‌بندی باید انتخاب شود"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductResponseDto.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductResponseDto.cs new file mode 100644 index 0000000..d24c30b --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/CreateDiscountProduct/CreateDiscountProductResponseDto.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.CreateDiscountProduct; + +public class CreateDiscountProductResponseDto +{ + /// شناسه محصول ایجاد شده + public long ProductId { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommand.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommand.cs new file mode 100644 index 0000000..e537a23 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommand.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.DeleteDiscountProduct; + +public record DeleteDiscountProductCommand : IRequest +{ + /// شناسه محصول + public long ProductId { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommandHandler.cs new file mode 100644 index 0000000..7a63c35 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommandHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Protobuf.Protos.DiscountProduct; + +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.DeleteDiscountProduct; + +public class DeleteDiscountProductCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public DeleteDiscountProductCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(DeleteDiscountProductCommand request, CancellationToken cancellationToken) + { + await _context.DiscountProducts.DeleteDiscountProductAsync( + new DeleteDiscountProductRequest { ProductId = request.ProductId }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommandValidator.cs new file mode 100644 index 0000000..774b126 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/DeleteDiscountProduct/DeleteDiscountProductCommandValidator.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.DeleteDiscountProduct; + +public class DeleteDiscountProductCommandValidator : AbstractValidator +{ + public DeleteDiscountProductCommandValidator() + { + RuleFor(x => x.ProductId) + .GreaterThan(0).WithMessage("شناسه محصول نامعتبر است"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs new file mode 100644 index 0000000..5b94fb1 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs @@ -0,0 +1,49 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.UpdateDiscountProduct; + +public record UpdateDiscountProductCommand : IRequest +{ + /// شناسه محصول + public long ProductId { get; init; } + + /// عنوان محصول + public string Title { get; init; } + + /// اطلاعات کوتاه محصول + public string ShortInformation { get; init; } + + /// اطلاعات کامل محصول + public string FullInformation { get; init; } + + /// قیمت محصول + public long Price { get; init; } + + /// حداکثر درصد تخفیف (0-100) + public int MaxDiscountPercent { get; init; } + + /// ترتیب نمایش + public int SortOrder { get; init; } + + /// وضعیت فعال/غیرفعال + public bool IsActive { get; init; } + + /// آیدی دسته‌بندی‌ها + public List CategoryIds { get; init; } = new(); + + /// فایل تصویر اصلی (اختیاری - برای تغییر تصویر) + public DiscountProductFileModel ImageFile { get; init; } + + /// فایل تصویر کوچک (اختیاری - برای تغییر تصویر) + public DiscountProductFileModel ThumbnailFile { get; init; } +} + +public class DiscountProductFileModel +{ + /// فایل + public byte[] File { get; set; } + + /// نام فایل + public string FileName { get; set; } + + /// نوع فایل + public string Mime { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs new file mode 100644 index 0000000..0791cc6 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs @@ -0,0 +1,67 @@ +using CMSMicroservice.Protobuf.Protos.DiscountProduct; +using Google.Protobuf; + +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.UpdateDiscountProduct; + +public class UpdateDiscountProductCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateDiscountProductCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateDiscountProductCommand request, CancellationToken cancellationToken) + { + var updateRequest = new UpdateDiscountProductRequest + { + ProductId = request.ProductId, + Title = request.Title, + ShortInfomation = request.ShortInformation, + FullInformation = request.FullInformation, + Price = request.Price, + MaxDiscountPercent = request.MaxDiscountPercent, + SortOrder = request.SortOrder, + IsActive = request.IsActive + }; + + updateRequest.CategoryIds.AddRange(request.CategoryIds); + + // Upload new Image if provided + if (request.ImageFile != null && request.ImageFile.File != null && request.ImageFile.File.Length > 0) + { + var imageFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new() + { + Directory = "Images/DiscountShop/Products", + IsBase64 = false, + MIME = request.ImageFile.Mime, + FileName = request.ImageFile.FileName, + File = ByteString.CopyFrom(request.ImageFile.File) + }, cancellationToken: cancellationToken); + + if (imageFileInfo != null && !string.IsNullOrWhiteSpace(imageFileInfo.File)) + updateRequest.ImagePath = imageFileInfo.File; + } + + // Upload new Thumbnail if provided + if (request.ThumbnailFile != null && request.ThumbnailFile.File != null && request.ThumbnailFile.File.Length > 0) + { + var thumbnailFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new() + { + Directory = "Images/DiscountShop/Products/Thumbnails", + IsBase64 = false, + MIME = request.ThumbnailFile.Mime, + FileName = request.ThumbnailFile.FileName, + File = ByteString.CopyFrom(request.ThumbnailFile.File) + }, cancellationToken: cancellationToken); + + if (thumbnailFileInfo != null && !string.IsNullOrWhiteSpace(thumbnailFileInfo.File)) + updateRequest.ThumbnailPath = thumbnailFileInfo.File; + } + + await _context.DiscountProducts.UpdateDiscountProductAsync(updateRequest, cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs new file mode 100644 index 0000000..80f6bcb --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandValidator.cs @@ -0,0 +1,30 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Commands.UpdateDiscountProduct; + +public class UpdateDiscountProductCommandValidator : AbstractValidator +{ + public UpdateDiscountProductCommandValidator() + { + RuleFor(x => x.ProductId) + .GreaterThan(0).WithMessage("شناسه محصول نامعتبر است"); + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان محصول الزامی است") + .MaximumLength(200).WithMessage("عنوان محصول نباید بیشتر از 200 کاراکتر باشد"); + + RuleFor(x => x.ShortInformation) + .NotEmpty().WithMessage("اطلاعات کوتاه محصول الزامی است") + .MaximumLength(500).WithMessage("اطلاعات کوتاه نباید بیشتر از 500 کاراکتر باشد"); + + RuleFor(x => x.FullInformation) + .NotEmpty().WithMessage("اطلاعات کامل محصول الزامی است"); + + RuleFor(x => x.Price) + .GreaterThan(0).WithMessage("قیمت محصول باید بیشتر از صفر باشد"); + + RuleFor(x => x.MaxDiscountPercent) + .InclusiveBetween(0, 100).WithMessage("درصد تخفیف باید بین 0 تا 100 باشد"); + + RuleFor(x => x.CategoryIds) + .NotEmpty().WithMessage("حداقل یک دسته‌بندی باید انتخاب شود"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQuery.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQuery.cs new file mode 100644 index 0000000..bf2f1a2 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProductById; + +public record GetDiscountProductByIdQuery : IRequest +{ + /// شناسه محصول + public long ProductId { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQueryHandler.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQueryHandler.cs new file mode 100644 index 0000000..e25f588 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQueryHandler.cs @@ -0,0 +1,43 @@ +using CMSMicroservice.Protobuf.Protos.DiscountProduct; + +namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProductById; + +public class GetDiscountProductByIdQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetDiscountProductByIdQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetDiscountProductByIdQuery request, CancellationToken cancellationToken) + { + var response = await _context.DiscountProducts.GetDiscountProductByIdAsync( + new GetDiscountProductByIdRequest { ProductId = request.ProductId }, + cancellationToken: cancellationToken); + + return new GetDiscountProductByIdResponseDto + { + Id = response.Id, + Title = response.Title, + ShortInformation = response.ShortInfomation, + FullInformation = response.FullInformation, + Price = response.Price, + MaxDiscountPercent = response.MaxDiscountPercent, + ImagePath = response.ImagePath, + ThumbnailPath = response.ThumbnailPath, + RemainingCount = response.RemainingCount, + ViewCount = response.ViewCount, + SortOrder = response.SortOrder, + IsActive = response.IsActive, + Categories = response.Categories.Select(c => new CategoryInfoDto + { + Id = c.Id, + Name = c.Name, + Title = c.Title + }).ToList(), + Created = response.Created.ToDateTime() + }; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQueryValidator.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQueryValidator.cs new file mode 100644 index 0000000..4bb5760 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdQueryValidator.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProductById; + +public class GetDiscountProductByIdQueryValidator : AbstractValidator +{ + public GetDiscountProductByIdQueryValidator() + { + RuleFor(x => x.ProductId) + .GreaterThan(0).WithMessage("شناسه محصول نامعتبر است"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdResponseDto.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdResponseDto.cs new file mode 100644 index 0000000..13e3dbc --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProductById/GetDiscountProductByIdResponseDto.cs @@ -0,0 +1,26 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProductById; + +public class GetDiscountProductByIdResponseDto +{ + public long Id { get; set; } + public string Title { get; set; } + public string ShortInformation { 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 int ViewCount { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public List Categories { get; set; } = new(); + public DateTime Created { get; set; } +} + +public class CategoryInfoDto +{ + public long Id { get; set; } + public string Name { get; set; } + public string Title { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQuery.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQuery.cs new file mode 100644 index 0000000..ab3fdd3 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQuery.cs @@ -0,0 +1,28 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProducts; + +public record GetDiscountProductsQuery : IRequest +{ + /// فیلتر براساس دسته‌بندی + public long? CategoryId { get; init; } + + /// جستجو در عنوان و توضیحات + public string SearchQuery { get; init; } + + /// حداقل قیمت + public long? MinPrice { get; init; } + + /// حداکثر قیمت + public long? MaxPrice { get; init; } + + /// فقط محصولات فعال + public bool? IsActive { get; init; } + + /// فقط محصولات موجود در انبار + public bool? InStock { get; init; } + + /// شماره صفحه + public int PageNumber { get; init; } = 1; + + /// تعداد در هر صفحه + public int PageSize { get; init; } = 20; +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs new file mode 100644 index 0000000..be24a15 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs @@ -0,0 +1,71 @@ +using BackOffice.BFF.Application.Common.Models; +using CMSMicroservice.Protobuf.Protos.DiscountProduct; +using Google.Protobuf.WellKnownTypes; + +namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProducts; + +public class GetDiscountProductsQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetDiscountProductsQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetDiscountProductsQuery request, CancellationToken cancellationToken) + { + var grpcRequest = new GetDiscountProductsRequest + { + PageNumber = request.PageNumber, + PageSize = request.PageSize + }; + + if (request.CategoryId.HasValue) + grpcRequest.CategoryId = new Int64Value { Value = request.CategoryId.Value }; + + if (!string.IsNullOrWhiteSpace(request.SearchQuery)) + grpcRequest.SearchQuery = new StringValue { Value = request.SearchQuery }; + + if (request.MinPrice.HasValue) + grpcRequest.MinPrice = new Int64Value { Value = request.MinPrice.Value }; + + if (request.MaxPrice.HasValue) + grpcRequest.MaxPrice = new Int64Value { Value = request.MaxPrice.Value }; + + if (request.IsActive.HasValue) + grpcRequest.IsActive = new BoolValue { Value = request.IsActive.Value }; + + if (request.InStock.HasValue) + grpcRequest.InStock = new BoolValue { Value = request.InStock.Value }; + + var response = await _context.DiscountProducts.GetDiscountProductsAsync(grpcRequest, cancellationToken: cancellationToken); + + return new GetDiscountProductsResponseDto + { + MetaData = new MetaData + { + CurrentPage = response.MetaData.CurrentPage, + TotalPage = response.MetaData.TotalPages, + PageSize = response.MetaData.PageSize, + TotalCount = response.MetaData.TotalCount, + HasPrevious = response.MetaData.HasPrevious, + HasNext = response.MetaData.HasNext + }, + Products = response.Models.Select(p => new DiscountProductDto + { + Id = p.Id, + Title = p.Title, + ShortInformation = p.ShortInfomation, + Price = p.Price, + MaxDiscountPercent = p.MaxDiscountPercent, + ImagePath = p.ImagePath, + ThumbnailPath = p.ThumbnailPath, + RemainingCount = p.RemainingCount, + ViewCount = p.ViewCount, + IsActive = p.IsActive, + Created = p.Created.ToDateTime() + }).ToList() + }; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryValidator.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryValidator.cs new file mode 100644 index 0000000..dc9701d --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryValidator.cs @@ -0,0 +1,24 @@ +namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProducts; + +public class GetDiscountProductsQueryValidator : AbstractValidator +{ + public GetDiscountProductsQueryValidator() + { + RuleFor(x => x.PageNumber) + .GreaterThan(0).WithMessage("شماره صفحه باید بیشتر از صفر باشد"); + + RuleFor(x => x.PageSize) + .GreaterThan(0).WithMessage("تعداد در صفحه باید بیشتر از صفر باشد") + .LessThanOrEqualTo(100).WithMessage("حداکثر تعداد در صفحه 100 است"); + + RuleFor(x => x.MinPrice) + .GreaterThanOrEqualTo(0).When(x => x.MinPrice.HasValue) + .WithMessage("حداقل قیمت نمی‌تواند منفی باشد"); + + RuleFor(x => x.MaxPrice) + .GreaterThanOrEqualTo(0).When(x => x.MaxPrice.HasValue) + .WithMessage("حداکثر قیمت نمی‌تواند منفی باشد") + .GreaterThanOrEqualTo(x => x.MinPrice).When(x => x.MinPrice.HasValue && x.MaxPrice.HasValue) + .WithMessage("حداکثر قیمت باید بیشتر از حداقل قیمت باشد"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsResponseDto.cs b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsResponseDto.cs new file mode 100644 index 0000000..01d875d --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountProductCQ/Queries/GetDiscountProducts/GetDiscountProductsResponseDto.cs @@ -0,0 +1,24 @@ +using BackOffice.BFF.Application.Common.Models; + +namespace BackOffice.BFF.Application.DiscountProductCQ.Queries.GetDiscountProducts; + +public class GetDiscountProductsResponseDto +{ + public MetaData MetaData { get; set; } + public List Products { get; set; } = new(); +} + +public class DiscountProductDto +{ + public long Id { get; set; } + public string Title { get; set; } + public string ShortInformation { 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 int ViewCount { get; set; } + public bool IsActive { get; set; } + public DateTime Created { get; set; } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommand.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommand.cs new file mode 100644 index 0000000..fd8d84a --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommand.cs @@ -0,0 +1,13 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.AddToCart; + +public record AddToCartCommand : IRequest +{ + /// شناسه کاربر + public long UserId { get; init; } + + /// شناسه محصول + public long ProductId { get; init; } + + /// تعداد + public int Count { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommandHandler.cs new file mode 100644 index 0000000..6518a4c --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommandHandler.cs @@ -0,0 +1,27 @@ +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; + +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.AddToCart; + +public class AddToCartCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public AddToCartCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(AddToCartCommand request, CancellationToken cancellationToken) + { + await _context.DiscountShoppingCarts.AddToCartAsync( + new AddToCartRequest + { + UserId = request.UserId, + ProductId = request.ProductId, + Count = request.Count + }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommandValidator.cs new file mode 100644 index 0000000..87bc881 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/AddToCart/AddToCartCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.AddToCart; + +public class AddToCartCommandValidator : AbstractValidator +{ + public AddToCartCommandValidator() + { + RuleFor(x => x.UserId) + .GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است"); + + RuleFor(x => x.ProductId) + .GreaterThan(0).WithMessage("شناسه محصول نامعتبر است"); + + RuleFor(x => x.Count) + .GreaterThan(0).WithMessage("تعداد باید بیشتر از صفر باشد"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommand.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommand.cs new file mode 100644 index 0000000..be5bccd --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommand.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.ClearCart; + +public record ClearCartCommand : IRequest +{ + /// شناسه کاربر + public long UserId { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommandHandler.cs new file mode 100644 index 0000000..9308677 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommandHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; + +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.ClearCart; + +public class ClearCartCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public ClearCartCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(ClearCartCommand request, CancellationToken cancellationToken) + { + await _context.DiscountShoppingCarts.ClearCartAsync( + new ClearCartRequest { UserId = request.UserId }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommandValidator.cs new file mode 100644 index 0000000..9927c01 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/ClearCart/ClearCartCommandValidator.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.ClearCart; + +public class ClearCartCommandValidator : AbstractValidator +{ + public ClearCartCommandValidator() + { + RuleFor(x => x.UserId) + .GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommand.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommand.cs new file mode 100644 index 0000000..c433270 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommand.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.RemoveFromCart; + +public record RemoveFromCartCommand : IRequest +{ + /// شناسه کاربر + public long UserId { get; init; } + + /// شناسه آیتم سبد خرید + public long CartItemId { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommandHandler.cs new file mode 100644 index 0000000..88c3f29 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommandHandler.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; + +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.RemoveFromCart; + +public class RemoveFromCartCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public RemoveFromCartCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(RemoveFromCartCommand request, CancellationToken cancellationToken) + { + await _context.DiscountShoppingCarts.RemoveFromCartAsync( + new RemoveFromCartRequest + { + UserId = request.UserId, + CartItemId = request.CartItemId + }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommandValidator.cs new file mode 100644 index 0000000..749ea69 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/RemoveFromCart/RemoveFromCartCommandValidator.cs @@ -0,0 +1,13 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.RemoveFromCart; + +public class RemoveFromCartCommandValidator : AbstractValidator +{ + public RemoveFromCartCommandValidator() + { + RuleFor(x => x.UserId) + .GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است"); + + RuleFor(x => x.CartItemId) + .GreaterThan(0).WithMessage("شناسه آیتم سبد خرید نامعتبر است"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommand.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommand.cs new file mode 100644 index 0000000..1827200 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommand.cs @@ -0,0 +1,13 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.UpdateCartItemCount; + +public record UpdateCartItemCountCommand : IRequest +{ + /// شناسه کاربر + public long UserId { get; init; } + + /// شناسه آیتم سبد خرید + public long CartItemId { get; init; } + + /// تعداد جدید + public int NewCount { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommandHandler.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommandHandler.cs new file mode 100644 index 0000000..5a9e815 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommandHandler.cs @@ -0,0 +1,27 @@ +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; + +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.UpdateCartItemCount; + +public class UpdateCartItemCountCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateCartItemCountCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateCartItemCountCommand request, CancellationToken cancellationToken) + { + await _context.DiscountShoppingCarts.UpdateCartItemCountAsync( + new UpdateCartItemCountRequest + { + UserId = request.UserId, + CartItemId = request.CartItemId, + NewCount = request.NewCount + }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommandValidator.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommandValidator.cs new file mode 100644 index 0000000..644fb49 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Commands/UpdateCartItemCount/UpdateCartItemCountCommandValidator.cs @@ -0,0 +1,16 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Commands.UpdateCartItemCount; + +public class UpdateCartItemCountCommandValidator : AbstractValidator +{ + public UpdateCartItemCountCommandValidator() + { + RuleFor(x => x.UserId) + .GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است"); + + RuleFor(x => x.CartItemId) + .GreaterThan(0).WithMessage("شناسه آیتم سبد خرید نامعتبر است"); + + RuleFor(x => x.NewCount) + .GreaterThan(0).WithMessage("تعداد جدید باید بیشتر از صفر باشد"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQuery.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQuery.cs new file mode 100644 index 0000000..3c7697f --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Queries.GetUserCart; + +public record GetUserCartQuery : IRequest +{ + /// شناسه کاربر + public long UserId { get; init; } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQueryHandler.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQueryHandler.cs new file mode 100644 index 0000000..48541ed --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQueryHandler.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; + +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Queries.GetUserCart; + +public class GetUserCartQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetUserCartQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetUserCartQuery request, CancellationToken cancellationToken) + { + var response = await _context.DiscountShoppingCarts.GetUserCartAsync( + new GetUserCartRequest { UserId = request.UserId }, + cancellationToken: cancellationToken); + + return new GetUserCartResponseDto + { + UserId = response.UserId, + TotalPrice = response.TotalPrice, + TotalDiscountedPrice = response.TotalDiscountedPrice, + TotalSavings = response.TotalSavings, + Items = response.Items.Select(item => new CartItemDto + { + Id = item.Id, + ProductId = item.ProductId, + ProductTitle = item.ProductTitle, + ProductImagePath = item.ProductImagePath, + UnitPrice = item.UnitPrice, + MaxDiscountPercent = item.MaxDiscountPercent, + Count = item.Count, + TotalPrice = item.TotalPrice, + DiscountedPrice = item.DiscountedPrice, + AddedAt = item.AddedAt.ToDateTime() + }).ToList() + }; + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQueryValidator.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQueryValidator.cs new file mode 100644 index 0000000..58712fa --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartQueryValidator.cs @@ -0,0 +1,10 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Queries.GetUserCart; + +public class GetUserCartQueryValidator : AbstractValidator +{ + public GetUserCartQueryValidator() + { + RuleFor(x => x.UserId) + .GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است"); + } +} diff --git a/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartResponseDto.cs b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartResponseDto.cs new file mode 100644 index 0000000..3f81de8 --- /dev/null +++ b/src/BackOffice.BFF.Application/DiscountShoppingCartCQ/Queries/GetUserCart/GetUserCartResponseDto.cs @@ -0,0 +1,24 @@ +namespace BackOffice.BFF.Application.DiscountShoppingCartCQ.Queries.GetUserCart; + +public class GetUserCartResponseDto +{ + public long UserId { get; set; } + public List Items { get; set; } = new(); + public long TotalPrice { get; set; } + public long TotalDiscountedPrice { get; set; } + public long TotalSavings { get; set; } +} + +public class CartItemDto +{ + public long Id { get; set; } + public long ProductId { get; set; } + public string ProductTitle { get; set; } + public string ProductImagePath { get; set; } + public long UnitPrice { get; set; } + public int MaxDiscountPercent { get; set; } + public int Count { get; set; } + public long TotalPrice { get; set; } + public long DiscountedPrice { get; set; } + public DateTime AddedAt { get; set; } +} diff --git a/src/BackOffice.BFF.Application/ProductTagCQ/Commands/AssignTagToProduct/AssignTagToProductCommand.cs b/src/BackOffice.BFF.Application/ProductTagCQ/Commands/AssignTagToProduct/AssignTagToProductCommand.cs new file mode 100644 index 0000000..a1622f7 --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductTagCQ/Commands/AssignTagToProduct/AssignTagToProductCommand.cs @@ -0,0 +1,9 @@ +using CMSMicroservice.Protobuf.Protos.ProductTag; + +namespace BackOffice.BFF.Application.ProductTagCQ.Commands.AssignTagToProduct; + +public record AssignTagToProductCommand : IRequest +{ + public long ProductId { get; init; } + public long TagId { get; init; } +} diff --git a/src/BackOffice.BFF.Application/ProductTagCQ/Commands/AssignTagToProduct/AssignTagToProductCommandHandler.cs b/src/BackOffice.BFF.Application/ProductTagCQ/Commands/AssignTagToProduct/AssignTagToProductCommandHandler.cs new file mode 100644 index 0000000..c60f812 --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductTagCQ/Commands/AssignTagToProduct/AssignTagToProductCommandHandler.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Protobuf.Protos.ProductTag; + +namespace BackOffice.BFF.Application.ProductTagCQ.Commands.AssignTagToProduct; + +public class AssignTagToProductCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public AssignTagToProductCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(AssignTagToProductCommand request, CancellationToken cancellationToken) + { + var response = await _context.ProductTags.CreateNewProductTagAsync( + new CreateNewProductTagRequest + { + ProductId = request.ProductId, + TagId = request.TagId + }, + cancellationToken: cancellationToken); + + return response.Id; + } +} diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommandHandler.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommandHandler.cs index 15a8ce6..d679f29 100644 --- a/src/BackOffice.BFF.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommandHandler.cs +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/AddProductImage/AddProductImageCommandHandler.cs @@ -1,7 +1,7 @@ using System.IO; using BackOffice.BFF.Application.Common.Interfaces; using CMSMicroservice.Protobuf.Protos.ProductImages; -using CMSMicroservice.Protobuf.Protos.ProductGallerys; +using CMSMicroservice.Protobuf.Protos.ProductGalleries; using Google.Protobuf; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; @@ -59,7 +59,7 @@ public class AddProductImageCommandHandler : IRequestHandler Handle(RemoveProductImageCommand request, CancellationToken cancellationToken) { - await _context.ProductGallerys.DeleteProductGallerysAsync(new DeleteProductGallerysRequest + await _context.ProductGalleries.DeleteProductGalleriesAsync(new DeleteProductGalleriesRequest { Id = request.ProductGalleryId }, cancellationToken: cancellationToken); diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommandHandler.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommandHandler.cs index a17d32c..95bcebc 100644 --- a/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommandHandler.cs +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommandHandler.cs @@ -1,5 +1,5 @@ using BackOffice.BFF.Application.Common.Interfaces; -using CMSPruductCategory = CMSMicroservice.Protobuf.Protos.PruductCategory; +using CMSProductCategory = CMSMicroservice.Protobuf.Protos.ProductCategory; namespace BackOffice.BFF.Application.ProductsCQ.Commands.UpdateCategoryProducts; @@ -17,9 +17,9 @@ public class UpdateCategoryProductsCommandHandler : IRequestHandler(); // load existing links for this category - var existingRequest = new CMSPruductCategory.GetAllPruductCategoryByFilterRequest + var existingRequest = new CMSProductCategory.GetAllProductCategoryByFilterRequest { - Filter = new CMSPruductCategory.GetAllPruductCategoryByFilterFilter + Filter = new CMSProductCategory.GetAllProductCategoryByFilterFilter { CategoryId = request.CategoryId }, @@ -30,7 +30,7 @@ public class UpdateCategoryProductsCommandHandler : IRequestHandler x.ProductId).ToHashSet(); @@ -40,19 +40,19 @@ public class UpdateCategoryProductsCommandHandler : IRequestHandler(); // Load existing product-category relations - var existingRequest = new CMSPruductCategory.GetAllPruductCategoryByFilterRequest + var existingRequest = new CMSProductCategory.GetAllProductCategoryByFilterRequest { - Filter = new CMSPruductCategory.GetAllPruductCategoryByFilterFilter + Filter = new CMSProductCategory.GetAllProductCategoryByFilterFilter { ProductId = request.ProductId }, @@ -30,7 +30,7 @@ public class UpdateProductCategoriesCommandHandler : IRequestHandler x.CategoryId).ToHashSet(); @@ -41,19 +41,19 @@ public class UpdateProductCategoriesCommandHandler : IRequestHandler Handle(GetProductGalleryQuery request, CancellationToken cancellationToken) { - var galleryRequest = new GetAllProductGallerysByFilterRequest + var grpcRequest = new GetAllProductGalleriesByFilterRequest { - Filter = new GetAllProductGallerysByFilterFilter() + Filter = new GetAllProductGalleriesByFilterFilter() }; - var galleryResponse = await _context.ProductGallerys.GetAllProductGallerysByFilterAsync(galleryRequest, cancellationToken: cancellationToken); + var galleryResponse = await _context.ProductGalleries.GetAllProductGalleriesByFilterAsync(grpcRequest, cancellationToken: cancellationToken); // Filter by product id on client side because generated type may not support assigning Int64Value directly var filteredModels = galleryResponse?.Models?.Where(x => x.ProductId == request.ProductId).ToList(); diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQueryHandler.cs b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQueryHandler.cs index 54962cc..2dcec8c 100644 --- a/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQueryHandler.cs +++ b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQueryHandler.cs @@ -1,6 +1,6 @@ using BackOffice.BFF.Application.Common.Interfaces; using CMSProducts = CMSMicroservice.Protobuf.Protos.Products; -using CMSPruductCategory = CMSMicroservice.Protobuf.Protos.PruductCategory; +using CMSProductCategory = CMSMicroservice.Protobuf.Protos.ProductCategory; namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductsForCategory; @@ -30,9 +30,9 @@ public class GetProductsForCategoryQueryHandler : IRequestHandler l.ProductId).ToHashSet(); diff --git a/src/BackOffice.BFF.Application/TagCQ/Commands/CreateTag/CreateTagCommand.cs b/src/BackOffice.BFF.Application/TagCQ/Commands/CreateTag/CreateTagCommand.cs new file mode 100644 index 0000000..8940910 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Commands/CreateTag/CreateTagCommand.cs @@ -0,0 +1,12 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Commands.CreateTag; + +public record CreateTagCommand : IRequest +{ + public string Name { get; init; } = string.Empty; + public string Title { get; init; } = string.Empty; + public string? Description { get; init; } + public bool IsActive { get; init; } = true; + public int SortOrder { get; init; } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Commands/CreateTag/CreateTagCommandHandler.cs b/src/BackOffice.BFF.Application/TagCQ/Commands/CreateTag/CreateTagCommandHandler.cs new file mode 100644 index 0000000..6ddcc07 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Commands/CreateTag/CreateTagCommandHandler.cs @@ -0,0 +1,29 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Commands.CreateTag; + +public class CreateTagCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public CreateTagCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(CreateTagCommand request, CancellationToken cancellationToken) + { + var response = await _context.Tags.CreateNewTagAsync( + new CreateNewTagRequest + { + Name = request.Name, + Title = request.Title, + Description = request.Description, + IsActive = request.IsActive, + SortOrder = request.SortOrder + }, + cancellationToken: cancellationToken); + + return response.Id; + } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Commands/DeleteTag/DeleteTagCommand.cs b/src/BackOffice.BFF.Application/TagCQ/Commands/DeleteTag/DeleteTagCommand.cs new file mode 100644 index 0000000..45af23b --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Commands/DeleteTag/DeleteTagCommand.cs @@ -0,0 +1,6 @@ +namespace BackOffice.BFF.Application.TagCQ.Commands.DeleteTag; + +public record DeleteTagCommand : IRequest +{ + public long Id { get; init; } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Commands/DeleteTag/DeleteTagCommandHandler.cs b/src/BackOffice.BFF.Application/TagCQ/Commands/DeleteTag/DeleteTagCommandHandler.cs new file mode 100644 index 0000000..b4ff0e0 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Commands/DeleteTag/DeleteTagCommandHandler.cs @@ -0,0 +1,25 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Commands.DeleteTag; + +public class DeleteTagCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public DeleteTagCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(DeleteTagCommand request, CancellationToken cancellationToken) + { + await _context.Tags.DeleteTagAsync( + new DeleteTagRequest + { + Id = request.Id + }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Commands/UpdateTag/UpdateTagCommand.cs b/src/BackOffice.BFF.Application/TagCQ/Commands/UpdateTag/UpdateTagCommand.cs new file mode 100644 index 0000000..784db27 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Commands/UpdateTag/UpdateTagCommand.cs @@ -0,0 +1,13 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Commands.UpdateTag; + +public record UpdateTagCommand : IRequest +{ + public long Id { get; init; } + public string Name { get; init; } = string.Empty; + public string Title { get; init; } = string.Empty; + public string? Description { get; init; } + public bool IsActive { get; init; } + public int SortOrder { get; init; } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Commands/UpdateTag/UpdateTagCommandHandler.cs b/src/BackOffice.BFF.Application/TagCQ/Commands/UpdateTag/UpdateTagCommandHandler.cs new file mode 100644 index 0000000..99cbdd9 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Commands/UpdateTag/UpdateTagCommandHandler.cs @@ -0,0 +1,30 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Commands.UpdateTag; + +public class UpdateTagCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateTagCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateTagCommand request, CancellationToken cancellationToken) + { + await _context.Tags.UpdateTagAsync( + new UpdateTagRequest + { + Id = request.Id, + Name = request.Name, + Title = request.Title, + Description = request.Description, + IsActive = request.IsActive, + SortOrder = request.SortOrder + }, + cancellationToken: cancellationToken); + + return Unit.Value; + } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Queries/GetAllTags/GetAllTagsQuery.cs b/src/BackOffice.BFF.Application/TagCQ/Queries/GetAllTags/GetAllTagsQuery.cs new file mode 100644 index 0000000..5b1b387 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Queries/GetAllTags/GetAllTagsQuery.cs @@ -0,0 +1,11 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Queries.GetAllTags; + +public record GetAllTagsQuery : IRequest +{ + public int PageNumber { get; init; } = 1; + public int PageSize { get; init; } = 10; + public string? SearchTerm { get; init; } + public bool? IsActive { get; init; } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Queries/GetAllTags/GetAllTagsQueryHandler.cs b/src/BackOffice.BFF.Application/TagCQ/Queries/GetAllTags/GetAllTagsQueryHandler.cs new file mode 100644 index 0000000..fa893e5 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Queries/GetAllTags/GetAllTagsQueryHandler.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Protobuf.Protos.Tag; +using Google.Protobuf.WellKnownTypes; +using CMSMicroservice.Protobuf.Protos; + +namespace BackOffice.BFF.Application.TagCQ.Queries.GetAllTags; + +public class GetAllTagsQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetAllTagsQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetAllTagsQuery request, CancellationToken cancellationToken) + { + var grpcRequest = new GetAllTagByFilterRequest + { + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState + { + PageNumber = request.PageNumber, + PageSize = request.PageSize + }, + Filter = new GetAllTagByFilterFilter() + }; + + if (!string.IsNullOrEmpty(request.SearchTerm)) + { + grpcRequest.Filter.Name = request.SearchTerm; + } + + if (request.IsActive.HasValue) + { + grpcRequest.Filter.IsActive = request.IsActive.Value; + } + + var response = await _context.Tags.GetAllTagByFilterAsync(grpcRequest, cancellationToken: cancellationToken); + return response; + } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Queries/GetProductsByTag/GetProductsByTagQuery.cs b/src/BackOffice.BFF.Application/TagCQ/Queries/GetProductsByTag/GetProductsByTagQuery.cs new file mode 100644 index 0000000..faa2224 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Queries/GetProductsByTag/GetProductsByTagQuery.cs @@ -0,0 +1,8 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Queries.GetProductsByTag; + +public record GetProductsByTagQuery : IRequest +{ + public long TagId { get; init; } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Queries/GetProductsByTag/GetProductsByTagQueryHandler.cs b/src/BackOffice.BFF.Application/TagCQ/Queries/GetProductsByTag/GetProductsByTagQueryHandler.cs new file mode 100644 index 0000000..c0b2843 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Queries/GetProductsByTag/GetProductsByTagQueryHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Queries.GetProductsByTag; + +public class GetProductsByTagQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetProductsByTagQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetProductsByTagQuery request, CancellationToken cancellationToken) + { + var response = await _context.Tags.GetProductsByTagAsync( + new GetProductsByTagRequest { TagId = request.TagId }, + cancellationToken: cancellationToken); + + return response; + } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Queries/GetTag/GetTagQuery.cs b/src/BackOffice.BFF.Application/TagCQ/Queries/GetTag/GetTagQuery.cs new file mode 100644 index 0000000..6bec908 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Queries/GetTag/GetTagQuery.cs @@ -0,0 +1,8 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Queries.GetTag; + +public record GetTagQuery : IRequest +{ + public long Id { get; init; } +} diff --git a/src/BackOffice.BFF.Application/TagCQ/Queries/GetTag/GetTagQueryHandler.cs b/src/BackOffice.BFF.Application/TagCQ/Queries/GetTag/GetTagQueryHandler.cs new file mode 100644 index 0000000..3424753 --- /dev/null +++ b/src/BackOffice.BFF.Application/TagCQ/Queries/GetTag/GetTagQueryHandler.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Protobuf.Protos.Tag; + +namespace BackOffice.BFF.Application.TagCQ.Queries.GetTag; + +public class GetTagQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetTagQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetTagQuery request, CancellationToken cancellationToken) + { + var response = await _context.Tags.GetTagAsync( + new GetTagRequest { Id = request.Id }, + cancellationToken: cancellationToken); + + return response; + } +} diff --git a/src/BackOffice.BFF.Domain/BackOffice.BFF.Domain.csproj b/src/BackOffice.BFF.Domain/BackOffice.BFF.Domain.csproj index f674477..520241c 100644 --- a/src/BackOffice.BFF.Domain/BackOffice.BFF.Domain.csproj +++ b/src/BackOffice.BFF.Domain/BackOffice.BFF.Domain.csproj @@ -7,11 +7,15 @@ - + + + + + diff --git a/src/BackOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs b/src/BackOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs index 108d080..1edf49f 100644 --- a/src/BackOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs +++ b/src/BackOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs @@ -7,14 +7,20 @@ using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserRole; using CMSMicroservice.Protobuf.Protos.Products; using CMSMicroservice.Protobuf.Protos.ProductImages; -using CMSMicroservice.Protobuf.Protos.ProductGallerys; +using CMSMicroservice.Protobuf.Protos.ProductGalleries; using CMSMicroservice.Protobuf.Protos.Category; -using CMSMicroservice.Protobuf.Protos.PruductCategory; +using CMSMicroservice.Protobuf.Protos.ProductCategory; using BackOffice.BFF.Commission.Protobuf; using BackOffice.BFF.NetworkMembership.Protobuf; using BackOffice.BFF.ClubMembership.Protobuf; using BackOffice.BFF.Configuration.Protobuf; using FMSMicroservice.Protobuf.Protos.FileInfo; +using CMSMicroservice.Protobuf.Protos.DiscountProduct; +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; +using CMSMicroservice.Protobuf.Protos.DiscountOrder; +using CMSMicroservice.Protobuf.Protos.Tag; +using CMSMicroservice.Protobuf.Protos.ProductTag; using Microsoft.Extensions.DependencyInjection; namespace BackOffice.BFF.Infrastructure.Services; @@ -50,10 +56,10 @@ public class ApplicationContractContext : IApplicationContractContext public PackageContract.PackageContractClient Packages => GetService(); public ProductsContract.ProductsContractClient Products => GetService(); public ProductImagesContract.ProductImagesContractClient ProductImages => GetService(); - public ProductGallerysContract.ProductGallerysContractClient ProductGallerys => GetService(); + public ProductGalleriesContract.ProductGalleriesContractClient ProductGalleries => GetService(); public RoleContract.RoleContractClient Roles => GetService(); public CategoryContract.CategoryContractClient Categories => GetService(); - public PruductCategoryContract.PruductCategoryContractClient ProductCategories => GetService(); + public ProductCategoryContract.ProductCategoryContractClient ProductCategories => GetService(); public UserAddressContract.UserAddressContractClient UserAddress => GetService(); public UserContract.UserContractClient Users => GetService(); public UserOrderContract.UserOrderContractClient UserOrders => GetService(); @@ -64,5 +70,15 @@ public class ApplicationContractContext : IApplicationContractContext public NetworkMembershipContract.NetworkMembershipContractClient NetworkMemberships => GetService(); public ClubMembershipContract.ClubMembershipContractClient ClubMemberships => GetService(); public ConfigurationContract.ConfigurationContractClient Configurations => GetService(); + + // Discount Shop System (Phase 9) + public DiscountProductContract.DiscountProductContractClient DiscountProducts => GetService(); + public DiscountCategoryContract.DiscountCategoryContractClient DiscountCategories => GetService(); + public DiscountShoppingCartContract.DiscountShoppingCartContractClient DiscountShoppingCarts => GetService(); + public DiscountOrderContract.DiscountOrderContractClient DiscountOrders => GetService(); + + // Tag Management System + public TagContract.TagContractClient Tags => GetService(); + public ProductTagContract.ProductTagContractClient ProductTags => GetService(); #endregion } diff --git a/src/Protobufs/BackOffice.BFF.Commission.Protobuf/Protos/commission.proto b/src/Protobufs/BackOffice.BFF.Commission.Protobuf/Protos/commission.proto index c8523ab..2faab23 100644 --- a/src/Protobufs/BackOffice.BFF.Commission.Protobuf/Protos/commission.proto +++ b/src/Protobufs/BackOffice.BFF.Commission.Protobuf/Protos/commission.proto @@ -48,6 +48,8 @@ service CommissionContract }; rpc GetWorkerExecutionLogs(GetWorkerExecutionLogsRequest) returns (GetWorkerExecutionLogsResponse){ }; + rpc GetWithdrawalReports(GetWithdrawalReportsRequest) returns (GetWithdrawalReportsResponse){ + }; } // ============ Commands ============ @@ -347,3 +349,47 @@ message WorkerExecutionLogModel int32 records_processed = 9; google.protobuf.StringValue details = 10; // JSON or text details } + +// GetWithdrawalReports Query +message GetWithdrawalReportsRequest +{ + google.protobuf.Timestamp start_date = 1; // Optional - default: 30 days ago + google.protobuf.Timestamp end_date = 2; // Optional - default: today + int32 period_type = 3; // ReportPeriodType: Daily=1, Weekly=2, Monthly=3 + google.protobuf.Int32Value status = 4; // CommissionPayoutStatus enum (optional) + google.protobuf.Int64Value user_id = 5; // Optional user filter +} + +message GetWithdrawalReportsResponse +{ + repeated PeriodReport period_reports = 1; + WithdrawalSummary summary = 2; +} + +message PeriodReport +{ + string period_label = 1; // e.g., "2025-01-15", "هفته 3", "فروردین 1404" + google.protobuf.Timestamp start_date = 2; + google.protobuf.Timestamp end_date = 3; + int32 total_requests = 4; + int32 pending_count = 5; + int32 approved_count = 6; + int32 rejected_count = 7; + int32 completed_count = 8; + int32 failed_count = 9; + int64 total_amount = 10; + int64 paid_amount = 11; + int64 pending_amount = 12; +} + +message WithdrawalSummary +{ + int32 total_requests = 1; + int64 total_amount = 2; + int64 total_paid = 3; + int64 total_pending = 4; + int64 total_rejected = 5; + int64 average_amount = 6; + int32 unique_users = 7; + float success_rate = 8; // Percentage (0-100) +}