diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommand.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommand.cs new file mode 100644 index 0000000..013ec54 --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommand.cs @@ -0,0 +1,28 @@ +namespace BackOffice.BFF.Application.ProductsCQ.Commands.BulkUpdateProductPrices; + +public record BulkUpdateProductPricesCommand : IRequest +{ + public List Products { get; init; } = new(); +} + +public class ProductPriceUpdateDto +{ + public long ProductId { get; set; } + public long NewPrice { get; set; } + public int? NewDiscount { get; set; } + public int? NewClubDiscountPercent { get; set; } +} + +public class BulkUpdateProductPricesResponseDto +{ + public int Total { get; set; } + public int Succeeded { get; set; } + public int Failed { get; set; } + public List Errors { get; set; } = new(); +} + +public class BulkOperationError +{ + public long ProductId { get; set; } + public string ErrorMessage { get; set; } = string.Empty; +} diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommandHandler.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommandHandler.cs new file mode 100644 index 0000000..b01a405 --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductPrices/BulkUpdateProductPricesCommandHandler.cs @@ -0,0 +1,34 @@ +using BackOffice.BFF.Application.Common.Interfaces; +using CMSMicroservice.Protobuf.Protos.Products; + +namespace BackOffice.BFF.Application.ProductsCQ.Commands.BulkUpdateProductPrices; + +public class BulkUpdateProductPricesCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public BulkUpdateProductPricesCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(BulkUpdateProductPricesCommand request, CancellationToken cancellationToken) + { + var protoRequest = new BulkUpdateProductPricesRequest(); + + foreach (var product in request.Products) + { + protoRequest.Products.Add(new ProductPriceUpdate + { + ProductId = product.ProductId, + NewPrice = product.NewPrice, + NewDiscount = product.NewDiscount, + NewClubDiscountPercent = product.NewClubDiscountPercent + }); + } + + var response = await _context.Products.BulkUpdateProductPricesAsync(protoRequest, cancellationToken: cancellationToken); + + return response.Adapt(); + } +} diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommand.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommand.cs new file mode 100644 index 0000000..e25b3f0 --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommand.cs @@ -0,0 +1,34 @@ +namespace BackOffice.BFF.Application.ProductsCQ.Commands.BulkUpdateProductStock; + +public record BulkUpdateProductStockCommand : IRequest +{ + public List Products { get; init; } = new(); + public StockUpdateType UpdateType { get; init; } +} + +public class ProductStockUpdateDto +{ + public long ProductId { get; set; } + public int Quantity { get; set; } +} + +public enum StockUpdateType +{ + Set = 0, + Add = 1, + Subtract = 2 +} + +public class BulkUpdateProductStockResponseDto +{ + public int Total { get; set; } + public int Succeeded { get; set; } + public int Failed { get; set; } + public List Errors { get; set; } = new(); +} + +public class BulkOperationError +{ + public long ProductId { get; set; } + public string ErrorMessage { get; set; } = string.Empty; +} diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommandHandler.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommandHandler.cs new file mode 100644 index 0000000..7ee5dd9 --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/BulkUpdateProductStock/BulkUpdateProductStockCommandHandler.cs @@ -0,0 +1,35 @@ +using BackOffice.BFF.Application.Common.Interfaces; +using CMSMicroservice.Protobuf.Protos.Products; + +namespace BackOffice.BFF.Application.ProductsCQ.Commands.BulkUpdateProductStock; + +public class BulkUpdateProductStockCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public BulkUpdateProductStockCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(BulkUpdateProductStockCommand request, CancellationToken cancellationToken) + { + var protoRequest = new BulkUpdateProductStockRequest + { + UpdateType = (CMSMicroservice.Protobuf.Protos.Products.StockUpdateType)request.UpdateType + }; + + foreach (var product in request.Products) + { + protoRequest.Products.Add(new ProductStockUpdate + { + ProductId = product.ProductId, + Quantity = product.Quantity + }); + } + + var response = await _context.Products.BulkUpdateProductStockAsync(protoRequest, cancellationToken: cancellationToken); + + return response.Adapt(); + } +} diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommand.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommand.cs new file mode 100644 index 0000000..2f9927a --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommand.cs @@ -0,0 +1,22 @@ +namespace BackOffice.BFF.Application.ProductsCQ.Commands.ToggleProductStatus; + +public record ToggleProductStatusCommand : IRequest +{ + public List ProductIds { get; init; } = new(); + public bool Enable { get; init; } + public int DefaultStock { get; init; } = 1; +} + +public class ToggleProductStatusResponseDto +{ + public int Total { get; set; } + public int Succeeded { get; set; } + public int Failed { get; set; } + public List Errors { get; set; } = new(); +} + +public class BulkOperationError +{ + public long ProductId { get; set; } + public string ErrorMessage { get; set; } = string.Empty; +} diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommandHandler.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommandHandler.cs new file mode 100644 index 0000000..60c93ac --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/ToggleProductStatus/ToggleProductStatusCommandHandler.cs @@ -0,0 +1,29 @@ +using BackOffice.BFF.Application.Common.Interfaces; +using CMSMicroservice.Protobuf.Protos.Products; + +namespace BackOffice.BFF.Application.ProductsCQ.Commands.ToggleProductStatus; + +public class ToggleProductStatusCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public ToggleProductStatusCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(ToggleProductStatusCommand request, CancellationToken cancellationToken) + { + var protoRequest = new ToggleProductStatusRequest + { + Enable = request.Enable, + DefaultStock = request.DefaultStock + }; + + protoRequest.ProductIds.AddRange(request.ProductIds); + + var response = await _context.Products.ToggleProductStatusAsync(protoRequest, cancellationToken: cancellationToken); + + return response.Adapt(); + } +} diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQuery.cs b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQuery.cs new file mode 100644 index 0000000..8b4271f --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQuery.cs @@ -0,0 +1,26 @@ +using BackOffice.BFF.Application.Common.Models; + +namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetLowStockProducts; + +public record GetLowStockProductsQuery : IRequest +{ + public int Threshold { get; init; } = 10; + public int PageIndex { get; init; } = 1; + public int PageSize { get; init; } = 20; + public bool? IsClubExclusive { get; init; } +} + +public class GetLowStockProductsResponseDto +{ + public MetaData MetaData { get; set; } = new(); + public List Products { get; set; } = new(); +} + +public class LowStockProductDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public int RemainingCount { get; set; } + public long Price { get; set; } + public bool IsClubExclusive { get; set; } +} diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryHandler.cs b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryHandler.cs new file mode 100644 index 0000000..48c84fc --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetLowStockProducts/GetLowStockProductsQueryHandler.cs @@ -0,0 +1,33 @@ +using BackOffice.BFF.Application.Common.Interfaces; +using CMSMicroservice.Protobuf.Protos.Products; + +namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetLowStockProducts; + +public class GetLowStockProductsQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetLowStockProductsQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetLowStockProductsQuery request, CancellationToken cancellationToken) + { + var protoRequest = new GetLowStockProductsRequest + { + Threshold = request.Threshold, + PageIndex = request.PageIndex, + PageSize = request.PageSize + }; + + if (request.IsClubExclusive.HasValue) + { + protoRequest.IsClubExclusive = request.IsClubExclusive.Value; + } + + var response = await _context.Products.GetLowStockProductsAsync(protoRequest, cancellationToken: cancellationToken); + + return response.Adapt(); + } +} diff --git a/src/Protobufs/BackOffice.BFF.Products.Protobuf/BackOffice.BFF.Products.Protobuf.csproj b/src/Protobufs/BackOffice.BFF.Products.Protobuf/BackOffice.BFF.Products.Protobuf.csproj index c9b37eb..f4157d1 100644 --- a/src/Protobufs/BackOffice.BFF.Products.Protobuf/BackOffice.BFF.Products.Protobuf.csproj +++ b/src/Protobufs/BackOffice.BFF.Products.Protobuf/BackOffice.BFF.Products.Protobuf.csproj @@ -24,7 +24,11 @@ - + + + + + diff --git a/src/Protobufs/BackOffice.BFF.Products.Protobuf/Protos/products.proto b/src/Protobufs/BackOffice.BFF.Products.Protobuf/Protos/products.proto index ab74618..0c1f73c 100644 --- a/src/Protobufs/BackOffice.BFF.Products.Protobuf/Protos/products.proto +++ b/src/Protobufs/BackOffice.BFF.Products.Protobuf/Protos/products.proto @@ -2,314 +2,275 @@ syntax = "proto3"; package products; +import "public_messages.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/wrappers.proto"; -import "google/protobuf/duration.proto"; +import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "google/api/annotations.proto"; -option csharp_namespace = "BackOffice.BFF.Products.Protobuf.Protos.Products"; +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.Products"; service ProductsContract { - rpc CreateNewProducts(CreateNewProductsRequest) returns (CreateNewProductsResponse){ - option (google.api.http) = { - post: "/CreateNewProducts" - body: "*" + rpc CreateNewProducts(CreateNewProductsRequest) returns (CreateNewProductsResponse){ + option (google.api.http) = { + post: "/CreateNewProducts" + body: "*" + }; }; - }; - rpc UpdateProducts(UpdateProductsRequest) returns (google.protobuf.Empty){ - option (google.api.http) = { - put: "/UpdateProducts" - body: "*" + rpc UpdateProducts(UpdateProductsRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/UpdateProducts" + body: "*" + }; }; - }; - rpc DeleteProducts(DeleteProductsRequest) returns (google.protobuf.Empty){ - option (google.api.http) = { - delete: "/DeleteProducts" - body: "*" + rpc DeleteProducts(DeleteProductsRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/DeleteProducts" + body: "*" + }; }; - }; - rpc GetProducts(GetProductsRequest) returns (GetProductsResponse){ - option (google.api.http) = { - get: "/GetProducts" + rpc GetProducts(GetProductsRequest) returns (GetProductsResponse){ + option (google.api.http) = { + get: "/GetProducts" + + }; }; - }; - rpc GetAllProductsByFilter(GetAllProductsByFilterRequest) returns (GetAllProductsByFilterResponse){ - option (google.api.http) = { - get: "/GetAllProductsByFilter" + rpc GetAllProductsByFilter(GetAllProductsByFilterRequest) returns (GetAllProductsByFilterResponse){ + option (google.api.http) = { + get: "/GetAllProductsByFilter" + + }; }; - }; - - rpc AddProductImage(AddProductImageRequest) returns (AddProductImageResponse){ - option (google.api.http) = { - post: "/AddProductImage" - body: "*" + rpc BulkUpdateProductPrices(BulkUpdateProductPricesRequest) returns (BulkUpdateProductPricesResponse){ + option (google.api.http) = { + post: "/BulkUpdateProductPrices" + body: "*" + }; }; - }; - - rpc GetProductGallery(GetProductGalleryRequest) returns (GetProductGalleryResponse){ - option (google.api.http) = { - get: "/GetProductGallery" + rpc BulkUpdateProductStock(BulkUpdateProductStockRequest) returns (BulkUpdateProductStockResponse){ + option (google.api.http) = { + post: "/BulkUpdateProductStock" + body: "*" + }; }; - }; - - rpc RemoveProductImage(RemoveProductImageRequest) returns (google.protobuf.Empty){ - option (google.api.http) = { - delete: "/RemoveProductImage" - body: "*" + rpc GetLowStockProducts(GetLowStockProductsRequest) returns (GetLowStockProductsResponse){ + option (google.api.http) = { + get: "/GetLowStockProducts" + }; }; - }; - - rpc GetCategories(GetCategoriesRequest) returns (GetCategoriesResponse){ - option (google.api.http) = { - get: "/GetCategories" + rpc ToggleProductStatus(ToggleProductStatusRequest) returns (ToggleProductStatusResponse){ + option (google.api.http) = { + post: "/ToggleProductStatus" + body: "*" + }; }; - }; - - rpc UpdateProductCategories(UpdateProductCategoriesRequest) returns (google.protobuf.Empty){ - option (google.api.http) = { - post: "/UpdateProductCategories" - body: "*" - }; - }; - - rpc GetProductsForCategory(GetProductsForCategoryRequest) returns (GetProductsForCategoryResponse){ - option (google.api.http) = { - get: "/GetProductsForCategory" - }; - }; - - rpc UpdateCategoryProducts(UpdateCategoryProductsRequest) returns (google.protobuf.Empty){ - option (google.api.http) = { - post: "/UpdateCategoryProducts" - body: "*" - }; - }; } - -message ImageFileModel -{ - bytes file = 1; - string file_name = 2; - string mime = 3; -} - message CreateNewProductsRequest { - string title = 1; - string description = 2; - string short_infomation = 3; - string full_information = 4; - int64 price = 5; - int32 discount = 6; - int32 rate = 7; - ImageFileModel image_file = 8; - ImageFileModel thumbnail_file = 9; - // لیست شناسه دسته‌بندی‌های محصول - repeated int64 category_ids = 10; + string title = 1; + string description = 2; + string short_infomation = 3; + string full_information = 4; + int64 price = 5; + int32 discount = 6; + int32 rate = 7; + string image_path = 8; + string thumbnail_path = 9; + int32 sale_count = 10; + int32 view_count = 11; + int32 remaining_count = 12; + // لیست شناسه دسته‌بندی‌های محصول + repeated int64 category_ids = 13; } - message CreateNewProductsResponse { - int64 id = 1; + int64 id = 1; } - message UpdateProductsRequest { - int64 id = 1; - string title = 2; - string description = 3; - string short_infomation = 4; - string full_information = 5; - int64 price = 6; - int32 discount = 7; - int32 rate = 8; - string image_path = 9; - string thumbnail_path = 10; - ImageFileModel image_file = 11; - ImageFileModel thumbnail_file = 12; - // لیست شناسه دسته‌بندی‌های محصول - repeated int64 category_ids = 13; + int64 id = 1; + string title = 2; + string description = 3; + string short_infomation = 4; + string full_information = 5; + int64 price = 6; + int32 discount = 7; + int32 rate = 8; + string image_path = 9; + string thumbnail_path = 10; + int32 sale_count = 11; + int32 view_count = 12; + int32 remaining_count = 13; + // لیست شناسه دسته‌بندی‌های محصول + repeated int64 category_ids = 14; } - message DeleteProductsRequest { - int64 id = 1; + int64 id = 1; } - message GetProductsRequest { - int64 id = 1; + int64 id = 1; } - message GetProductsResponse { - int64 id = 1; - string title = 2; - string description = 3; - string short_infomation = 4; - string full_information = 5; - int64 price = 6; - int32 discount = 7; - int32 rate = 8; - string image_path = 9; - string thumbnail_path = 10; - int32 sale_count = 11; - int32 view_count = 12; - int32 remaining_count = 13; - // لیست شناسه دسته‌بندی‌های محصول - repeated int64 category_ids = 14; + int64 id = 1; + string title = 2; + string description = 3; + string short_infomation = 4; + string full_information = 5; + int64 price = 6; + int32 discount = 7; + int32 rate = 8; + string image_path = 9; + string thumbnail_path = 10; + int32 sale_count = 11; + int32 view_count = 12; + int32 remaining_count = 13; + // لیست شناسه دسته‌بندی‌های محصول + repeated int64 category_ids = 14; } - message GetAllProductsByFilterRequest { - PaginationState pagination_state = 1; - google.protobuf.StringValue sort_by = 2; - GetAllProductsByFilterFilter filter = 3; + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllProductsByFilterFilter filter = 3; } - message GetAllProductsByFilterFilter { - google.protobuf.Int64Value id = 1; - google.protobuf.StringValue title = 2; - google.protobuf.StringValue description = 3; - google.protobuf.StringValue short_infomation = 4; - google.protobuf.StringValue full_information = 5; - google.protobuf.Int64Value price = 6; - google.protobuf.Int32Value discount = 7; - google.protobuf.Int32Value rate = 8; + google.protobuf.Int64Value id = 1; + google.protobuf.StringValue title = 2; + google.protobuf.StringValue description = 3; + google.protobuf.StringValue short_infomation = 4; + google.protobuf.StringValue full_information = 5; + google.protobuf.Int64Value price = 6; + google.protobuf.Int32Value discount = 7; + google.protobuf.Int32Value rate = 8; + google.protobuf.StringValue image_path = 9; + google.protobuf.StringValue thumbnail_path = 10; + google.protobuf.Int32Value sale_count = 11; + google.protobuf.Int32Value view_count = 12; + google.protobuf.Int32Value remaining_count = 13; + google.protobuf.Int64Value category_id = 14; } - message GetAllProductsByFilterResponse { - MetaData meta_data = 1; - repeated GetAllProductsByFilterResponseModel models = 2; + messages.MetaData meta_data = 1; + repeated GetAllProductsByFilterResponseModel models = 2; } - message GetAllProductsByFilterResponseModel { - int64 id = 1; - string title = 2; - string description = 3; - string short_infomation = 4; - string full_information = 5; - int64 price = 6; - int32 discount = 7; - int32 rate = 8; - string image_path = 9; - string thumbnail_path = 10; - int32 sale_count = 11; - int32 view_count = 12; - int32 remaining_count = 13; - // لیست شناسه دسته‌بندی‌های محصول - repeated int64 category_ids = 14; + int64 id = 1; + string title = 2; + string description = 3; + string short_infomation = 4; + string full_information = 5; + int64 price = 6; + int32 discount = 7; + int32 rate = 8; + string image_path = 9; + string thumbnail_path = 10; + int32 sale_count = 11; + int32 view_count = 12; + int32 remaining_count = 13; + // لیست شناسه دسته‌بندی‌های محصول + repeated int64 category_ids = 14; } -message PaginationState +// Bulk Update Product Prices +message BulkUpdateProductPricesRequest { - int32 page_number = 1; - int32 page_size = 2; + repeated ProductPriceUpdate products = 1; } -message MetaData +message ProductPriceUpdate { - int64 current_page = 1; - int64 total_page = 2; - int64 page_size = 3; - int64 total_count = 4; - bool has_previous = 5; - bool has_next = 6; + int64 product_id = 1; + int64 new_price = 2; + google.protobuf.Int32Value new_discount = 3; + google.protobuf.Int32Value new_club_discount_percent = 4; } -message DecimalValue +message BulkUpdateProductPricesResponse { - int64 units = 1; - sfixed32 nanos = 2; + int32 total = 1; + int32 succeeded = 2; + int32 failed = 3; + repeated BulkOperationError errors = 4; } -message AddProductImageRequest +message BulkOperationError { - int64 product_id = 1; - string title = 2; - ImageFileModel image_file = 3; + int64 product_id = 1; + string error_message = 2; } -message AddProductImageResponse +// Bulk Update Product Stock +message BulkUpdateProductStockRequest { - int64 product_gallery_id = 1; - int64 product_image_id = 2; - string title = 3; - string image_path = 4; - string image_thumbnail_path = 5; + repeated ProductStockUpdate products = 1; + StockUpdateType update_type = 2; } -message GetProductGalleryRequest +message ProductStockUpdate { - int64 product_id = 1; + int64 product_id = 1; + int32 quantity = 2; } -message ProductGalleryItem +enum StockUpdateType { - int64 product_gallery_id = 1; - int64 product_image_id = 2; - string title = 3; - string image_path = 4; - string image_thumbnail_path = 5; + SET = 0; + ADD = 1; + SUBTRACT = 2; } -message GetProductGalleryResponse +message BulkUpdateProductStockResponse { - repeated ProductGalleryItem items = 1; + int32 total = 1; + int32 succeeded = 2; + int32 failed = 3; + repeated BulkOperationError errors = 4; } -message RemoveProductImageRequest +// Get Low Stock Products +message GetLowStockProductsRequest { - int64 product_gallery_id = 1; + int32 threshold = 1; + int32 page_index = 2; + int32 page_size = 3; + google.protobuf.BoolValue is_club_exclusive = 4; } -message CategoryItem +message GetLowStockProductsResponse { - int64 id = 1; - string title = 2; - bool selected = 3; + messages.MetaData meta_data = 1; + repeated LowStockProduct products = 2; } -message GetCategoriesRequest +message LowStockProduct { - int64 product_id = 1; + int64 id = 1; + string title = 2; + int32 remaining_count = 3; + int64 price = 4; + bool is_club_exclusive = 5; } -message GetCategoriesResponse +// Toggle Product Status +message ToggleProductStatusRequest { - repeated CategoryItem items = 1; + repeated int64 product_ids = 1; + bool enable = 2; + int32 default_stock = 3; } -message UpdateProductCategoriesRequest +message ToggleProductStatusResponse { - int64 product_id = 1; - repeated int64 category_ids = 2; -} - -message CategoryProductItem -{ - int64 id = 1; - string title = 2; - bool selected = 3; -} - -message GetProductsForCategoryRequest -{ - int64 category_id = 1; -} - -message GetProductsForCategoryResponse -{ - repeated CategoryProductItem items = 1; -} - -message UpdateCategoryProductsRequest -{ - int64 category_id = 1; - repeated int64 product_ids = 2; + int32 total = 1; + int32 succeeded = 2; + int32 failed = 3; + repeated BulkOperationError errors = 4; }