feat: ozon products recreator, small refactor

This commit is contained in:
2025-07-23 06:12:04 +03:00
parent fc07338998
commit 0c86228095
11 changed files with 2012 additions and 91 deletions

View File

@@ -1,6 +1,7 @@
package products
import (
"context"
"fmt"
"github.com/samber/lo"
"google.golang.org/grpc"
@@ -59,3 +60,53 @@ func (g *AdapterGRPC) GetListOfProducts(req *pb.GetListOfProductsRequest, stream
}
}
}
func (g *AdapterGRPC) GetProductAttributes(req *pb.GetProductAttributesRequest, stream pb.ProductsService_GetProductAttributesServer) error {
ctx := stream.Context()
resultChan := make(chan []pb.ProductAttributes, 10)
errChan := make(chan error)
productIds := lo.Map(req.ProductId, func(item int64, index int) int {
return int(item)
})
go g.repo.StreamProductAttributesCache(ctx, int(req.MarketplaceId), productIds, resultChan, errChan)
for {
select {
case <-ctx.Done():
return ctx.Err()
case attrs, ok := <-resultChan:
if !ok {
return nil
}
items := lo.Map(attrs, func(item pb.ProductAttributes, index int) *pb.ProductAttributes {
return &item
})
resp := pb.GetProductAttributesResponse{Items: items}
if err := stream.Send(&resp); err != nil {
return err
}
case err, ok := <-errChan:
if !ok {
return nil
}
return err
}
}
}
func (g *AdapterGRPC) DeleteProducts(ctx context.Context, req *pb.DeleteProductsRequest) (*pb.DeleteProductsResponse, error) {
responseItems, err := g.repo.DeleteProducts(ctx, int(req.MarketplaceId), req.Items)
if err != nil {
return nil, err
}
return &pb.DeleteProductsResponse{
MarketplaceId: req.MarketplaceId,
Items: responseItems,
},
nil
}
func (g *AdapterGRPC) CreateOrUpdateProducts(ctx context.Context, req *pb.CreateOrUpdateProductsRequest) (*pb.CreateOrUpdateProductsResponse, error) {
return g.repo.CreateOrUpdateProducts(ctx, int(req.MarketplaceId), req.Items)
}

View File

