feat: enhance tariff processing with error handling and category filtering

This commit is contained in:
2025-08-19 15:22:42 +03:00
parent e2b4b22095
commit b4e55197be
2 changed files with 77 additions and 20 deletions

View File

@@ -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

View File

@@ -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
}