Update November 6, 2024 (#115)
This commit is contained in:
74
ozon/clusters.go
Normal file
74
ozon/clusters.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package ozon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
core "github.com/diphantxm/ozon-api-client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Clusters struct {
|
||||||
|
client *core.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListClustersParams struct {
|
||||||
|
// Clusters identifiers
|
||||||
|
ClusterIds []string `json:"cluster_ids"`
|
||||||
|
|
||||||
|
// Cluster type
|
||||||
|
ClusterType string `json:"cluster_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListClustersResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Cluster details
|
||||||
|
Clusters []Cluster `json:"clusters"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cluster struct {
|
||||||
|
// Cluster identifier
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
|
||||||
|
// Cluster warehouse details
|
||||||
|
LogisticClusters []LogisticCluster `json:"logistic_clusters"`
|
||||||
|
|
||||||
|
// Cluster name
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Cluster type
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogisticCluster struct {
|
||||||
|
// Warehouse status
|
||||||
|
IsArchived bool `json:"is_archived"`
|
||||||
|
|
||||||
|
// Warehouses
|
||||||
|
Warehouses []LogisticClusterWarehouse `json:"warehouses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogisticClusterWarehouse struct {
|
||||||
|
// Warehouse name
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Warehouse type
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Warehouse identifier
|
||||||
|
Id int64 `json:"warehouse_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Clusters) List(ctx context.Context, params *ListClustersParams) (*ListClustersResponse, error) {
|
||||||
|
url := "/v1/cluster/list"
|
||||||
|
|
||||||
|
resp := &ListClustersResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
78
ozon/clusters_test.go
Normal file
78
ozon/clusters_test.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package ozon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
core "github.com/diphantxm/ozon-api-client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListClusters(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *ListClustersParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&ListClustersParams{
|
||||||
|
ClusterIds: []string{"string"},
|
||||||
|
ClusterType: "CLUSTER_TYPE_UNKNOWN",
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"clusters": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"logistic_clusters": [
|
||||||
|
{
|
||||||
|
"is_archived": true,
|
||||||
|
"warehouses": [
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"type": "FULL_FILLMENT",
|
||||||
|
"warehouse_id": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "string",
|
||||||
|
"type": "CLUSTER_TYPE_UNKNOWN"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&ListClustersParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Clusters().List(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &ListClustersResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
278
ozon/fbo.go
278
ozon/fbo.go
@@ -872,3 +872,281 @@ func (c FBO) GetSupplyContent(ctx context.Context, params *GetSupplyContentParam
|
|||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateSupplyDraftParams struct {
|
||||||
|
// Clusters identifiers
|
||||||
|
ClusterIds []string `json:"cluster_ids"`
|
||||||
|
|
||||||
|
// Shipping point identifier: pick-up point or sorting center. Only for the type = CREATE_TYPE_CROSSDOCK supply type.
|
||||||
|
DropoffWarehouseId int64 `json:"drop_off_point_warehouse_id"`
|
||||||
|
|
||||||
|
// Products
|
||||||
|
Items []CreateSupplyDraftItem `json:"items"`
|
||||||
|
|
||||||
|
// Supply type
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSupplyDraftItem struct {
|
||||||
|
// Product quantity
|
||||||
|
Quantity int32 `json:"quantity"`
|
||||||
|
|
||||||
|
// Product identifier
|
||||||
|
SKU int64 `json:"sku"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSupplyDraftResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Identifier of the supply request draft
|
||||||
|
OperationId string `json:"operation_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a direct or cross-docking supply request draft and specify the products to supply.
|
||||||
|
//
|
||||||
|
// You can leave feedback on this method in the comments section to the discussion in the Ozon for dev community
|
||||||
|
func (c FBO) CreateSupplyDraft(ctx context.Context, params *CreateSupplyDraftParams) (*CreateSupplyDraftResponse, error) {
|
||||||
|
url := "/v1/draft/create"
|
||||||
|
|
||||||
|
resp := &CreateSupplyDraftResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodGet, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSupplyDraftInfoParams struct {
|
||||||
|
// Identifier of the supply request draft
|
||||||
|
OperationId string `json:"operation_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSupplyDraftInfoResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Clusters
|
||||||
|
Clusters []SupplyDraftCluster `json:"clusters"`
|
||||||
|
|
||||||
|
// Identifier of the supply request draft
|
||||||
|
DraftId int64 `json:"draft_id"`
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
Errors []GetSupplyDraftInfoError `json:"errors"`
|
||||||
|
|
||||||
|
// Creation status of the supply request draft
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupplyDraftCluster struct {
|
||||||
|
// Cluster identifier
|
||||||
|
Id int64 `json:"cluster_id"`
|
||||||
|
|
||||||
|
// Cluster name
|
||||||
|
Name string `json:"cluster_name"`
|
||||||
|
|
||||||
|
// Warehouses
|
||||||
|
Warehouses []SupplyDraftWarehouse `json:"warehouses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupplyDraftWarehouse struct {
|
||||||
|
// Warehouse address
|
||||||
|
Address string `json:"address"`
|
||||||
|
|
||||||
|
// Product list bundle
|
||||||
|
BundleIds []SupplyDraftWarehouseBundle `json:"bundle_ids"`
|
||||||
|
|
||||||
|
// Warehouse name
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Bundle of products that don't come in a shipment
|
||||||
|
RestrictedBundleId string `json:"restricted_bundle_id"`
|
||||||
|
|
||||||
|
// Warehouse availability
|
||||||
|
Status SupplyDraftWarehouseStatus `json:"status"`
|
||||||
|
|
||||||
|
// Supply warehouses
|
||||||
|
SupplyWarehouse SupplyWarehouse `json:"supply_warehouse"`
|
||||||
|
|
||||||
|
// Warehouse rank in the cluster
|
||||||
|
TotalRank int32 `json:"total_rank"`
|
||||||
|
|
||||||
|
// Warehouse rating
|
||||||
|
TotalScore float64 `json:"total_score"`
|
||||||
|
|
||||||
|
// Estimated delivery time
|
||||||
|
//
|
||||||
|
// Nullable
|
||||||
|
TravelTimeDays *int64 `json:"travel_time_days"`
|
||||||
|
|
||||||
|
// Warehouse identifier
|
||||||
|
Id int64 `json:"warehouse_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupplyDraftWarehouseBundle struct {
|
||||||
|
// Bundle identifier
|
||||||
|
Id string `json:"bundle_id"`
|
||||||
|
|
||||||
|
// Indicates that the UTD is to be passed
|
||||||
|
IsDocless bool `json:"is_docless"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupplyDraftWarehouseStatus struct {
|
||||||
|
// Reason why the warehouse isn't available
|
||||||
|
InvalidReason string `json:"invalid_reason"`
|
||||||
|
|
||||||
|
// Warehouse availability
|
||||||
|
IsAvailable bool `json:"is_available"`
|
||||||
|
|
||||||
|
// Warehouse status
|
||||||
|
State string `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSupplyDraftInfoError struct {
|
||||||
|
// Possible errors
|
||||||
|
Message string `json:"error_message"`
|
||||||
|
|
||||||
|
// Validation errors
|
||||||
|
ItemsValidation []GetSupplyDraftInfoValidationError `json:"items_validation"`
|
||||||
|
|
||||||
|
// Unknown clusters identifiers
|
||||||
|
UnknownClusterIds []string `json:"unknown_cluster_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSupplyDraftInfoValidationError struct {
|
||||||
|
// Error reasons
|
||||||
|
Reasons []string `json:"reasons"`
|
||||||
|
|
||||||
|
// Product identifier in the Ozon system, SKU
|
||||||
|
SKU int64 `json:"sku"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c FBO) GetSupplyDraftInfo(ctx context.Context, params *GetSupplyDraftInfoParams) (*GetSupplyDraftInfoResponse, error) {
|
||||||
|
url := "/v1/draft/create/info"
|
||||||
|
|
||||||
|
resp := &GetSupplyDraftInfoResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodGet, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSupplyFromDraftParams struct {
|
||||||
|
// Identifier of the supply request draft
|
||||||
|
DraftId int64 `json:"draft_id"`
|
||||||
|
|
||||||
|
// Supply time slot
|
||||||
|
Timeslot CreateSupplyFromDraftTimeslot `json:"timeslot"`
|
||||||
|
|
||||||
|
// Shipping warehouse identifier
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSupplyFromDraftTimeslot struct {
|
||||||
|
// Supply time slot start date
|
||||||
|
FromInTimezone time.Time `json:"from_in_timezone"`
|
||||||
|
|
||||||
|
// Supply time slot end date
|
||||||
|
ToInTimezone time.Time `json:"to_in_timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSupplyFromDraftResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Supply request identifier
|
||||||
|
OperationId string `json:"operation_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c FBO) CreateSupplyFromDraft(ctx context.Context, params *CreateSupplyFromDraftParams) (*CreateSupplyFromDraftResponse, error) {
|
||||||
|
url := "/v1/draft/supply/create"
|
||||||
|
|
||||||
|
resp := &CreateSupplyFromDraftResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodGet, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDraftTimeslotsParams struct {
|
||||||
|
// Start date of the available supply time slots period
|
||||||
|
DateFrom time.Time `json:"date_from"`
|
||||||
|
|
||||||
|
// End date of the available supply time slots period
|
||||||
|
//
|
||||||
|
// The maximum period is 28 days from the current date
|
||||||
|
DateTo time.Time `json:"date_to"`
|
||||||
|
|
||||||
|
// Identifier of the supply request draft
|
||||||
|
DraftId int64 `json:"draft_id"`
|
||||||
|
|
||||||
|
// The warehouses identifiers for which supply time slots are required
|
||||||
|
WarehouseIds []string `json:"warehouse_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDraftTimeslotsResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Warehouses supply time slots
|
||||||
|
DropoffWarehouseTimeslots []DraftTimeslot `json:"drop_off_warehouse_timeslots"`
|
||||||
|
|
||||||
|
// Start date of the necessary period
|
||||||
|
RequestedDateFrom time.Time `json:"requested_date_from"`
|
||||||
|
|
||||||
|
// End date of the necessary period
|
||||||
|
RequestedDateTo time.Time `json:"requested_date_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DraftTimeslot struct {
|
||||||
|
// Current time in the warehouse time zone
|
||||||
|
CurrentTimeInTimezone time.Time `json:"current_time_in_timezone"`
|
||||||
|
|
||||||
|
// Supply time slots by dates
|
||||||
|
Days []DraftTimeslotDay `json:"days"`
|
||||||
|
|
||||||
|
// Warehouse identifier
|
||||||
|
DropoffWarehouseId int64 `json:"drop_off_warehouse_id"`
|
||||||
|
|
||||||
|
// Warehouse time zone
|
||||||
|
WarehouseTimezone string `json:"warehouse_timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DraftTimeslotDay struct {
|
||||||
|
// Supply time slots date
|
||||||
|
DateInTimezone time.Time `json:"date_in_timezone"`
|
||||||
|
|
||||||
|
// Supply time slots details
|
||||||
|
Timeslots []DraftTimeslotDayTimeslot `json:"timeslots"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DraftTimeslotDayTimeslot struct {
|
||||||
|
// Supply time slot start date
|
||||||
|
FromInTimezone time.Time `json:"from_in_timezone"`
|
||||||
|
|
||||||
|
// Supply time slot end date
|
||||||
|
ToInTimezone time.Time `json:"to_in_timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Available supply time slots at final shipping warehouses
|
||||||
|
func (c FBO) GetDraftTimeslots(ctx context.Context, params *GetDraftTimeslotsParams) (*GetDraftTimeslotsResponse, error) {
|
||||||
|
url := "/v1/draft/timeslot/info"
|
||||||
|
|
||||||
|
resp := &GetDraftTimeslotsResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodGet, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|||||||
285
ozon/fbo_test.go
285
ozon/fbo_test.go
@@ -989,3 +989,288 @@ func TestGetSupplyContent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateSupplyDraft(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *CreateSupplyDraftParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&CreateSupplyDraftParams{
|
||||||
|
ClusterIds: []string{"string"},
|
||||||
|
DropoffWarehouseId: 0,
|
||||||
|
Items: []CreateSupplyDraftItem{
|
||||||
|
{
|
||||||
|
Quantity: 1,
|
||||||
|
SKU: 11,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: "CREATE_TYPE_CROSSDOCK",
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"operation_id": "string"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&CreateSupplyDraftParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.FBO().CreateSupplyDraft(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &CreateSupplyDraftResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSupplyDraftInfo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *GetSupplyDraftInfoParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&GetSupplyDraftInfoParams{
|
||||||
|
OperationId: "string",
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"clusters": [
|
||||||
|
{
|
||||||
|
"cluster_id": 0,
|
||||||
|
"cluster_name": "string",
|
||||||
|
"warehouses": [
|
||||||
|
{
|
||||||
|
"address": "string",
|
||||||
|
"bundle_ids": [
|
||||||
|
{
|
||||||
|
"bundle_id": "string",
|
||||||
|
"is_docless": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "string",
|
||||||
|
"restricted_bundle_id": "string",
|
||||||
|
"status": {
|
||||||
|
"invalid_reason": "WAREHOUSE_SCORING_INVALID_REASON_UNSPECIFIED",
|
||||||
|
"is_available": true,
|
||||||
|
"state": "WAREHOUSE_SCORING_STATUS_FULL_AVAILABLE"
|
||||||
|
},
|
||||||
|
"supply_warehouse": {
|
||||||
|
"address": "string",
|
||||||
|
"name": "string",
|
||||||
|
"warehouse_id": 0
|
||||||
|
},
|
||||||
|
"total_rank": 0,
|
||||||
|
"total_score": 0,
|
||||||
|
"travel_time_days": 0,
|
||||||
|
"warehouse_id": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"draft_id": 0,
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"error_message": "string",
|
||||||
|
"items_validation": [
|
||||||
|
{
|
||||||
|
"reasons": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"sku": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"unknown_cluster_ids": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "CALCULATION_STATUS_FAILED"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&GetSupplyDraftInfoParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.FBO().GetSupplyDraftInfo(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &GetSupplyDraftInfoResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSupplyFromDraft(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *CreateSupplyFromDraftParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&CreateSupplyFromDraftParams{
|
||||||
|
DraftId: 11,
|
||||||
|
Timeslot: CreateSupplyFromDraftTimeslot{
|
||||||
|
FromInTimezone: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
|
||||||
|
ToInTimezone: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
|
||||||
|
},
|
||||||
|
WarehouseId: 45,
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"operation_id": "string"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&CreateSupplyFromDraftParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.FBO().CreateSupplyFromDraft(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &CreateSupplyFromDraftResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDraftTimeslots(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *GetDraftTimeslotsParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&GetDraftTimeslotsParams{
|
||||||
|
DraftId: 123,
|
||||||
|
DateFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
|
||||||
|
DateTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
|
||||||
|
WarehouseIds: []string{"ddd456"},
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"drop_off_warehouse_timeslots": [
|
||||||
|
{
|
||||||
|
"current_time_in_timezone": "2019-08-24T14:15:22Z",
|
||||||
|
"days": [
|
||||||
|
{
|
||||||
|
"date_in_timezone": "2019-08-24T14:15:22Z",
|
||||||
|
"timeslots": [
|
||||||
|
{
|
||||||
|
"from_in_timezone": "2019-08-24T14:15:22Z",
|
||||||
|
"to_in_timezone": "2019-08-24T14:15:22Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"drop_off_warehouse_id": 0,
|
||||||
|
"warehouse_timezone": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requested_date_from": "2019-08-24T14:15:22Z",
|
||||||
|
"requested_date_to": "2019-08-24T14:15:22Z"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&GetDraftTimeslotsParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.FBO().GetDraftTimeslots(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &GetDraftTimeslotsResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
10
ozon/fbs.go
10
ozon/fbs.go
@@ -53,6 +53,11 @@ type ListUnprocessedShipmentsFilter struct {
|
|||||||
// Delivery method identifier
|
// Delivery method identifier
|
||||||
DeliveryMethodId []int64 `json:"delivery_method_id"`
|
DeliveryMethodId []int64 `json:"delivery_method_id"`
|
||||||
|
|
||||||
|
// Specify true to get only MOQ shipments.
|
||||||
|
//
|
||||||
|
// The default value is false, the response contains all shipments
|
||||||
|
IsQuantum bool `json:"is_quantum"`
|
||||||
|
|
||||||
// Filter for shipments delivered from partner warehouse (FBP). You can pass one of the following values:
|
// Filter for shipments delivered from partner warehouse (FBP). You can pass one of the following values:
|
||||||
//
|
//
|
||||||
// Default value is all.
|
// Default value is all.
|
||||||
@@ -544,6 +549,11 @@ type GetFBSShipmentsListFilter struct {
|
|||||||
// Order identifier
|
// Order identifier
|
||||||
OrderId int64 `json:"order_id"`
|
OrderId int64 `json:"order_id"`
|
||||||
|
|
||||||
|
// Specify true to get only MOQ shipments.
|
||||||
|
//
|
||||||
|
// The default value is false, the response contains all shipments
|
||||||
|
IsQuantum bool `json:"is_quantum"`
|
||||||
|
|
||||||
// Delivery service identifier
|
// Delivery service identifier
|
||||||
ProviderId []int64 `json:"provider_id"`
|
ProviderId []int64 `json:"provider_id"`
|
||||||
|
|
||||||
|
|||||||
@@ -404,3 +404,57 @@ func (c Finance) ListTransactions(ctx context.Context, params *ListTransactionsP
|
|||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetReportParams struct {
|
||||||
|
// Time period in the YYYY-MM format
|
||||||
|
Date string `json:"date"`
|
||||||
|
|
||||||
|
// Response language
|
||||||
|
Language string `json:"language"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Method result
|
||||||
|
Result ReportResult `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportResult struct {
|
||||||
|
// Unique report identifier
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the method to get mutual settlements report.
|
||||||
|
// Matches the Finance → Documents → Analytical reports → Mutual
|
||||||
|
// settlements report section in your personal account.
|
||||||
|
func (c Finance) MutualSettlements(ctx context.Context, params *GetReportParams) (*ReportResponse, error) {
|
||||||
|
url := "/v1/finance/mutual-settlement"
|
||||||
|
|
||||||
|
resp := &ReportResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the method to get sales to legal entities report.
|
||||||
|
// Matches the Finance → Documents → Legal
|
||||||
|
// entities sales register section in your personal account.
|
||||||
|
func (c Finance) SalesToLegalEntities(ctx context.Context, params *GetReportParams) (*ReportResponse, error) {
|
||||||
|
url := "/v1/finance/mutual-settlement"
|
||||||
|
|
||||||
|
resp := &ReportResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -271,3 +271,127 @@ func TestListTransactions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMutualSettlements(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *GetReportParams
|
||||||
|
response string
|
||||||
|
errorMessage string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&GetReportParams{
|
||||||
|
Date: "2024-08",
|
||||||
|
Language: "DEFAULT",
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"result": {
|
||||||
|
"code": "string"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&GetReportParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
"Client-Id and Api-Key headers are required",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Finance().MutualSettlements(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &ReportResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if resp.Message != test.errorMessage {
|
||||||
|
t.Errorf("got wrong error message: got: %s, expected: %s", resp.Message, test.errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSalesToLegalEntities(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *GetReportParams
|
||||||
|
response string
|
||||||
|
errorMessage string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&GetReportParams{
|
||||||
|
Date: "2024-08",
|
||||||
|
Language: "DEFAULT",
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"result": {
|
||||||
|
"code": "string"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&GetReportParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
"Client-Id and Api-Key headers are required",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Finance().SalesToLegalEntities(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &ReportResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if resp.Message != test.errorMessage {
|
||||||
|
t.Errorf("got wrong error message: got: %s, expected: %s", resp.Message, test.errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
14
ozon/ozon.go
14
ozon/ozon.go
@@ -42,6 +42,8 @@ type Client struct {
|
|||||||
strategies *Strategies
|
strategies *Strategies
|
||||||
barcodes *Barcodes
|
barcodes *Barcodes
|
||||||
passes *Passes
|
passes *Passes
|
||||||
|
clusters *Clusters
|
||||||
|
quants *Quants
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) Analytics() *Analytics {
|
func (c Client) Analytics() *Analytics {
|
||||||
@@ -124,6 +126,14 @@ func (c Client) Passes() *Passes {
|
|||||||
return c.passes
|
return c.passes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Client) Clusters() *Clusters {
|
||||||
|
return c.clusters
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) Quants() *Quants {
|
||||||
|
return c.quants
|
||||||
|
}
|
||||||
|
|
||||||
type ClientOption func(c *ClientOptions)
|
type ClientOption func(c *ClientOptions)
|
||||||
|
|
||||||
func WithHttpClient(httpClient core.HttpClient) ClientOption {
|
func WithHttpClient(httpClient core.HttpClient) ClientOption {
|
||||||
@@ -188,6 +198,8 @@ func NewClient(opts ...ClientOption) *Client {
|
|||||||
strategies: &Strategies{client: coreClient},
|
strategies: &Strategies{client: coreClient},
|
||||||
barcodes: &Barcodes{client: coreClient},
|
barcodes: &Barcodes{client: coreClient},
|
||||||
passes: &Passes{client: coreClient},
|
passes: &Passes{client: coreClient},
|
||||||
|
clusters: &Clusters{client: coreClient},
|
||||||
|
quants: &Quants{client: coreClient},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,5 +228,7 @@ func NewMockClient(handler http.HandlerFunc) *Client {
|
|||||||
strategies: &Strategies{client: coreClient},
|
strategies: &Strategies{client: coreClient},
|
||||||
barcodes: &Barcodes{client: coreClient},
|
barcodes: &Barcodes{client: coreClient},
|
||||||
passes: &Passes{client: coreClient},
|
passes: &Passes{client: coreClient},
|
||||||
|
clusters: &Clusters{client: coreClient},
|
||||||
|
quants: &Quants{client: coreClient},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
206
ozon/products.go
206
ozon/products.go
@@ -525,6 +525,14 @@ type UpdateQuantityStockProductsStock struct {
|
|||||||
// Product identifier
|
// Product identifier
|
||||||
ProductId int64 `json:"product_id"`
|
ProductId int64 `json:"product_id"`
|
||||||
|
|
||||||
|
// Use parameter if the regular and economy products have the same article code—offer_id = quant_id. To update quantity of the:
|
||||||
|
//
|
||||||
|
// - regular product, pass the 1 value;
|
||||||
|
// - economy product, pass the size of its MOQ.
|
||||||
|
//
|
||||||
|
// If the regular and economy products have different article codes, don't specify the parameter.
|
||||||
|
QuantSize int64 `json:"quant_size"`
|
||||||
|
|
||||||
// Quantity
|
// Quantity
|
||||||
Stock int64 `json:"stock"`
|
Stock int64 `json:"stock"`
|
||||||
|
|
||||||
@@ -549,6 +557,12 @@ type UpdateQuantityStockProductsResult struct {
|
|||||||
// Product identifier
|
// Product identifier
|
||||||
ProductId int64 `json:"product_id"`
|
ProductId int64 `json:"product_id"`
|
||||||
|
|
||||||
|
// Shows the quantity of which product type you are updating:
|
||||||
|
//
|
||||||
|
// - 1, if you are updating the stock of a regular product
|
||||||
|
// - MOQ size, if you are updating the stock of economy product
|
||||||
|
QuantSize int64 `json:"quant_size"`
|
||||||
|
|
||||||
// If the request was completed successfully and the stocks are updated — true
|
// If the request was completed successfully and the stocks are updated — true
|
||||||
Updated bool `json:"updated"`
|
Updated bool `json:"updated"`
|
||||||
|
|
||||||
@@ -686,6 +700,14 @@ type UpdatePricesPrice struct {
|
|||||||
|
|
||||||
// Product identifier
|
// Product identifier
|
||||||
ProductId int64 `json:"product_id"`
|
ProductId int64 `json:"product_id"`
|
||||||
|
|
||||||
|
// Use parameter if the regular and economy products have the same article code—offer_id = quant_id. To update price of the:
|
||||||
|
//
|
||||||
|
// - regular product, pass the 1 value;
|
||||||
|
// - economy product, pass the size of its MOQ.
|
||||||
|
//
|
||||||
|
// If the regular and economy products have different article codes, don't specify the parameter.
|
||||||
|
QuantSize int64 `json:"quant_size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdatePricesResponse struct {
|
type UpdatePricesResponse struct {
|
||||||
@@ -964,6 +986,10 @@ type GetListOfProductsResultItem struct {
|
|||||||
ProductId int64 `json:"product_id"`
|
ProductId int64 `json:"product_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When using the filter by offer_id or product_id identifier, other parameters are not required.
|
||||||
|
// Only one identifiers group can be used at a time, not more than 1000 products.
|
||||||
|
//
|
||||||
|
// If you do not use identifiers for display, specify limit and last_id in subsequent requests.
|
||||||
func (c Products) GetListOfProducts(ctx context.Context, params *GetListOfProductsParams) (*GetListOfProductsResponse, error) {
|
func (c Products) GetListOfProducts(ctx context.Context, params *GetListOfProductsParams) (*GetListOfProductsResponse, error) {
|
||||||
url := "/v2/product/list"
|
url := "/v2/product/list"
|
||||||
|
|
||||||
@@ -2348,3 +2374,183 @@ func (c Products) GetRelatedSKUs(ctx context.Context, params *GetRelatedSKUsPara
|
|||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetEconomyInfoParams struct {
|
||||||
|
// List of MOQs with products
|
||||||
|
QuantCode []string `json:"quant_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetEconomyInfoResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Economy products
|
||||||
|
Items []EconomyInfoItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EconomyInfoItem struct {
|
||||||
|
// Product identifier in the seller's system
|
||||||
|
OfferId string `json:"offer_id"`
|
||||||
|
|
||||||
|
// Product identifier
|
||||||
|
ProductId int64 `json:"product_id"`
|
||||||
|
|
||||||
|
// MOQ information
|
||||||
|
QuantInfo EconomyInfoItemQuants `json:"quant_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EconomyInfoItemQuants struct {
|
||||||
|
Quants []EconomyInfoItemQuant `json:"quants"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EconomyInfoItemQuant struct {
|
||||||
|
// Barcodes information
|
||||||
|
BarcodesExtended []EconomyInfoItemQuantBarcode `json:"barcodes_extended"`
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
Dimensions DimensionsMM `json:"dimensions"`
|
||||||
|
|
||||||
|
// Marketing prices
|
||||||
|
MarketingPrice EconomyInfoItemQuantMarketingPrice `json:"marketing_price"`
|
||||||
|
|
||||||
|
// Minimum price specified by the seller
|
||||||
|
MinPrice string `json:"min_price"`
|
||||||
|
|
||||||
|
// The strikethrough price specified by the seller
|
||||||
|
OldPrice string `json:"old_price"`
|
||||||
|
|
||||||
|
// The selling price specified by the seller
|
||||||
|
Price string `json:"price"`
|
||||||
|
|
||||||
|
// Economy product identifier
|
||||||
|
QuantCode string `json:"quant_code"`
|
||||||
|
|
||||||
|
// MOQ size
|
||||||
|
QuantSize int64 `json:"quant_sice"`
|
||||||
|
|
||||||
|
// Product delivery type
|
||||||
|
ShipmentType string `json:"shipment_type"`
|
||||||
|
|
||||||
|
// Product SKU
|
||||||
|
SKU int64 `json:"sku"`
|
||||||
|
|
||||||
|
// Statuses descriptions
|
||||||
|
Statuses EconomyInfoItemQuantStatus `json:"statuses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EconomyInfoItemQuantBarcode struct {
|
||||||
|
// Barcode
|
||||||
|
Barcode string `json:"barcode"`
|
||||||
|
|
||||||
|
// Error when receiving the barcode
|
||||||
|
Error string `json:"error"`
|
||||||
|
|
||||||
|
// Barcode status
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DimensionsMM struct {
|
||||||
|
// Depth, mm
|
||||||
|
Depth int64 `json:"depth"`
|
||||||
|
|
||||||
|
// Height, mm
|
||||||
|
Height int64 `json:"height"`
|
||||||
|
|
||||||
|
// Weight, g
|
||||||
|
Weight int64 `json:"weight"`
|
||||||
|
|
||||||
|
// Width, mm
|
||||||
|
Width int64 `json:"width"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EconomyInfoItemQuantMarketingPrice struct {
|
||||||
|
// Selling price
|
||||||
|
Price string `json:"price"`
|
||||||
|
|
||||||
|
// Price specified by the seller
|
||||||
|
SellerPrice string `json:"seller_price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EconomyInfoItemQuantStatus struct {
|
||||||
|
// Status description
|
||||||
|
StateDescription string `json:"state_description"`
|
||||||
|
|
||||||
|
// Status name
|
||||||
|
StateName string `json:"state_name"`
|
||||||
|
|
||||||
|
// System name of the status
|
||||||
|
StateSysName string `json:"state_sys_name"`
|
||||||
|
|
||||||
|
// Tooltip with current product status details
|
||||||
|
StateTooltip string `json:"state_tooltip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Products) EconomyInfo(ctx context.Context, params *GetEconomyInfoParams) (*GetEconomyInfoResponse, error) {
|
||||||
|
url := "/v1/product/quant/info"
|
||||||
|
|
||||||
|
resp := &GetEconomyInfoResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListEconomyProductsParams struct {
|
||||||
|
// Cursor for the next data sample
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
|
||||||
|
// Maximum number of values in the response
|
||||||
|
Limit int64 `json:"limit"`
|
||||||
|
|
||||||
|
// Filter by product visibility
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListEconomyProductsResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Cursor for the next data sample
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
|
||||||
|
// Economy products
|
||||||
|
Products []EconomyProduct `json:"products"`
|
||||||
|
|
||||||
|
// Leftover stock in all warehouses
|
||||||
|
TotalItems int32 `json:"total_items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EconomyProduct struct {
|
||||||
|
// Product identifier in the seller's system
|
||||||
|
OfferId string `json:"offer_id"`
|
||||||
|
|
||||||
|
// Product identifier
|
||||||
|
ProductId int64 `json:"product_id"`
|
||||||
|
|
||||||
|
// Product MOQs list
|
||||||
|
Quants []EconomyProductQuant `json:"quants"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EconomyProductQuant struct {
|
||||||
|
// MOQ identifier
|
||||||
|
QuantCode string `json:"quant_code"`
|
||||||
|
|
||||||
|
// MOQ size
|
||||||
|
QuantSize int64 `json:"quant_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Products) ListEconomy(ctx context.Context, params *ListEconomyProductsParams) (*ListEconomyProductsResponse, error) {
|
||||||
|
url := "/v1/product/quant/list"
|
||||||
|
|
||||||
|
resp := &ListEconomyProductsResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2686,3 +2686,159 @@ func TestGetRelatedSKUs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEconomyInfo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *GetEconomyInfoParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&GetEconomyInfoParams{
|
||||||
|
QuantCode: []string{"321", "322"},
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"offer_id": "string",
|
||||||
|
"product_id": 0,
|
||||||
|
"quant_info": {
|
||||||
|
"quants": [
|
||||||
|
{
|
||||||
|
"barcodes_extended": [
|
||||||
|
{
|
||||||
|
"barcode": "string",
|
||||||
|
"error": "string",
|
||||||
|
"status": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dimensions": {
|
||||||
|
"depth": 0,
|
||||||
|
"height": 0,
|
||||||
|
"weight": 0,
|
||||||
|
"width": 0
|
||||||
|
},
|
||||||
|
"marketing_price": {
|
||||||
|
"price": "string",
|
||||||
|
"seller_price": "string"
|
||||||
|
},
|
||||||
|
"min_price": "string",
|
||||||
|
"old_price": "string",
|
||||||
|
"price": "string",
|
||||||
|
"quant_code": "string",
|
||||||
|
"quant_sice": 0,
|
||||||
|
"shipment_type": "string",
|
||||||
|
"sku": 0,
|
||||||
|
"statuses": {
|
||||||
|
"state_description": "string",
|
||||||
|
"state_name": "string",
|
||||||
|
"state_sys_name": "string",
|
||||||
|
"state_tooltip": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&GetEconomyInfoParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Products().EconomyInfo(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &GetEconomyInfoResponse{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListEconomy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *ListEconomyProductsParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&ListEconomyProductsParams{
|
||||||
|
Cursor: "string",
|
||||||
|
Limit: 10,
|
||||||
|
Visibility: "ALL",
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"cursor": "string",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"offer_id": "string",
|
||||||
|
"product_id": 0,
|
||||||
|
"quants": [
|
||||||
|
{
|
||||||
|
"quant_code": "string",
|
||||||
|
"quant_size": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_items": 0
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&ListEconomyProductsParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Products().ListEconomy(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &ListEconomyProductsResponse{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
356
ozon/quants.go
Normal file
356
ozon/quants.go
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
package ozon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
core "github.com/diphantxm/ozon-api-client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Quants struct {
|
||||||
|
client *core.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListQuantsParams struct {
|
||||||
|
// Cursor for the next data sample
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
|
||||||
|
// Filter
|
||||||
|
Filter ListQuantsFilter `json:"filter"`
|
||||||
|
|
||||||
|
// Maximum number of values in the response
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
|
||||||
|
// Parameter by which products will be sorted
|
||||||
|
Sort string `json:"sort"`
|
||||||
|
|
||||||
|
// Sorting direction
|
||||||
|
SortDir string `json:"sort_dir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListQuantsFilter struct {
|
||||||
|
// MOQ creation period
|
||||||
|
CreatedAt *ListQuantsFilterTime `json:"created_at"`
|
||||||
|
|
||||||
|
// Time for MOQ assembly
|
||||||
|
Cutoff *ListQuantsFilterTime `json:"cutoff"`
|
||||||
|
|
||||||
|
// Destination point identifier
|
||||||
|
DestinationPlaceId int64 `json:"destination_place_id"`
|
||||||
|
|
||||||
|
// MOQ inventory identifiers
|
||||||
|
InvQuantIds []string `json:"inv_quants_ids"`
|
||||||
|
|
||||||
|
// Product identifier in the seller's system
|
||||||
|
OfferId string `json:"offer_id"`
|
||||||
|
|
||||||
|
// Product name
|
||||||
|
SKUName string `json:"sku_name"`
|
||||||
|
|
||||||
|
// MOQ statuses
|
||||||
|
Statuses []string `json:"statuses"`
|
||||||
|
|
||||||
|
// Warehouse identifier
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListQuantsFilterTime struct {
|
||||||
|
// Start date
|
||||||
|
From string `json:"from"`
|
||||||
|
|
||||||
|
// End date
|
||||||
|
To string `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListQuantsResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
Result ListQuantsResult `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListQuantsResult struct {
|
||||||
|
// Cursor for the next data sample
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
|
||||||
|
// Indication that the response returned only a part of characteristic values
|
||||||
|
HasNext bool `json:"has_next"`
|
||||||
|
|
||||||
|
// MOQs list
|
||||||
|
Quants []Quant `json:"quants"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Quant struct {
|
||||||
|
// List of available actions with MOQ
|
||||||
|
AvailableActions []string `json:"available_actions"`
|
||||||
|
|
||||||
|
// Date until which the leftover stock amount must be specified
|
||||||
|
AwaitingStockDueDate string `json:"awaiting_stock_due_date"`
|
||||||
|
|
||||||
|
// MOQ cancellation reason
|
||||||
|
CancelReason `json:"cancel_reason"`
|
||||||
|
|
||||||
|
// Seller company identifier
|
||||||
|
CompanyId int64 `json:"company_id"`
|
||||||
|
|
||||||
|
// MOQ creation date
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
|
||||||
|
// Current number of shipments in the MOQ
|
||||||
|
CurrentPostingsCount int64 `json:"current_postings_count"`
|
||||||
|
|
||||||
|
// Time until which the MOQ must be assembled
|
||||||
|
Cutoff string `json:"cutoff"`
|
||||||
|
|
||||||
|
// Delivery method name
|
||||||
|
DeliveryMethod string `json:"delivery_method_name"`
|
||||||
|
|
||||||
|
// Destination point identifier
|
||||||
|
DestinationPlaceId int64 `json:"destination_place_id"`
|
||||||
|
|
||||||
|
// Destination point name
|
||||||
|
DestinationPlaceName string `json:"destination_place_name"`
|
||||||
|
|
||||||
|
// MOQ filling percentage
|
||||||
|
FillingPercent float32 `json:"filling_percent"`
|
||||||
|
|
||||||
|
// Date when the shipments start to get canceled if the MOQ is not reserved
|
||||||
|
FirstPostingCancellationDate string `json:"first_posting_cancellation_date"`
|
||||||
|
|
||||||
|
// MOQ identifier in Ozon system
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
|
||||||
|
// MOQ inventory identifier
|
||||||
|
InvQuantId int64 `json:"inv_quant_id"`
|
||||||
|
|
||||||
|
// Date of the last MOQ status change
|
||||||
|
LastStatusChangeAt string `json:"last_status_change_at"`
|
||||||
|
|
||||||
|
// Product identifier in the seller's system
|
||||||
|
OfferId string `json:"offer_id"`
|
||||||
|
|
||||||
|
// Total cost of products in the MOQ
|
||||||
|
ProductsPrice float32 `json:"products_price"`
|
||||||
|
|
||||||
|
// Start date of MOQ filling
|
||||||
|
QuantumStartDate string `json:"quantum_start_date"`
|
||||||
|
|
||||||
|
// Product SKU
|
||||||
|
SKU int64 `json:"sku"`
|
||||||
|
|
||||||
|
// Product name
|
||||||
|
SKUName string `json:"sku_name"`
|
||||||
|
|
||||||
|
// MOQ statuses
|
||||||
|
Status string `json:"status"`
|
||||||
|
|
||||||
|
// Required number of products in the MOQ
|
||||||
|
TargetPostingsCount int64 `json:"target_postings_count"`
|
||||||
|
|
||||||
|
// Delivery service name
|
||||||
|
TPLProviderName string `json:"tpl_provider_name"`
|
||||||
|
|
||||||
|
// MOQ type: box or pallet
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Seller warehouse identifier
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
|
||||||
|
// Seller warehouse name
|
||||||
|
WarehouseName string `json:"warehouse_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelReason struct {
|
||||||
|
// Identifier of MOQ cancellation reason
|
||||||
|
Id int64 `json:"cancel_reason_id"`
|
||||||
|
|
||||||
|
// Cancellation reason name
|
||||||
|
Name string `json:"cancel_reason_name"`
|
||||||
|
|
||||||
|
// Cancellation initiator
|
||||||
|
Responsible string `json:"responsible"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can leave feedback on this method in the comments section to the discussion in the Ozon for dev community.
|
||||||
|
func (q Quants) List(ctx context.Context, params *ListQuantsParams) (*ListQuantsResponse, error) {
|
||||||
|
url := "/v1/quant/list"
|
||||||
|
|
||||||
|
resp := &ListQuantsResponse{}
|
||||||
|
|
||||||
|
response, err := q.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetQuantParams struct {
|
||||||
|
// MOQ inventory identifier
|
||||||
|
QuantId int64 `json:"inv_quant_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetQuantResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// MOQ information
|
||||||
|
Result []GetQuantResult `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetQuantResult struct {
|
||||||
|
// Available actions
|
||||||
|
AvailableActions []string `json:"available_actions"`
|
||||||
|
|
||||||
|
// Date until which the leftover stock amount must be specified
|
||||||
|
AwaitingStockDueDate time.Time `json:"awaiting_stock_due_date"`
|
||||||
|
|
||||||
|
// Shipment cancellation reason
|
||||||
|
CancelReason CancelReason `json:"cancel_reason"`
|
||||||
|
|
||||||
|
// Current number of shipments in the MOQ
|
||||||
|
CurrentPostingsCount int64 `json:"current_postings_count"`
|
||||||
|
|
||||||
|
// Time until which the MOQ must be assembled
|
||||||
|
Cutoff time.Time `json:"cutoff"`
|
||||||
|
|
||||||
|
// Delivery method name
|
||||||
|
DeliveryMethodName string `json:"delivery_method_name"`
|
||||||
|
|
||||||
|
// Destination point identifier
|
||||||
|
DestinationPlaceId int64 `json:"destination_place_id"`
|
||||||
|
|
||||||
|
// Destination point name
|
||||||
|
DestinationPlaceName string `json:"destination_place_name"`
|
||||||
|
|
||||||
|
// MOQ filling percentage
|
||||||
|
FillingPercent float32 `json:"filling_percent"`
|
||||||
|
|
||||||
|
// Date when the shipments start to get canceled if the MOQ isn't reserved
|
||||||
|
FirstPostingCancellationDate time.Time `json:"first_posting_cancellation_date"`
|
||||||
|
|
||||||
|
// MOQ identifier
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
|
||||||
|
// MOQ inventory identifier
|
||||||
|
QuantId int64 `json:"inv_quant_id"`
|
||||||
|
|
||||||
|
// Product identifier in the seller's system
|
||||||
|
OfferId string `json:"offer_id"`
|
||||||
|
|
||||||
|
// Shipments
|
||||||
|
Postings []GetQuantResultPosting `json:"postings"`
|
||||||
|
|
||||||
|
// Link to product photo
|
||||||
|
ProductPictureURL string `json:"product_picture_url"`
|
||||||
|
|
||||||
|
// Total price of products in the MOQ
|
||||||
|
ProductsPrice float32 `json:"products_price"`
|
||||||
|
|
||||||
|
// Start date of MOQ filling
|
||||||
|
QuantumStartDate time.Time `json:"quantum_start_date"`
|
||||||
|
|
||||||
|
// Product identifier in the Ozon system, SKU
|
||||||
|
SKU int64 `json:"sku"`
|
||||||
|
|
||||||
|
// Product name
|
||||||
|
SKUName string `json:"sku_name"`
|
||||||
|
|
||||||
|
// MOQ statuses
|
||||||
|
Status string `json:"status"`
|
||||||
|
|
||||||
|
// Required number of products in the MOQ
|
||||||
|
TargetPostingsCount int64 `json:"target_postings_count"`
|
||||||
|
|
||||||
|
// Delivery service name
|
||||||
|
TPLProviderName string `json:"tpl_provider_name"`
|
||||||
|
|
||||||
|
// MOQ type: box or pallet
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Warehouse identifier
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
|
||||||
|
// Warehouse name
|
||||||
|
WarehouseName string `json:"warehouse_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetQuantResultPosting struct {
|
||||||
|
// Shipment cancellation reason
|
||||||
|
CancelReason CancelReason `json:"cancel_reason"`
|
||||||
|
|
||||||
|
// Shipment number
|
||||||
|
PostingNumber string `json:"posting_number"`
|
||||||
|
|
||||||
|
// Total price of products in the MOQ
|
||||||
|
ProductsPrice float32 `json:"products_price"`
|
||||||
|
|
||||||
|
// Status text
|
||||||
|
StatusAlias string `json:"status_alias"`
|
||||||
|
|
||||||
|
// Status identifier
|
||||||
|
StatusId int64 `json:"status_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Quants) Get(ctx context.Context, params *GetQuantParams) (*GetQuantResponse, error) {
|
||||||
|
url := "/v1/quant/get"
|
||||||
|
|
||||||
|
resp := &GetQuantResponse{}
|
||||||
|
|
||||||
|
response, err := q.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShipQuantParams struct {
|
||||||
|
// MOQ inventory identifier
|
||||||
|
QuantId int64 `json:"quant_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShipQuantResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Quants) Ship(ctx context.Context, params *ShipQuantParams) (*ShipQuantResponse, error) {
|
||||||
|
url := "/v1/quant/ship"
|
||||||
|
|
||||||
|
resp := &ShipQuantResponse{}
|
||||||
|
|
||||||
|
response, err := q.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusQuantParams struct {
|
||||||
|
// MOQ inventory identifier
|
||||||
|
QuantId int64 `json:"inv_quant_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusQuantResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// MOQ statuses
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q Quants) Status(ctx context.Context, params *StatusQuantParams) (*StatusQuantResponse, error) {
|
||||||
|
url := "/v1/quant/ship"
|
||||||
|
|
||||||
|
resp := &StatusQuantResponse{}
|
||||||
|
|
||||||
|
response, err := q.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
303
ozon/quants_test.go
Normal file
303
ozon/quants_test.go
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
package ozon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
core "github.com/diphantxm/ozon-api-client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListQuants(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *ListQuantsParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&ListQuantsParams{
|
||||||
|
Cursor: "string",
|
||||||
|
Filter: ListQuantsFilter{
|
||||||
|
InvQuantIds: []string{"string"},
|
||||||
|
DestinationPlaceId: 123,
|
||||||
|
OfferId: "string",
|
||||||
|
SKUName: "string",
|
||||||
|
Statuses: []string{"unknown"},
|
||||||
|
WarehouseId: 456,
|
||||||
|
},
|
||||||
|
Limit: 10,
|
||||||
|
Sort: "string",
|
||||||
|
SortDir: "string",
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"result": {
|
||||||
|
"cursor": "string",
|
||||||
|
"has_next": true,
|
||||||
|
"quants": [
|
||||||
|
{
|
||||||
|
"available_actions": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"awaiting_stock_due_date": "2019-08-24T14:15:22Z",
|
||||||
|
"cancel_reason": {
|
||||||
|
"cancel_reason_id": 0,
|
||||||
|
"cancel_reason_name": "string",
|
||||||
|
"responsible": "string"
|
||||||
|
},
|
||||||
|
"company_id": 0,
|
||||||
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
|
"current_postings_count": 0,
|
||||||
|
"cutoff": "2019-08-24T14:15:22Z",
|
||||||
|
"delivery_method_name": "string",
|
||||||
|
"destination_place_id": 0,
|
||||||
|
"destination_place_name": "string",
|
||||||
|
"filling_percent": 0,
|
||||||
|
"first_posting_cancellation_date": "2019-08-24T14:15:22Z",
|
||||||
|
"id": 0,
|
||||||
|
"inv_quant_id": 0,
|
||||||
|
"last_status_change_at": "2019-08-24T14:15:22Z",
|
||||||
|
"offer_id": "string",
|
||||||
|
"products_price": 0,
|
||||||
|
"quantum_start_date": "2019-08-24T14:15:22Z",
|
||||||
|
"sku": 0,
|
||||||
|
"sku_name": "string",
|
||||||
|
"status": "unknown",
|
||||||
|
"target_postings_count": 0,
|
||||||
|
"tpl_provider_name": "string",
|
||||||
|
"type": "string",
|
||||||
|
"warehouse_id": 0,
|
||||||
|
"warehouse_name": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&ListQuantsParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Quants().List(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &ListQuantsResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetQuant(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *GetQuantParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&GetQuantParams{
|
||||||
|
QuantId: 456,
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"available_actions": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"awaiting_stock_due_date": "2019-08-24T14:15:22Z",
|
||||||
|
"cancel_reason": {
|
||||||
|
"cancel_reason_id": 0,
|
||||||
|
"cancel_reason_name": "string",
|
||||||
|
"responsible": "string"
|
||||||
|
},
|
||||||
|
"current_postings_count": 0,
|
||||||
|
"cutoff": "2019-08-24T14:15:22Z",
|
||||||
|
"delivery_method_name": "string",
|
||||||
|
"destination_place_id": 0,
|
||||||
|
"destination_place_name": "string",
|
||||||
|
"filling_percent": 0,
|
||||||
|
"first_posting_cancellation_date": "2019-08-24T14:15:22Z",
|
||||||
|
"id": 0,
|
||||||
|
"inv_quant_id": 0,
|
||||||
|
"offer_id": "string",
|
||||||
|
"postings": [
|
||||||
|
{
|
||||||
|
"cancel_reason": {
|
||||||
|
"cancel_reason_id": 0,
|
||||||
|
"cancel_reason_name": "string",
|
||||||
|
"responsible": "string"
|
||||||
|
},
|
||||||
|
"posting_number": "string",
|
||||||
|
"products_price": 0,
|
||||||
|
"status_alias": "string",
|
||||||
|
"status_id": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"product_picture_url": "string",
|
||||||
|
"products_price": 0,
|
||||||
|
"quantum_start_date": "2019-08-24T14:15:22Z",
|
||||||
|
"sku": 0,
|
||||||
|
"sku_name": "string",
|
||||||
|
"status": "unknown",
|
||||||
|
"target_postings_count": 0,
|
||||||
|
"tpl_provider_name": "string",
|
||||||
|
"type": "string",
|
||||||
|
"warehouse_id": 0,
|
||||||
|
"warehouse_name": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&GetQuantParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Quants().Get(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &GetQuantResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShipQuant(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *ShipQuantParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&ShipQuantParams{
|
||||||
|
QuantId: 456,
|
||||||
|
},
|
||||||
|
`{}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&ShipQuantParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Quants().Ship(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &ShipQuantResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusQuant(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *StatusQuantParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&StatusQuantParams{
|
||||||
|
QuantId: 456,
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"status": "unknown"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&StatusQuantParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Quants().Status(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &StatusQuantResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,9 @@ type GetListOfWarehousesResult struct {
|
|||||||
// Indication that the warehouse accepts bulky products
|
// Indication that the warehouse accepts bulky products
|
||||||
IsKGT bool `json:"is_kgt"`
|
IsKGT bool `json:"is_kgt"`
|
||||||
|
|
||||||
|
// true if the warehouse handles economy products
|
||||||
|
IsEconomy bool `json:"is_economy"`
|
||||||
|
|
||||||
// Indication that warehouse schedule can be changed
|
// Indication that warehouse schedule can be changed
|
||||||
IsTimetableEditable bool `json:"is_timetable_editable"`
|
IsTimetableEditable bool `json:"is_timetable_editable"`
|
||||||
|
|
||||||
@@ -189,3 +192,58 @@ func (c Warehouses) GetListOfDeliveryMethods(ctx context.Context, params *GetLis
|
|||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListForShippingParams struct {
|
||||||
|
// Supply type
|
||||||
|
FilterBySupplyType []string `json:"filter_by_supply_type"`
|
||||||
|
|
||||||
|
// Search by warehouse name. To search for pick-up points, specify the full name
|
||||||
|
Search string `json:"search"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListForShippingResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Warehouse search result
|
||||||
|
Search []ListForShippingSearch `json:"search"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListForShippingSearch struct {
|
||||||
|
// Warehouse address
|
||||||
|
Address string `json:"address"`
|
||||||
|
|
||||||
|
// Warehouse coordinates
|
||||||
|
Coordinates Coordinates `json:"coordinates"`
|
||||||
|
|
||||||
|
// Warehouse name
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Identifier of the warehouse, pick-up point, or sorting center
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
|
||||||
|
// Type of warehouse, pick-up point, or sorting center
|
||||||
|
WarehouseType string `json:"warehouse_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Coordinates struct {
|
||||||
|
// Latitude
|
||||||
|
Latitude float64 `json:"latitude"`
|
||||||
|
|
||||||
|
// Longitude
|
||||||
|
Longitude float64 `json:"longitude"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of warehouses, sorting centers and pick-up points available for cross-docking, and direct supplies.
|
||||||
|
func (c Warehouses) ListForShipping(ctx context.Context, params *ListForShippingParams) (*ListForShippingResponse, error) {
|
||||||
|
url := "/v1/warehouse/fbo/list"
|
||||||
|
|
||||||
|
resp := &ListForShippingResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -179,3 +179,65 @@ func TestGetListOfDeliveryMethods(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListForShipping(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *ListForShippingParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&ListForShippingParams{
|
||||||
|
FilterBySupplyType: []string{"CREATE_TYPE_UNKNOWN"},
|
||||||
|
Search: "string",
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"search": [
|
||||||
|
{
|
||||||
|
"address": "string",
|
||||||
|
"coordinates": {
|
||||||
|
"latitude": 0,
|
||||||
|
"longitude": 0
|
||||||
|
},
|
||||||
|
"name": "string",
|
||||||
|
"warehouse_id": 0,
|
||||||
|
"warehouse_type": "WAREHOUSE_TYPE_UNKNOWN"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&ListForShippingParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
resp, err := c.Warehouses().ListForShipping(ctx, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compareJsonResponse(t, test.response, &ListForShippingResponse{})
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user