@@ -7,3 +7,8 @@ import (
type OzonProduct = ozon.ProductDetails
type PbProduct = pb.Product
type PbProductAttributes = pb.ProductAttributes
type PbDeleteProductRequestItem = pb.DeleteProductsRequest_Item
type PbDeleteProductResponseItem = pb.DeleteProductsResponse_Item
type PbCreateOrUpdateItem = pb.CreateOrUpdateProductsRequest_Item
type PbCreateOrUpdateProductsResponse = pb.CreateOrUpdateProductsResponse

View File

@@ -8,13 +8,21 @@ import (
//go:generate go run github.com/jmattheis/goverter/cmd/goverter gen -global "ignoreUnexported yes" .
// goverter:converter
// goverter:extend Int632ToInt64
// goverter:extend Int32ToInt64 Int64ToInt32
// goverter:matchIgnoreCase yes
// goverter:useZeroValueOnPointerInconsistency yes
type Converter interface {
// goverter:ignore state sizeCache unknownFields
ToProto(details *internal.ProductDetails) *proto.Product
AttributesToProto(attrs *internal.GetDescriptionOfProductsResult) *proto.ProductAttributes
// goverter:ignore ComplexAttributes GeoNames
ProtoToUpdateItem(update *proto.CreateOrUpdateProductsRequest_Item) *internal.CreateOrUpdateProductItem
}
func Int632ToInt64(i int32) int64 {
func Int32ToInt64(i int32) int64 {
return int64(i)
}
func Int64ToInt32(i int64) int32 {
return int32(i)
}

View File

@@ -11,6 +11,92 @@ import (
type ConverterImpl struct{}
func (c *ConverterImpl) AttributesToProto(source *ozon.GetDescriptionOfProductsResult) *products.ProductAttributes {
var pProductsProductAttributes *products.ProductAttributes
if source != nil {
var productsProductAttributes products.ProductAttributes
productsProductAttributes.Id = (*source).Id
productsProductAttributes.Barcode = (*source).Barcode
productsProductAttributes.Name = (*source).Name
productsProductAttributes.OfferId = (*source).OfferId
productsProductAttributes.TypeId = (*source).TypeId
productsProductAttributes.Height = mapping.Int32ToInt64((*source).Height)
productsProductAttributes.Depth = mapping.Int32ToInt64((*source).Depth)
productsProductAttributes.Width = mapping.Int32ToInt64((*source).Width)
productsProductAttributes.DimensionUnit = (*source).DimensionUnit
productsProductAttributes.Weight = mapping.Int32ToInt64((*source).Weight)
productsProductAttributes.WeightUnit = (*source).WeightUnit
productsProductAttributes.PrimaryImage = (*source).PrimaryImage
productsProductAttributes.ModelInfo = c.pOzonModelInfoToPProductsProductAttributes_ModelInfo((*source).ModelInfo)
if (*source).Images != nil {
productsProductAttributes.Images = make([]string, len((*source).Images))
for i := 0; i < len((*source).Images); i++ {
productsProductAttributes.Images[i] = (*source).Images[i]
}
}
if (*source).Attributes != nil {
productsProductAttributes.Attributes = make([]*products.ProductAttributes_Attributes, len((*source).Attributes))
for j := 0; j < len((*source).Attributes); j++ {
productsProductAttributes.Attributes[j] = c.ozonGetDescriptionOfProductsAttributeToPProductsProductAttributes_Attributes((*source).Attributes[j])
}
}
productsProductAttributes.ColorImage = (*source).ColorImage
productsProductAttributes.DescriptionCategoryId = (*source).DescriptionCategoryId
pProductsProductAttributes = &productsProductAttributes
}
return pProductsProductAttributes
}
func (c *ConverterImpl) ProtoToUpdateItem(source *products.CreateOrUpdateProductsRequest_Item) *ozon.CreateOrUpdateProductItem {
var pOzonCreateOrUpdateProductItem *ozon.CreateOrUpdateProductItem
if source != nil {
var ozonCreateOrUpdateProductItem ozon.CreateOrUpdateProductItem
if (*source).Attributes != nil {
ozonCreateOrUpdateProductItem.Attributes = make([]ozon.CreateOrUpdateAttribute, len((*source).Attributes))
for i := 0; i < len((*source).Attributes); i++ {
ozonCreateOrUpdateProductItem.Attributes[i] = c.pProductsCreateOrUpdateProductsRequest_Item_AttributesToOzonCreateOrUpdateAttribute((*source).Attributes[i])
}
}
ozonCreateOrUpdateProductItem.Barcode = (*source).Barcode
ozonCreateOrUpdateProductItem.DescriptionCategoryId = (*source).DescriptionCategoryId
ozonCreateOrUpdateProductItem.NewDescriptionCategoryId = (*source).NewDescriptionCategoryId
ozonCreateOrUpdateProductItem.ColorImage = (*source).ColorImage
ozonCreateOrUpdateProductItem.Depth = mapping.Int64ToInt32((*source).Depth)
ozonCreateOrUpdateProductItem.DimensionUnit = (*source).DimensionUnit
ozonCreateOrUpdateProductItem.Height = mapping.Int64ToInt32((*source).Height)
if (*source).Images != nil {
ozonCreateOrUpdateProductItem.Images = make([]string, len((*source).Images))
for j := 0; j < len((*source).Images); j++ {
ozonCreateOrUpdateProductItem.Images[j] = (*source).Images[j]
}
}
ozonCreateOrUpdateProductItem.PrimaryImage = (*source).PrimaryImage
if (*source).Images360 != nil {
ozonCreateOrUpdateProductItem.Images360 = make([]string, len((*source).Images360))
for k := 0; k < len((*source).Images360); k++ {
ozonCreateOrUpdateProductItem.Images360[k] = (*source).Images360[k]
}
}
ozonCreateOrUpdateProductItem.Name = (*source).Name
ozonCreateOrUpdateProductItem.OfferId = (*source).OfferId
ozonCreateOrUpdateProductItem.CurrencyCode = (*source).CurrencyCode
ozonCreateOrUpdateProductItem.OldPrice = (*source).OldPrice
if (*source).PdfList != nil {
ozonCreateOrUpdateProductItem.PDFList = make([]ozon.CreateOrUpdateProductPDF, len((*source).PdfList))
for l := 0; l < len((*source).PdfList); l++ {
ozonCreateOrUpdateProductItem.PDFList[l] = c.pProductsCreateOrUpdateProductsRequest_Item_PdfListItemToOzonCreateOrUpdateProductPDF((*source).PdfList[l])
}
}
ozonCreateOrUpdateProductItem.Price = (*source).Price
ozonCreateOrUpdateProductItem.ServiceType = (*source).ServiceType
ozonCreateOrUpdateProductItem.TypeId = (*source).TypeId
ozonCreateOrUpdateProductItem.VAT = ozon.VAT((*source).Vat)
ozonCreateOrUpdateProductItem.Weight = mapping.Int64ToInt32((*source).Weight)
ozonCreateOrUpdateProductItem.WeightUnit = (*source).WeightUnit
ozonCreateOrUpdateProductItem.Width = mapping.Int64ToInt32((*source).Width)
pOzonCreateOrUpdateProductItem = &ozonCreateOrUpdateProductItem
}
return pOzonCreateOrUpdateProductItem
}
func (c *ConverterImpl) ToProto(source *ozon.ProductDetails) *products.Product {
var pProductsProduct *products.Product
if source != nil {
@@ -25,14 +111,38 @@ func (c *ConverterImpl) ToProto(source *ozon.ProductDetails) *products.Product {
}
}
productsProduct.Statuses = c.ozonProductDetailsStatusToPProductsProduct_Status((*source).Statuses)
if (*source).Errors != nil {
productsProduct.Errors = make([]*products.Product_Error, len((*source).Errors))
for j := 0; j < len((*source).Errors); j++ {
productsProduct.Errors[j] = c.ozonProductDetailsErrorToPProductsProduct_Error((*source).Errors[j])
}
}
pProductsProduct = &productsProduct
}
return pProductsProduct
}
func (c *ConverterImpl) ozonGetDescriptionOfProductsAttributeToPProductsProductAttributes_Attributes(source ozon.GetDescriptionOfProductsAttribute) *products.ProductAttributes_Attributes {
var productsProductAttributes_Attributes products.ProductAttributes_Attributes
productsProductAttributes_Attributes.Id = source.Id
productsProductAttributes_Attributes.ComplexId = source.ComplexId
if source.Values != nil {
productsProductAttributes_Attributes.Values = make([]*products.ProductAttributes_Values, len(source.Values))
for i := 0; i < len(source.Values); i++ {
productsProductAttributes_Attributes.Values[i] = c.ozonGetDescriptionOfProductsAttributeValueToPProductsProductAttributes_Values(source.Values[i])
}
}
return &productsProductAttributes_Attributes
}
func (c *ConverterImpl) ozonGetDescriptionOfProductsAttributeValueToPProductsProductAttributes_Values(source ozon.GetDescriptionOfProductsAttributeValue) *products.ProductAttributes_Values {
var productsProductAttributes_Values products.ProductAttributes_Values
productsProductAttributes_Values.DictionaryValueId = source.DictionaryValueId
productsProductAttributes_Values.Value = source.Value
return &productsProductAttributes_Values
}
func (c *ConverterImpl) ozonProductDetailStockStockToPProductsProduct_Stock(source ozon.ProductDetailStockStock) *products.Product_Stock {
var productsProduct_Stock products.Product_Stock
productsProduct_Stock.Present = mapping.Int632ToInt64(source.Present)
productsProduct_Stock.Reserved = mapping.Int632ToInt64(source.Reserved)
productsProduct_Stock.Present = mapping.Int32ToInt64(source.Present)
productsProduct_Stock.Reserved = mapping.Int32ToInt64(source.Reserved)
productsProduct_Stock.SKU = source.SKU
productsProduct_Stock.Source = source.Source
return &productsProduct_Stock
@@ -48,8 +158,60 @@ func (c *ConverterImpl) ozonProductDetailStockToPProductsProduct_Stocks(source o
productsProduct_Stocks.HasStock = source.HasStock
return &productsProduct_Stocks
}
func (c *ConverterImpl) ozonProductDetailsErrorToPProductsProduct_Error(source ozon.ProductDetailsError) *products.Product_Error {
var productsProduct_Error products.Product_Error
productsProduct_Error.Code = source.Code
return &productsProduct_Error
}
func (c *ConverterImpl) ozonProductDetailsStatusToPProductsProduct_Status(source ozon.ProductDetailsStatus) *products.Product_Status {
var productsProduct_Status products.Product_Status
productsProduct_Status.StatusName = source.StatusName
return &productsProduct_Status
}
func (c *ConverterImpl) pOzonModelInfoToPProductsProductAttributes_ModelInfo(source *ozon.ModelInfo) *products.ProductAttributes_ModelInfo {
var pProductsProductAttributes_ModelInfo *products.ProductAttributes_ModelInfo
if source != nil {
var productsProductAttributes_ModelInfo products.ProductAttributes_ModelInfo
productsProductAttributes_ModelInfo.ModelId = (*source).ModelId
productsProductAttributes_ModelInfo.Count = (*source).Count
pProductsProductAttributes_ModelInfo = &productsProductAttributes_ModelInfo
}
return pProductsProductAttributes_ModelInfo
}
func (c *ConverterImpl) pProductsCreateOrUpdateProductsRequest_Item_AttributesToOzonCreateOrUpdateAttribute(source *products.CreateOrUpdateProductsRequest_Item_Attributes) ozon.CreateOrUpdateAttribute {
var ozonCreateOrUpdateAttribute ozon.CreateOrUpdateAttribute
if source != nil {
var ozonCreateOrUpdateAttribute2 ozon.CreateOrUpdateAttribute
ozonCreateOrUpdateAttribute2.ComplexId = (*source).ComplexId
ozonCreateOrUpdateAttribute2.Id = (*source).Id
if (*source).Values != nil {
ozonCreateOrUpdateAttribute2.Values = make([]ozon.CreateOrUpdateAttributeValue, len((*source).Values))
for i := 0; i < len((*source).Values); i++ {
ozonCreateOrUpdateAttribute2.Values[i] = c.pProductsCreateOrUpdateProductsRequest_Item_ValuesToOzonCreateOrUpdateAttributeValue((*source).Values[i])
}
}
ozonCreateOrUpdateAttribute = ozonCreateOrUpdateAttribute2
}
return ozonCreateOrUpdateAttribute
}
func (c *ConverterImpl) pProductsCreateOrUpdateProductsRequest_Item_PdfListItemToOzonCreateOrUpdateProductPDF(source *products.CreateOrUpdateProductsRequest_Item_PdfListItem) ozon.CreateOrUpdateProductPDF {
var ozonCreateOrUpdateProductPDF ozon.CreateOrUpdateProductPDF
if source != nil {
var ozonCreateOrUpdateProductPDF2 ozon.CreateOrUpdateProductPDF
ozonCreateOrUpdateProductPDF2.Index = (*source).Index
ozonCreateOrUpdateProductPDF2.Name = (*source).Name
ozonCreateOrUpdateProductPDF2.SrcUrl = (*source).SrcUrl
ozonCreateOrUpdateProductPDF = ozonCreateOrUpdateProductPDF2
}
return ozonCreateOrUpdateProductPDF
}
func (c *ConverterImpl) pProductsCreateOrUpdateProductsRequest_Item_ValuesToOzonCreateOrUpdateAttributeValue(source *products.CreateOrUpdateProductsRequest_Item_Values) ozon.CreateOrUpdateAttributeValue {
var ozonCreateOrUpdateAttributeValue ozon.CreateOrUpdateAttributeValue
if source != nil {
var ozonCreateOrUpdateAttributeValue2 ozon.CreateOrUpdateAttributeValue
ozonCreateOrUpdateAttributeValue2.DictionaryValueId = (*source).DictionaryValueId
ozonCreateOrUpdateAttributeValue2.Value = (*source).Value
ozonCreateOrUpdateAttributeValue = ozonCreateOrUpdateAttributeValue2
}
return ozonCreateOrUpdateAttributeValue
}

View File

@@ -6,4 +6,7 @@ type Repository interface {
GetAllProducts(ctx context.Context, marketplaceId int) ([]OzonProduct, error)
StreamAllProducts(ctx context.Context, marketplaceId int, resultChan chan<- []OzonProduct, errChan chan<- error)
StreamAllProductsCache(ctx context.Context, marketplaceId int, resultChan chan<- []PbProduct, errChan chan<- error)
StreamProductAttributesCache(ctx context.Context, marketplaceId int, productIds []int, resultChan chan<- []PbProductAttributes, errChan chan<- error)
DeleteProducts(ctx context.Context, marketplaceId int, items []*PbDeleteProductRequestItem) ([]*PbDeleteProductResponseItem, error)
CreateOrUpdateProducts(ctx context.Context, marketplaceId int, items []*PbCreateOrUpdateItem) (*PbCreateOrUpdateProductsResponse, error)
}

View File

@@ -6,12 +6,14 @@ import (
"fmt"
api "git.denco.store/fakz9/ozon-api-client/ozon"
"github.com/samber/lo"
pb "sipro-mps/api/generated/v1/ozon/products"
"sipro-mps/internal/marketplace"
"sipro-mps/internal/ozon"
"sipro-mps/internal/ozon/products/mapping/generated"
"sipro-mps/internal/redis"
"sipro-mps/internal/tasks/client"
"sipro-mps/internal/tasks/types"
"strconv"
"sync"
)
@@ -202,3 +204,125 @@ func (a *apiRepository) StreamAllProductsCache(ctx context.Context, marketplaceI
}
}
}
func (a *apiRepository) StreamProductAttributesCache(ctx context.Context, marketplaceId int, productIds []int, resultChan chan<- []PbProductAttributes, errChan chan<- error) {
defer close(resultChan)
defer close(errChan)
mp, err := a.marketplaceRepository.GetMarketplaceByID(ctx, marketplaceId)
if err != nil {
errChan <- err
return
}
ozonClient, err := ozon.GetClientFromMarketplace(mp)
if err != nil {
errChan <- err
return
}
converter := generated.ConverterImpl{}
for _, chunk := range lo.Chunk(productIds, 1000) {
chunkStrings := lo.Map(chunk, func(item int, index int) string {
return strconv.Itoa(item)
})
request := api.GetDescriptionOfProductsParams{
LastId: "",
Limit: 1000,
SortBy: "id",
SortDirection: "asc",
Filter: api.GetDescriptionOfProductsFilter{ProductId: chunkStrings},
}
response, err := ozonClient.Products().GetDescriptionOfProducts(ctx, &request)
if err != nil {
errChan <- err
return
}
resultChan <- lo.Map(response.Result, func(item api.GetDescriptionOfProductsResult, index int) pb.ProductAttributes {
return *converter.AttributesToProto(&item)
})
}
//ozonClient.Products().GetDescriptionOfProducts()
}
func (a *apiRepository) DeleteProducts(ctx context.Context, marketplaceId int, items []*PbDeleteProductRequestItem) ([]*PbDeleteProductResponseItem, error) {
mp, err := a.marketplaceRepository.GetMarketplaceByID(ctx, marketplaceId)
if err != nil {
return nil, err
}
ozonClient, err := ozon.GetClientFromMarketplace(mp)
if err != nil {
return nil, err
}
// Step 1: map the items into a slice
mapped := lo.Map(items, func(item *PbDeleteProductRequestItem, _ int) *PbDeleteProductResponseItem {
return &PbDeleteProductResponseItem{
ProductId: item.ProductId,
OfferId: item.OfferId,
IsDeleted: false,
}
})
// Step 2: create the map keyed by OfferId
result := lo.KeyBy(mapped, func(item *PbDeleteProductResponseItem) string {
return item.OfferId
})
for _, chunk := range lo.Chunk(items, 100) {
productIds := lo.Map(chunk, func(item *PbDeleteProductRequestItem, index int) int64 {
return item.ProductId
})
request := api.ArchiveProductParams{ProductId: productIds}
_, err := ozonClient.Products().ArchiveProduct(ctx, &request)
if err != nil {
return nil, err
}
}
for _, chunk := range lo.Chunk(items, 500) {
products := lo.Map(chunk, func(item *PbDeleteProductRequestItem, index int) api.RemoveProductWithoutSKUProduct {
return api.RemoveProductWithoutSKUProduct{OfferId: item.OfferId}
})
request := api.RemoveProductWithoutSKUParams{Products: products}
response, err := ozonClient.Products().RemoveProductWithoutSKU(ctx, &request)
if err != nil {
return nil, err
}
for _, status := range response.Status {
if item, ok := result[status.OfferId]; ok {
item.IsDeleted = status.IsDeleted
}
}
}
return lo.Values(result), nil
}
func (a *apiRepository) CreateOrUpdateProducts(ctx context.Context, marketplaceId int, items []*PbCreateOrUpdateItem) (*PbCreateOrUpdateProductsResponse, error) {
mp, err := a.marketplaceRepository.GetMarketplaceByID(ctx, marketplaceId)
if err != nil {
return nil, err
}
ozonClient, err := ozon.GetClientFromMarketplace(mp)
if err != nil {
return nil, err
}
converter := generated.ConverterImpl{}
pageSize := 100
result := make([]int64, (len(items)+pageSize-1)/pageSize)
for idx, chunk := range lo.Chunk(items, pageSize) {
mappedItems := lo.Map(chunk, func(item *PbCreateOrUpdateItem, index int) api.CreateOrUpdateProductItem {
return *converter.ProtoToUpdateItem(item)
})
request := api.CreateOrUpdateProductParams{Items: mappedItems}
response, err := ozonClient.Products().CreateOrUpdateProduct(ctx, &request)
if err != nil {
return nil, err
}
result[idx] = response.Result.TaskId
}
return &pb.CreateOrUpdateProductsResponse{TaskId: result}, nil
}