From b4e55197be873fffc97e55af52adf7a186ce96c9 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 19 Aug 2025 15:22:42 +0300 Subject: [PATCH] feat: enhance tariff processing with error handling and category filtering --- internal/ym/products/repository_api.go | 89 ++++++++++++++++++++------ pkg/utils/utils.go | 8 +++ 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/internal/ym/products/repository_api.go b/internal/ym/products/repository_api.go index 21c1199..e896858 100644 --- a/internal/ym/products/repository_api.go +++ b/internal/ym/products/repository_api.go @@ -3,9 +3,13 @@ package products import ( "context" "fmt" + "io" "math" + "net/http" "sipro-mps/internal/redis" + "sipro-mps/pkg/utils" "strconv" + "strings" "time" pb "sipro-mps/api/generated/v1/yandexmarket/products" @@ -15,6 +19,7 @@ import ( "git.denco.store/fakz9/yandex-go-client" "github.com/samber/lo" + "github.com/tidwall/gjson" ) const ( @@ -246,46 +251,90 @@ func (r *apiRepository) CalculateProductTariffs(ctx context.Context, marketplace func (r *apiRepository) setTariffsRateLimit() { ym.SetPathLimit("/tariffs/calculate", rateLimitWindow, tariffsRateLimit) } +func getCategoriesError(bodyData string) []int64 { + var result []int64 + for _, v := range gjson.Get(bodyData, "errors.#.message").Array() { + errorMessage := v.String() + split := strings.Split(errorMessage, "categories: ") + if len(split) < 2 { + continue + } + categories := strings.Split(strings.TrimSpace(split[1]), ",") + for _, category := range categories { + category = strings.TrimSpace(category) + if category == "" { + continue + } + isDigit, categoryId := utils.IsDigit(category) + if !isDigit || categoryId == nil { + continue + } + result = append(result, *categoryId) + } + } + return result +} -// processTariffChunk processes a single chunk of offers for tariff calculation -func (r *apiRepository) processTariffChunk(ctx context.Context, client *ymclient.APIClient, ymParameters *ymclient.CalculateTariffsParametersDTO, offerChunk []*pb.CalculateProductTariffsRequest_Offer, chunkIndex int) (*pb.CalculateProductTariffsResponse, error) { +func filterOffersByCategories(offers []*pb.CalculateProductTariffsRequest_Offer, categories []int64) []*pb.CalculateProductTariffsRequest_Offer { + return lo.Filter(offers, func(offer *pb.CalculateProductTariffsRequest_Offer, _ int) bool { + return lo.Contains(categories, offer.CategoryId) + }) +} + +func (r *apiRepository) makeTariffRequest(context context.Context, client *ymclient.APIClient, ymParameters *ymclient.CalculateTariffsParametersDTO, offerChunk []*pb.CalculateProductTariffsRequest_Offer) (*ymclient.CalculateTariffsResponse, *http.Response, error) { ymOffers := r.convertOffersToYM(offerChunk) if len(ymOffers) == 0 { - fmt.Printf("Skipping chunk %d: no valid offers\n", chunkIndex+1) - return nil, nil + return nil, nil, fmt.Errorf("no valid offers in chunk") } if ymParameters.CampaignId != nil && *ymParameters.CampaignId > 0 { ymParameters.SellingProgram = nil ymParameters.Frequency = nil ymParameters.Currency = nil - } else { ymParameters.CampaignId = nil } + ymRequest := ymclient.NewCalculateTariffsRequest(*ymParameters, ymOffers) - response, httpResp, err := client.TariffsAPI.CalculateTariffs(ctx). + response, httpResp, err := client.TariffsAPI.CalculateTariffs(context). CalculateTariffsRequest(*ymRequest). Execute() + return response, httpResp, err +} - if err != nil { - if httpResp != nil && httpResp.Body != nil { - bodyData := make([]byte, 2048) - _, httpErr := httpResp.Body.Read(bodyData) - if httpErr == nil { - fmt.Printf("Error response for chunk %d: %s\n", chunkIndex+1, string(bodyData)) +// processTariffChunk processes a single chunk of offers for tariff calculation +func (r *apiRepository) processTariffChunk(ctx context.Context, client *ymclient.APIClient, ymParameters *ymclient.CalculateTariffsParametersDTO, offerChunk []*pb.CalculateProductTariffsRequest_Offer, chunkIndex int) (*pb.CalculateProductTariffsResponse, error) { + globalError := fmt.Errorf("failed to process chunk %d", chunkIndex+1) + for range 2 { + response, httpResp, err := r.makeTariffRequest(ctx, client, ymParameters, offerChunk) + + if err != nil { + globalError = err + if httpResp == nil || httpResp.Body == nil { + return nil, fmt.Errorf("failed to call Yandex Market API for chunk %d: %w", chunkIndex+1, err) } + bodyData, httpErr := io.ReadAll(httpResp.Body) + if httpErr != nil { + return nil, fmt.Errorf("failed to read response body for chunk %d: %w", chunkIndex+1, httpErr) + } + fmt.Printf("Error response for chunk %d: %s\n", chunkIndex+1, string(bodyData)) + categoriesErrors := getCategoriesError(string(bodyData)) + if len(categoriesErrors) == 0 { + return nil, fmt.Errorf("failed to call Yandex Market API for chunk %d: %w", chunkIndex+1, err) + } + + offerChunk = filterOffersByCategories(offerChunk, categoriesErrors) + continue } - return nil, fmt.Errorf("failed to call Yandex Market API for chunk %d: %w", chunkIndex+1, err) + if response == nil || response.Result == nil { + + return nil, fmt.Errorf("Warning: received empty response for chunk %d\n", chunkIndex+1) + } + + return r.convertResponseToProto(response), nil } - - if response == nil || response.Result == nil { - fmt.Printf("Warning: received empty response for chunk %d\n", chunkIndex+1) - return nil, nil - } - - return r.convertResponseToProto(response), nil + return nil, globalError } // convertOffersToYM converts protobuf offers to Yandex Market format diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 7430313..23e27a2 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -6,6 +6,7 @@ import ( "fmt" "hash/fnv" "math" + "strconv" "github.com/golang-jwt/jwt/v5" ) @@ -123,3 +124,10 @@ func HashArray[T Hashable](arr []T) (string, error) { return fmt.Sprintf("%x", h.Sum64()), nil } + +func IsDigit(v string) (bool, *int64) { + if val, err := strconv.ParseInt(v, 10, 64); err == nil { + return true, &val + } + return false, nil +}