Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99b0a24d48 | ||
|
|
e5867813ab | ||
|
|
b0e1210f34 | ||
|
|
049b02835b | ||
|
|
6cf4ae89e3 | ||
|
|
8a6ca3b2eb | ||
|
|
25051ee802 |
11
client.go
11
client.go
@@ -37,17 +37,20 @@ func NewMockClient(handler http.HandlerFunc) *Client {
|
||||
}
|
||||
|
||||
func (c Client) newRequest(ctx context.Context, method string, uri string, body interface{}) (*http.Request, error) {
|
||||
var err error
|
||||
var bodyJson []byte
|
||||
|
||||
// Set default values for empty fields if `default` tag is present
|
||||
// And body is not nil
|
||||
if body != nil {
|
||||
if err := getDefaultValues(reflect.ValueOf(body)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
bodyJson, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
bodyJson, err = json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
uri, err = url.JoinPath(c.baseUrl, uri)
|
||||
|
||||
42
core.go
42
core.go
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -162,3 +163,44 @@ func TimeFromString(t *testing.T, format, datetime string) time.Time {
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
const ShortDateLayout = "2006-01-02"
|
||||
|
||||
// Do not use this structure for responses
|
||||
// as there are no ways to unmarshal to any layout
|
||||
// and leave nil if json field is null
|
||||
type TimeFormat struct {
|
||||
time.Time
|
||||
layout string
|
||||
}
|
||||
|
||||
func NewTimeFormat(t time.Time, layout string) *TimeFormat {
|
||||
return &TimeFormat{
|
||||
Time: t,
|
||||
layout: layout,
|
||||
}
|
||||
}
|
||||
|
||||
func newTimeLayout(layout string) *TimeFormat {
|
||||
return &TimeFormat{
|
||||
layout: layout,
|
||||
}
|
||||
}
|
||||
|
||||
func (rd *TimeFormat) UnmarshalJSON(b []byte) error {
|
||||
var err error
|
||||
|
||||
s := strings.Trim(string(b), `"`) // remove quotes
|
||||
|
||||
// Added for extra accuracy
|
||||
// encoding/json won't invoke this method if field is null
|
||||
if s == "null" {
|
||||
return nil
|
||||
}
|
||||
rd.Time, err = time.Parse(rd.layout, s)
|
||||
return err
|
||||
}
|
||||
|
||||
func (rd *TimeFormat) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`%q`, rd.Time.Format(rd.layout))), nil
|
||||
}
|
||||
|
||||
96
core_test.go
96
core_test.go
@@ -1,8 +1,10 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -54,3 +56,97 @@ func TestDefaultValues(t *testing.T) {
|
||||
assert.Equal(t, "empty_string", req.OptionalStructure.EmptyField)
|
||||
assert.Equal(t, (*DefaultStructure)(nil), req.EmptyOptionalStructure)
|
||||
}
|
||||
|
||||
func TestTimeFormat(t *testing.T) {
|
||||
t.Run("Time Format Marshalling", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
ft *TimeFormat
|
||||
layout string
|
||||
expectedJSON string
|
||||
diff time.Duration
|
||||
}{
|
||||
{
|
||||
ft: NewTimeFormat(time.Date(2024, 4, 30, 15, 42, 12, 55, time.FixedZone("Test Zone", 0)), ShortDateLayout),
|
||||
layout: ShortDateLayout,
|
||||
expectedJSON: `"2024-04-30"`,
|
||||
diff: time.Hour * 24,
|
||||
},
|
||||
{
|
||||
ft: NewTimeFormat(time.Date(2024, 4, 30, 0, 0, 0, 0, time.FixedZone("Test Zone", 0)), ShortDateLayout),
|
||||
layout: ShortDateLayout,
|
||||
expectedJSON: `"2024-04-30"`,
|
||||
diff: time.Hour * 24,
|
||||
},
|
||||
{
|
||||
ft: NewTimeFormat(time.Time{}, ShortDateLayout),
|
||||
layout: ShortDateLayout,
|
||||
expectedJSON: `"0001-01-01"`,
|
||||
diff: time.Hour * 24,
|
||||
},
|
||||
{
|
||||
ft: nil,
|
||||
layout: ShortDateLayout,
|
||||
expectedJSON: `null`,
|
||||
diff: time.Hour * 24,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
marshaled, err := json.Marshal(tc.ft)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, tc.expectedJSON, string(marshaled))
|
||||
|
||||
unmarshaled := newTimeLayout(tc.layout)
|
||||
err = json.Unmarshal(marshaled, unmarshaled)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
if tc.ft != nil {
|
||||
diffedTime := tc.ft.Add(-tc.diff)
|
||||
assert.Equal(t, true, diffedTime.Before(unmarshaled.Time) || diffedTime.Equal(unmarshaled.Time))
|
||||
assert.Equal(t, true, tc.ft.After(unmarshaled.Time) || tc.ft.Equal(unmarshaled.Time))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Time Format in structure Marshalling", func(t *testing.T) {
|
||||
type test struct {
|
||||
Date *TimeFormat `json:"date"`
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
structure *test
|
||||
layout string
|
||||
expectedJSON string
|
||||
diff time.Duration
|
||||
}{
|
||||
{
|
||||
structure: &test{Date: NewTimeFormat(time.Date(2024, 4, 30, 5, 4, 7, 20, time.FixedZone("Test Zone", 0)), ShortDateLayout)},
|
||||
layout: ShortDateLayout,
|
||||
expectedJSON: `{"date":"2024-04-30"}`,
|
||||
diff: time.Hour * 24,
|
||||
},
|
||||
{
|
||||
structure: &test{Date: nil},
|
||||
layout: ShortDateLayout,
|
||||
expectedJSON: `{"date":null}`,
|
||||
diff: time.Hour * 24,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
marshaled, err := json.Marshal(tc.structure)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, tc.expectedJSON, string(marshaled))
|
||||
|
||||
unmarshaled := &test{Date: newTimeLayout(tc.layout)}
|
||||
err = json.Unmarshal(marshaled, unmarshaled)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
if tc.structure != nil && tc.structure.Date != nil {
|
||||
diffedTime := tc.structure.Date.Add(-tc.diff)
|
||||
assert.Equal(t, true, diffedTime.Before(unmarshaled.Date.Time) || diffedTime.Equal(unmarshaled.Date.Time))
|
||||
assert.Equal(t, true, tc.structure.Date.After(unmarshaled.Date.Time) || tc.structure.Date.Equal(unmarshaled.Date.Time))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package ozon
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
core "github.com/diphantxm/ozon-api-client"
|
||||
)
|
||||
@@ -14,10 +13,10 @@ type Analytics struct {
|
||||
|
||||
type GetAnalyticsDataParams struct {
|
||||
// Date from which the data will be in the report
|
||||
DateFrom time.Time `json:"date_from"`
|
||||
DateFrom *core.TimeFormat `json:"date_from"`
|
||||
|
||||
// Date up to which the data will be in the report
|
||||
DateTo time.Time `json:"date_to"`
|
||||
DateTo *core.TimeFormat `json:"date_to"`
|
||||
|
||||
// Items Enum: "unknownDimension" "sku" "spu" "day" "week" "month" "year" "category1" "category2" "category3" "category4" "brand" "modelID"
|
||||
// Data grouping available to all sellers:
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
core "github.com/diphantxm/ozon-api-client"
|
||||
)
|
||||
@@ -22,8 +23,8 @@ func TestGetAnalyticsData(t *testing.T) {
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
&GetAnalyticsDataParams{
|
||||
DateFrom: core.TimeFromString(t, "2006-01-02", "2020-09-01"),
|
||||
DateTo: core.TimeFromString(t, "2006-01-02", "2021-10-15"),
|
||||
DateFrom: core.NewTimeFormat(time.Now().Add(time.Duration(30)*24*time.Hour), core.ShortDateLayout),
|
||||
DateTo: core.NewTimeFormat(time.Now(), core.ShortDateLayout),
|
||||
Dimension: []GetAnalyticsDataDimension{SKUDimension, DayDimension},
|
||||
Metrics: []GetAnalyticsDataFilterMetric{HistViewPDP},
|
||||
Sort: []GetAnalyticsDataSort{
|
||||
|
||||
140
ozon/common.go
140
ozon/common.go
@@ -1,6 +1,8 @@
|
||||
package ozon
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
testTimeout = 5 * time.Second
|
||||
@@ -91,16 +93,16 @@ const (
|
||||
Purchased ListDiscountRequestsStatus = "PURCHASED"
|
||||
)
|
||||
|
||||
type WorkingDay string
|
||||
type WorkingDay int
|
||||
|
||||
const (
|
||||
Mon WorkingDay = "1"
|
||||
Tue WorkingDay = "2"
|
||||
Wed WorkingDay = "3"
|
||||
Thu WorkingDay = "4"
|
||||
Fri WorkingDay = "5"
|
||||
Sat WorkingDay = "6"
|
||||
Sun WorkingDay = "7"
|
||||
Mon WorkingDay = 1
|
||||
Tue WorkingDay = 2
|
||||
Wed WorkingDay = 3
|
||||
Thu WorkingDay = 4
|
||||
Fri WorkingDay = 5
|
||||
Sat WorkingDay = 6
|
||||
Sun WorkingDay = 7
|
||||
)
|
||||
|
||||
type GetAnalyticsDataDimension string
|
||||
@@ -684,3 +686,123 @@ const (
|
||||
// Check is failed
|
||||
MandatoryMarkStatusFailed MandatoryMarkStatus = "failed"
|
||||
)
|
||||
|
||||
type GetCarriageStatus string
|
||||
|
||||
const (
|
||||
// acceptance in progress
|
||||
GetCarriageStatusReceived GetCarriageStatus = "received"
|
||||
|
||||
// closed after acceptance
|
||||
GetCarriageStatusClosed GetCarriageStatus = "closed"
|
||||
|
||||
GetCarriageStatusSended GetCarriageStatus = "sended"
|
||||
|
||||
GetCarriageStatusCancelled GetCarriageStatus = "cancelled"
|
||||
)
|
||||
|
||||
type TransactionOperationService string
|
||||
|
||||
const (
|
||||
// return of unclaimed products from the customer to the warehouse
|
||||
TransactionNotDelivered TransactionOperationService = "MarketplaceNotDeliveredCostItem"
|
||||
|
||||
// return from the customer to the warehouse after delivery
|
||||
TransactionReturnAfterDelivery TransactionOperationService = "TransactionOperationServiceNotDelivered"
|
||||
|
||||
// product delivery to the customer
|
||||
TransactionDelivery TransactionOperationService = "MarketplaceDeliveryCostItem"
|
||||
|
||||
// purchasing reviews on the platform
|
||||
TransactionSaleReviews TransactionOperationService = "MarketplaceSaleReviewsItem"
|
||||
|
||||
// products delivery to the Ozon warehouse (cross docking)
|
||||
TransactionItemAdForSupplierLogistic TransactionOperationService = "ItemAdvertisementForSupplierLogistic"
|
||||
|
||||
// product placement service
|
||||
TransactionServiceStorageItem TransactionOperationService = "MarketplaceServiceStorageItem"
|
||||
|
||||
// products promotion
|
||||
TransactionMarketingActionCost TransactionOperationService = "MarketplaceMarketingActionCostItem"
|
||||
|
||||
// promotion and selling on an instalment plan
|
||||
TransactionServiceItemInstallment TransactionOperationService = "MarketplaceServiceItemInstallment"
|
||||
|
||||
// mandatory products labeling
|
||||
TransactionServiceMarkingItems TransactionOperationService = "MarketplaceServiceItemMarkingItems"
|
||||
|
||||
// flexible payment schedule
|
||||
TransactionServiceFlexiblePaymentSchedule TransactionOperationService = "MarketplaceServiceItemFlexiblePaymentSchedule"
|
||||
|
||||
// picking up products for removal by the seller
|
||||
TransactionServiceReturnFromStock TransactionOperationService = "MarketplaceServiceItemReturnFromStock"
|
||||
|
||||
// forwarding trade
|
||||
TransactionItemAdForSupplierLogisticSeller TransactionOperationService = "ItemAdvertisementForSupplierLogisticSeller"
|
||||
|
||||
// last mile
|
||||
TransactionServiceDeliveryToCustomer TransactionOperationService = "MarketplaceServiceItemDelivToCustomer"
|
||||
|
||||
// pipeline
|
||||
TransactionServiceDirectFlowTrans TransactionOperationService = "MarketplaceServiceItemDirectFlowTrans"
|
||||
|
||||
// shipment processing
|
||||
TransactionServiceDropoffFF TransactionOperationService = "MarketplaceServiceItemDropoffFF"
|
||||
|
||||
// shipment processing
|
||||
TransactionServiceDropoffPVZ TransactionOperationService = "MarketplaceServiceItemDropoffPVZ"
|
||||
|
||||
// shipment processing
|
||||
TransactionServiceDropoffSC TransactionOperationService = "MarketplaceServiceItemDropoffSC"
|
||||
|
||||
// order packaging
|
||||
TransactionServiceFulfillment TransactionOperationService = "MarketplaceServiceItemFulfillment"
|
||||
|
||||
// picking products up by car from the seller's address (Pick-up)
|
||||
TransactionServicePickup TransactionOperationService = "MarketplaceServiceItemPickup"
|
||||
|
||||
// return processing
|
||||
TransactionServiceReturnAfterDelivToCustomer TransactionOperationService = "MarketplaceServiceItemReturnAfterDelivToCustomer"
|
||||
|
||||
// reverse pipeline
|
||||
TransactionServiceReturnFlowTrans TransactionOperationService = "MarketplaceServiceItemReturnFlowTrans"
|
||||
|
||||
// cancellation processing
|
||||
TransactionServiceReturnNotDelivToCustomer TransactionOperationService = "MarketplaceServiceItemReturnNotDelivToCustomer"
|
||||
|
||||
// unredeemed order processing
|
||||
TransactionServiceReturnPartGoodsCustomer TransactionOperationService = "MarketplaceServiceItemReturnPartGoodsCustomer"
|
||||
|
||||
// acquiring payment
|
||||
TransactionRedistributionOfAcquiringOperation TransactionOperationService = "MarketplaceRedistributionOfAcquiringOperation"
|
||||
|
||||
// FBS return short-term placement
|
||||
TransactionServiceAtPickupPointFBS TransactionOperationService = "MarketplaceReturnStorageServiceAtThePickupPointFbsItem"
|
||||
|
||||
// FBS return long-term placement
|
||||
TransactionServiceInWarehouseFBS TransactionOperationService = "MarketplaceReturnStorageServiceInTheWarehouseFbsItem"
|
||||
|
||||
// bulky products delivery
|
||||
TransactionServiceDeliveryKGT TransactionOperationService = "MarketplaceServiceItemDeliveryKGT"
|
||||
|
||||
// logistics
|
||||
TransactionServiceDirectFlowLogistic TransactionOperationService = "MarketplaceServiceItemDirectFlowLogistic"
|
||||
|
||||
// reverse logistics
|
||||
TransactionServiceReturnFlowLogistic TransactionOperationService = "MarketplaceServiceItemReturnFlowLogistic"
|
||||
|
||||
// "Seller's Bonus" promotion service
|
||||
TransactionServicePremiumCashbackIndPoints TransactionOperationService = "MarketplaceServicePremiumCashbackIndividualPoints"
|
||||
|
||||
// Premium promotion service, fixed commission
|
||||
TransactionServicePremiumPromotion TransactionOperationService = "MarketplaceServicePremiumPromotion"
|
||||
|
||||
// withholding for product shortage
|
||||
TransactionServiceWithHoldingForUndeliverableGoods TransactionOperationService = "OperationMarketplaceWithHoldingForUndeliverableGoods"
|
||||
|
||||
// drop-off service at the pick-up point
|
||||
TransactionServiceDropoffPPZ TransactionOperationService = "MarketplaceServiceItemDropoffPPZ"
|
||||
|
||||
// reissue of returns at the pick-up point
|
||||
TransactionServiceRedistributionReturnsPVZ TransactionOperationService = "MarketplaceServiceItemRedistributionReturnsPVZ"
|
||||
)
|
||||
|
||||
@@ -2962,7 +2962,7 @@ type GetCarriageResponse struct {
|
||||
RetryCount int32 `json:"retry_count"`
|
||||
|
||||
// Freight status
|
||||
Status string `json:"status"`
|
||||
Status GetCarriageStatus `json:"status"`
|
||||
|
||||
// Delivery method identifier
|
||||
TPLProviderId int64 `json:"tpl_provider_id"`
|
||||
|
||||
@@ -382,7 +382,7 @@ type ListTransactionsResultOperationPosting struct {
|
||||
|
||||
type ListTransactionsResultOperationService struct {
|
||||
// Service name
|
||||
Name string `json:"name"`
|
||||
Name TransactionOperationService `json:"name"`
|
||||
|
||||
// Price
|
||||
Price float64 `json:"price"`
|
||||
|
||||
@@ -27,7 +27,7 @@ type GetStocksInfoParams struct {
|
||||
|
||||
type GetStocksInfoFilter struct {
|
||||
// Filter by the offer_id parameter. It is possible to pass a list of values
|
||||
OfferId string `json:"offer_id,omitempty"`
|
||||
OfferId []string `json:"offer_id,omitempty"`
|
||||
|
||||
// Filter by the product_id parameter. It is possible to pass a list of values
|
||||
ProductId int64 `json:"product_id,omitempty"`
|
||||
@@ -890,6 +890,7 @@ type CreateOrUpdateProductResult struct {
|
||||
}
|
||||
|
||||
// This method allows you to create products and update their details
|
||||
// More info: https://docs.ozon.ru/api/seller/en/#operation/ProductAPI_ImportProductsV3
|
||||
func (c Products) CreateOrUpdateProduct(ctx context.Context, params *CreateOrUpdateProductParams) (*CreateOrUpdateProductResponse, error) {
|
||||
url := "/v3/product/import"
|
||||
|
||||
@@ -1235,7 +1236,7 @@ type ProductInfoResultPicture struct {
|
||||
//
|
||||
// If you called the `/v1/product/pictures/info` method, one of the statuses will appear:
|
||||
// - uploaded — image uploaded;
|
||||
// - failed — image was not uploaded
|
||||
// - pending — image was not uploaded
|
||||
State string `json:"state"`
|
||||
|
||||
// The link to the image in the public cloud storage. The image format is JPG or PNG
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestGetStocksInfo(t *testing.T) {
|
||||
Limit: 100,
|
||||
LastId: "",
|
||||
Filter: GetStocksInfoFilter{
|
||||
OfferId: "136834",
|
||||
OfferId: []string{"136834"},
|
||||
ProductId: 214887921,
|
||||
Visibility: "ALL",
|
||||
},
|
||||
|
||||
@@ -377,6 +377,7 @@ type GetCompetitorPriceResult struct {
|
||||
StrategyCompetitorProductURL string `json:"strategy_competitor_product_url"`
|
||||
}
|
||||
|
||||
// If you add a product to your pricing strategy, the method returns you the price and a link to the competitor's product
|
||||
func (c Strategies) GetCompetitorPrice(ctx context.Context, params *GetCompetitorPriceParams) (*GetCompetitorPriceResponse, error) {
|
||||
url := "/v1/pricing-strategy/product/info"
|
||||
|
||||
|
||||
@@ -21,27 +21,37 @@ func TestGetListOfWarehouses(t *testing.T) {
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
`{
|
||||
"result": [
|
||||
{
|
||||
"warehouse_id": 15588127982000,
|
||||
"name": "Proffi (Панорама Групп)",
|
||||
"is_rfbs": false
|
||||
},
|
||||
{
|
||||
"warehouse_id": 22142605386000,
|
||||
"name": "Склад на производственной",
|
||||
"is_rfbs": true
|
||||
},
|
||||
{
|
||||
"warehouse_id": 22208673494000,
|
||||
"name": "Тест 37349",
|
||||
"is_rfbs": true
|
||||
},
|
||||
{
|
||||
"warehouse_id": 22240462819000,
|
||||
"name": "Тест12",
|
||||
"is_rfbs": true
|
||||
}
|
||||
"result": [
|
||||
{
|
||||
"warehouse_id": 1020000177886000,
|
||||
"name": "This is a test",
|
||||
"is_rfbs": false,
|
||||
"has_entrusted_acceptance": false,
|
||||
"first_mile_type": {
|
||||
"dropoff_point_id": "",
|
||||
"dropoff_timeslot_id": 0,
|
||||
"first_mile_is_changing": false,
|
||||
"first_mile_type": ""
|
||||
},
|
||||
"is_kgt": false,
|
||||
"can_print_act_in_advance": false,
|
||||
"min_working_days": 5,
|
||||
"is_karantin": false,
|
||||
"has_postings_limit": false,
|
||||
"postings_limit": -1,
|
||||
"working_days": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7
|
||||
],
|
||||
"min_postings_limit": 10,
|
||||
"is_timetable_editable": true,
|
||||
"status": "disabled"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user