feat: Implement bulk update for product prices and stock, and add low stock products query and toggle product status functionality
This commit is contained in:
@@ -56,13 +56,18 @@ public class GetLowStockProductsQueryHandler : IRequestHandler<GetLowStockProduc
|
|||||||
"Found {Count} low stock products (threshold: {Threshold}, page: {Page})",
|
"Found {Count} low stock products (threshold: {Threshold}, page: {Page})",
|
||||||
totalCount, request.Threshold, request.PageIndex);
|
totalCount, request.Threshold, request.PageIndex);
|
||||||
|
|
||||||
|
var totalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize);
|
||||||
|
|
||||||
return new GetLowStockProductsResponseDto
|
return new GetLowStockProductsResponseDto
|
||||||
{
|
{
|
||||||
MetaData = new MetaData
|
MetaData = new MetaData
|
||||||
{
|
{
|
||||||
CurrentPage = request.PageIndex,
|
CurrentPage = request.PageIndex,
|
||||||
|
TotalPage = totalPages,
|
||||||
PageSize = request.PageSize,
|
PageSize = request.PageSize,
|
||||||
TotalCount = totalCount
|
TotalCount = totalCount,
|
||||||
|
HasNext = request.PageIndex < totalPages,
|
||||||
|
HasPrevious = request.PageIndex > 1
|
||||||
},
|
},
|
||||||
Products = products
|
Products = products
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,6 +43,29 @@ service ProductsContract
|
|||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
rpc BulkUpdateProductPrices(BulkUpdateProductPricesRequest) returns (BulkUpdateProductPricesResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/BulkUpdateProductPrices"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
rpc BulkUpdateProductStock(BulkUpdateProductStockRequest) returns (BulkUpdateProductStockResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/BulkUpdateProductStock"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
rpc GetLowStockProducts(GetLowStockProductsRequest) returns (GetLowStockProductsResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/GetLowStockProducts"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
rpc ToggleProductStatus(ToggleProductStatusRequest) returns (ToggleProductStatusResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/ToggleProductStatus"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
message CreateNewProductsRequest
|
message CreateNewProductsRequest
|
||||||
{
|
{
|
||||||
@@ -155,3 +178,99 @@ message GetAllProductsByFilterResponseModel
|
|||||||
// لیست شناسه دستهبندیهای محصول
|
// لیست شناسه دستهبندیهای محصول
|
||||||
repeated int64 category_ids = 14;
|
repeated int64 category_ids = 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bulk Update Product Prices
|
||||||
|
message BulkUpdateProductPricesRequest
|
||||||
|
{
|
||||||
|
repeated ProductPriceUpdate products = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ProductPriceUpdate
|
||||||
|
{
|
||||||
|
int64 product_id = 1;
|
||||||
|
int64 new_price = 2;
|
||||||
|
google.protobuf.Int32Value new_discount = 3;
|
||||||
|
google.protobuf.Int32Value new_club_discount_percent = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BulkUpdateProductPricesResponse
|
||||||
|
{
|
||||||
|
int32 total = 1;
|
||||||
|
int32 succeeded = 2;
|
||||||
|
int32 failed = 3;
|
||||||
|
repeated BulkOperationError errors = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BulkOperationError
|
||||||
|
{
|
||||||
|
int64 product_id = 1;
|
||||||
|
string error_message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk Update Product Stock
|
||||||
|
message BulkUpdateProductStockRequest
|
||||||
|
{
|
||||||
|
repeated ProductStockUpdate products = 1;
|
||||||
|
StockUpdateType update_type = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ProductStockUpdate
|
||||||
|
{
|
||||||
|
int64 product_id = 1;
|
||||||
|
int32 quantity = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StockUpdateType
|
||||||
|
{
|
||||||
|
SET = 0;
|
||||||
|
ADD = 1;
|
||||||
|
SUBTRACT = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BulkUpdateProductStockResponse
|
||||||
|
{
|
||||||
|
int32 total = 1;
|
||||||
|
int32 succeeded = 2;
|
||||||
|
int32 failed = 3;
|
||||||
|
repeated BulkOperationError errors = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Low Stock Products
|
||||||
|
message GetLowStockProductsRequest
|
||||||
|
{
|
||||||
|
int32 threshold = 1;
|
||||||
|
int32 page_index = 2;
|
||||||
|
int32 page_size = 3;
|
||||||
|
google.protobuf.BoolValue is_club_exclusive = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetLowStockProductsResponse
|
||||||
|
{
|
||||||
|
messages.MetaData meta_data = 1;
|
||||||
|
repeated LowStockProduct products = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LowStockProduct
|
||||||
|
{
|
||||||
|
int64 id = 1;
|
||||||
|
string title = 2;
|
||||||
|
int32 remaining_count = 3;
|
||||||
|
int64 price = 4;
|
||||||
|
bool is_club_exclusive = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Product Status
|
||||||
|
message ToggleProductStatusRequest
|
||||||
|
{
|
||||||
|
repeated int64 product_ids = 1;
|
||||||
|
bool enable = 2;
|
||||||
|
int32 default_stock = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ToggleProductStatusResponse
|
||||||
|
{
|
||||||
|
int32 total = 1;
|
||||||
|
int32 succeeded = 2;
|
||||||
|
int32 failed = 3;
|
||||||
|
repeated BulkOperationError errors = 4;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,93 @@
|
|||||||
|
using CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductPrices;
|
||||||
|
using CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock;
|
||||||
|
using CMSMicroservice.Application.ProductsCQ.Commands.ToggleProductStatus;
|
||||||
|
using CMSMicroservice.Application.ProductsCQ.Queries.GetLowStockProducts;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.Products;
|
||||||
|
using ProtoProductPriceUpdate = CMSMicroservice.Protobuf.Protos.Products.ProductPriceUpdate;
|
||||||
|
using AppProductPriceUpdate = CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductPrices.ProductPriceUpdate;
|
||||||
|
using ProtoProductStockUpdate = CMSMicroservice.Protobuf.Protos.Products.ProductStockUpdate;
|
||||||
|
using AppProductStockUpdate = CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock.ProductStockUpdate;
|
||||||
|
using ProtoStockUpdateType = CMSMicroservice.Protobuf.Protos.Products.StockUpdateType;
|
||||||
|
using AppStockUpdateType = CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock.StockUpdateType;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Common.Mappings;
|
namespace CMSMicroservice.WebApi.Common.Mappings;
|
||||||
|
|
||||||
public class ProductsProfile : IRegister
|
public class ProductsProfile : IRegister
|
||||||
{
|
{
|
||||||
void IRegister.Register(TypeAdapterConfig config)
|
void IRegister.Register(TypeAdapterConfig config)
|
||||||
{
|
{
|
||||||
//config.NewConfig<Source,Destination>()
|
// BulkUpdateProductPrices mappings
|
||||||
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
|
config.NewConfig<BulkUpdateProductPricesRequest, BulkUpdateProductPricesCommand>()
|
||||||
|
.Map(dest => dest.Products, src => src.Products);
|
||||||
|
|
||||||
|
config.NewConfig<ProtoProductPriceUpdate, AppProductPriceUpdate>()
|
||||||
|
.Map(dest => dest.ProductId, src => src.ProductId)
|
||||||
|
.Map(dest => dest.NewPrice, src => src.NewPrice)
|
||||||
|
.Map(dest => dest.NewDiscount, src => src.NewDiscount != null ? (int?)src.NewDiscount.Value : null)
|
||||||
|
.Map(dest => dest.NewClubDiscountPercent, src => src.NewClubDiscountPercent != null ? (int?)src.NewClubDiscountPercent.Value : null);
|
||||||
|
|
||||||
|
config.NewConfig<BulkUpdateProductPricesResponseDto, BulkUpdateProductPricesResponse>()
|
||||||
|
.Map(dest => dest.Total, src => src.UpdatedCount + src.FailedCount)
|
||||||
|
.Map(dest => dest.Succeeded, src => src.UpdatedCount)
|
||||||
|
.Map(dest => dest.Failed, src => src.FailedCount)
|
||||||
|
.Map(dest => dest.Errors, src => src.Errors.Select((msg, idx) => new BulkOperationError
|
||||||
|
{
|
||||||
|
ProductId = 0, // We don't have the ID in the error message
|
||||||
|
ErrorMessage = msg
|
||||||
|
}).ToList());
|
||||||
|
|
||||||
|
// BulkUpdateProductStock mappings
|
||||||
|
config.NewConfig<BulkUpdateProductStockRequest, BulkUpdateProductStockCommand>()
|
||||||
|
.Map(dest => dest.Products, src => src.Products)
|
||||||
|
.Map(dest => dest.UpdateType, src => (AppStockUpdateType)src.UpdateType);
|
||||||
|
|
||||||
|
config.NewConfig<ProtoProductStockUpdate, AppProductStockUpdate>()
|
||||||
|
.Map(dest => dest.ProductId, src => src.ProductId)
|
||||||
|
.Map(dest => dest.Quantity, src => src.Quantity);
|
||||||
|
|
||||||
|
config.NewConfig<BulkUpdateProductStockResponseDto, BulkUpdateProductStockResponse>()
|
||||||
|
.Map(dest => dest.Total, src => src.UpdatedCount + src.FailedCount)
|
||||||
|
.Map(dest => dest.Succeeded, src => src.UpdatedCount)
|
||||||
|
.Map(dest => dest.Failed, src => src.FailedCount)
|
||||||
|
.Map(dest => dest.Errors, src => src.Errors.Select(msg => new BulkOperationError
|
||||||
|
{
|
||||||
|
ProductId = 0,
|
||||||
|
ErrorMessage = msg
|
||||||
|
}).ToList());
|
||||||
|
|
||||||
|
// GetLowStockProducts mappings
|
||||||
|
config.NewConfig<GetLowStockProductsRequest, GetLowStockProductsQuery>()
|
||||||
|
.Map(dest => dest.Threshold, src => src.Threshold)
|
||||||
|
.Map(dest => dest.PageIndex, src => src.PageIndex)
|
||||||
|
.Map(dest => dest.PageSize, src => src.PageSize)
|
||||||
|
.Map(dest => dest.IsClubExclusive, src => src.IsClubExclusive != null ? (bool?)src.IsClubExclusive.Value : null);
|
||||||
|
|
||||||
|
config.NewConfig<GetLowStockProductsResponseDto, GetLowStockProductsResponse>()
|
||||||
|
.Map(dest => dest.MetaData, src => src.MetaData)
|
||||||
|
.Map(dest => dest.Products, src => src.Products);
|
||||||
|
|
||||||
|
config.NewConfig<LowStockProductDto, LowStockProduct>()
|
||||||
|
.Map(dest => dest.Id, src => src.Id)
|
||||||
|
.Map(dest => dest.Title, src => src.Title)
|
||||||
|
.Map(dest => dest.RemainingCount, src => src.RemainingCount)
|
||||||
|
.Map(dest => dest.Price, src => src.Price)
|
||||||
|
.Map(dest => dest.IsClubExclusive, src => src.IsClubExclusive);
|
||||||
|
|
||||||
|
// ToggleProductStatus mappings
|
||||||
|
config.NewConfig<ToggleProductStatusRequest, ToggleProductStatusCommand>()
|
||||||
|
.Map(dest => dest.ProductIds, src => src.ProductIds)
|
||||||
|
.Map(dest => dest.Enable, src => src.Enable)
|
||||||
|
.Map(dest => dest.DefaultStock, src => src.DefaultStock);
|
||||||
|
|
||||||
|
config.NewConfig<ToggleProductStatusResponseDto, ToggleProductStatusResponse>()
|
||||||
|
.Map(dest => dest.Total, src => src.UpdatedCount + src.FailedCount)
|
||||||
|
.Map(dest => dest.Succeeded, src => src.UpdatedCount)
|
||||||
|
.Map(dest => dest.Failed, src => src.FailedCount)
|
||||||
|
.Map(dest => dest.Errors, src => src.Errors.Select(msg => new BulkOperationError
|
||||||
|
{
|
||||||
|
ProductId = 0,
|
||||||
|
ErrorMessage = msg
|
||||||
|
}).ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ using CMSMicroservice.Application.ProductsCQ.Commands.UpdateProducts;
|
|||||||
using CMSMicroservice.Application.ProductsCQ.Commands.DeleteProducts;
|
using CMSMicroservice.Application.ProductsCQ.Commands.DeleteProducts;
|
||||||
using CMSMicroservice.Application.ProductsCQ.Queries.GetProducts;
|
using CMSMicroservice.Application.ProductsCQ.Queries.GetProducts;
|
||||||
using CMSMicroservice.Application.ProductsCQ.Queries.GetAllProductsByFilter;
|
using CMSMicroservice.Application.ProductsCQ.Queries.GetAllProductsByFilter;
|
||||||
|
using CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductPrices;
|
||||||
|
using CMSMicroservice.Application.ProductsCQ.Commands.BulkUpdateProductStock;
|
||||||
|
using CMSMicroservice.Application.ProductsCQ.Queries.GetLowStockProducts;
|
||||||
|
using CMSMicroservice.Application.ProductsCQ.Commands.ToggleProductStatus;
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
public class ProductsService : ProductsContract.ProductsContractBase
|
public class ProductsService : ProductsContract.ProductsContractBase
|
||||||
{
|
{
|
||||||
@@ -34,4 +38,24 @@ public class ProductsService : ProductsContract.ProductsContractBase
|
|||||||
{
|
{
|
||||||
return await _dispatchRequestToCQRS.Handle<GetAllProductsByFilterRequest, GetAllProductsByFilterQuery, GetAllProductsByFilterResponse>(request, context);
|
return await _dispatchRequestToCQRS.Handle<GetAllProductsByFilterRequest, GetAllProductsByFilterQuery, GetAllProductsByFilterResponse>(request, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<BulkUpdateProductPricesResponse> BulkUpdateProductPrices(BulkUpdateProductPricesRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<BulkUpdateProductPricesRequest, BulkUpdateProductPricesCommand, BulkUpdateProductPricesResponse>(request, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<BulkUpdateProductStockResponse> BulkUpdateProductStock(BulkUpdateProductStockRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<BulkUpdateProductStockRequest, BulkUpdateProductStockCommand, BulkUpdateProductStockResponse>(request, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<GetLowStockProductsResponse> GetLowStockProducts(GetLowStockProductsRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<GetLowStockProductsRequest, GetLowStockProductsQuery, GetLowStockProductsResponse>(request, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<ToggleProductStatusResponse> ToggleProductStatus(ToggleProductStatusRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<ToggleProductStatusRequest, ToggleProductStatusCommand, ToggleProductStatusResponse>(request, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user