21 Commits

Author SHA1 Message Date
diPhantxm
1c609b7c7a Update December 3, 2024 2024-12-27 23:13:43 +03:00
Kirill
7f71ed6545 Update November 29, 2024 (#124) 2024-12-27 22:59:45 +03:00
Kirill
2f4d207726 Update November 22, 2024 (#123) 2024-12-27 22:46:08 +03:00
Kirill
7b5f44ee44 Update November 20, 2024 (#122) 2024-12-27 22:01:51 +03:00
Kirill
bc5f0e52a5 Update November 18, 2024 (#121) 2024-12-27 21:50:21 +03:00
benice2me11
8b8b3bc974 Get description of products (#120) 2024-12-26 15:35:13 +03:00
Kirill
45b0dffe39 Update November 19, 2024 (#118) 2024-12-15 21:09:09 +03:00
Kirill
c7697863db Update November 14, 2024 (#117) 2024-12-15 21:05:21 +03:00
Kirill
8a585d086a Update November 13, 2024 (#116) 2024-12-15 21:03:19 +03:00
diPhantxm
d1fd698368 update 2024-12-15 20:52:23 +03:00
Kirill
6b8b22180a Update November 6, 2024 (#115) 2024-12-15 20:47:07 +03:00
Kirill
f4a09903c7 Update October 31, 2024 (#114) 2024-12-12 00:53:56 +03:00
Kirill
23ca98fedd Update October 24, 2024 (#111) 2024-10-31 16:14:01 +03:00
Kirill
eae6f54e71 Update October 23, 2024 (#110) 2024-10-31 15:59:47 +03:00
Kirill
8a6cd20b95 Update October 22, 2024 (#109)
New method for getting product turnovers
2024-10-31 15:56:04 +03:00
Kirill
1706575a34 Update October 17, 2024 (#108) 2024-10-31 15:47:35 +03:00
diPhantxm
3430ead143 method to list cancellation reasons for fbo scheme 2024-10-31 15:16:11 +03:00
Kirill
2164eff0a6 Update October 16, 2024 (#107) 2024-10-31 15:10:45 +03:00
Kirill
b6af642636 Update October 1, 2024 (#105) 2024-10-02 00:39:04 +03:00
Kirill
67898a4738 Update September 18, 2024 (#106) 2024-10-02 00:38:52 +03:00
Kirill
8c07540d28 Update September 6, 2024 (#104) 2024-10-02 00:38:01 +03:00
24 changed files with 4591 additions and 869 deletions

View File

@@ -219,3 +219,124 @@ func (c Analytics) GetStocksOnWarehouses(ctx context.Context, params *GetStocksO
return resp, nil
}
type GetProductTurnoverParams struct {
// Number of values in the response
Limit int64 `json:"limit"`
// Number of elements to skip in the response.
//
// For example, if offset = 10, the response starts with the 11th element found
Offset int32 `json:"offset"`
// Product identifiers in the Ozon system, SKU
SKU []string `json:"sku"`
}
type GetProductTurnoverResponse struct {
core.CommonResponse
// Products
Items []ProductTurnoverItem `json:"items"`
}
type ProductTurnoverItem struct {
// Average daily number of product items sold over the last 60 days
Ads float64 `json:"ads"`
// Product stock, pcs
CurrentStock int64 `json:"current_stock"`
// Number of days the stock will last based on your average daily sales
IDC float64 `json:"idc"`
// Product stock level
IDCGrade string `json:"idc_grade"`
}
// Use the method to get the product turnover rate and the number of days the current stock will last.
//
// If you request a list of products by sku, the limit and offset parameters are optional.
func (c Analytics) GetProductTurnover(ctx context.Context, params *GetProductTurnoverParams) (*GetProductTurnoverResponse, error) {
url := "/v1/analytics/turnover/stocks"
resp := &GetProductTurnoverResponse{}
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 GetStockManagementParams struct {
// GetStockManagementFilter
Filter GetStockManagementFilter `json:"filter"`
// Number of values in the response
Limit int32 `json:"limit,omitempty"`
// Number of elements to skip in the response
Offset int32 `json:"offset,omitempty"`
}
type GetStockManagementFilter struct {
// Product identifiers in the Ozon system, SKU
SKUs []string `json:"skus"`
// The type of item in stock
StockTypes string `json:"stock_types"`
// Warehouse identifiers
WarehouseIds []string `json:"warehouse_ids"`
}
type GetStockManagementResponse struct {
core.CommonResponse
// Products
Items []StockItem `json:"items"`
}
type StockItem struct {
// Stock of defective products, pcs
DefectCount int64 `json:"defect_stock_count"`
// Stock of near-expired products, pcs
ExpiringCount int64 `json:"expiring_stock_count"`
// Product name
ProductName string `json:"name"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Stock of valid products
ValidCount int64 `json:"valid_stock_count"`
// Stock of products that waiting for documents
WaitingDocsCount int64 `json:"waitingdocs_stock_count"`
// Warehouse name
WarehouseName string `json:"warehouse_name"`
}
// Use the method to find out how many product items are left in stock
func (c Analytics) Stock(ctx context.Context, params *GetStockManagementParams) (*GetStockManagementResponse, error) {
url := "/v1/analytics/manage/stocks"
resp := &GetStockManagementResponse{}
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
}

View File

@@ -145,3 +145,135 @@ func TestGetStocksOnWarehouses(t *testing.T) {
}
}
}
func TestGetProductTurnover(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetProductTurnoverParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetProductTurnoverParams{
Limit: 1,
SKU: []string{"string"},
},
`{
"items": [
{
"ads": 0,
"current_stock": 0,
"idc": 0,
"idc_grade": "GRADES_NONE"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetProductTurnoverParams{},
`{
"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.Analytics().GetProductTurnover(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductTurnoverResponse{})
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 len(resp.Items) > int(test.params.Limit) {
t.Errorf("Length of items is bigger than limit")
}
}
}
}
func TestGetStock(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetStockManagementParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetStockManagementParams{
Limit: 1,
Offset: 0,
Filter: GetStockManagementFilter{
StockTypes: "STOCK_TYPE_VALID",
SKUs: []string{
"string",
},
},
},
`{
"items": [
{
"defect_stock_count": 0,
"expiring_stock_count": 0,
"name": "string",
"offer_id": "string",
"sku": 0,
"valid_stock_count": 0,
"waitingdocs_stock_count": 0,
"warehouse_name": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetStockManagementParams{},
`{
"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.Analytics().Stock(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetStockManagementResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

74
ozon/clusters.go Normal file
View 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
View 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)
}
}
}

View File

@@ -333,7 +333,7 @@ const (
ServiceProcessingIdentifiedSurplus DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedSurplus"
ServiceProcessingIdentifiedDiscrepancies DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedDiscrepancies"
ServiceItemInternetSiteAdvertising DetailsServiceItemName = "MarketplaceServiceItemInternetSiteAdvertising"
ServiceItemPremiumSubscribtion DetailsServiceItemName = "MarketplaceServiceItemPremiumSubscribtion"
ServiceItemPremiumSubscribtion DetailsServiceItemName = "MarketplaceServiceItemSubscribtionPremium"
AgencyFeeAggregator3PLGlobalItem DetailsServiceItemName = "MarketplaceAgencyFeeAggregator3PLGlobalItem"
)
@@ -453,6 +453,8 @@ const (
ReadyForShipment GetFBSReturnsFilterStatus = "ready_for_shipment"
Disposing GetFBSReturnsFilterStatus = "disposing"
Disposed GetFBSReturnsFilterStatus = "disposed"
ArrivedForResale GetFBSReturnsFilterStatus = "arrived_for_resale"
MovingToResale GetFBSReturnsFilterStatus = "moving_to_resale"
)
type GetFBOReturnsFilterStatus string
@@ -559,6 +561,12 @@ const (
// financial report
ReportTypeSellerFinance ReportType = "SELLER_FINANCE"
// report on sales to legal entities
ReportTypeDocB2BSales ReportType = "DOCUMENT_B2B_SALES"
// settlement report
ReportTypeMutualSettlement ReportType = "MUTUAL_SETTLEMENT"
)
type ReportInfoStatus string
@@ -722,6 +730,8 @@ const (
// picking up products for removal by the seller
TransactionServiceReturnFromStock TransactionOperationService = "MarketplaceServiceItemReturnFromStock"
TransactionServiceStarsMembership TransactionOperationService = "ItemAgentServiceStarsMembership"
// forwarding trade
TransactionItemAdForSupplierLogisticSeller TransactionOperationService = "ItemAdvertisementForSupplierLogisticSeller"
@@ -792,7 +802,7 @@ const (
TransactionServiceRedistributionReturnsPVZ TransactionOperationService = "MarketplaceServiceItemRedistributionReturnsPVZ"
// Agregator 3PL Globalagency service tariffication
TransactionServiceAgencyFeeAggregator3PLGlobal TransactionOperationService = "OperationMarketplaceAgencyFeeAggregator3PLGlobal "
TransactionServiceAgencyFeeAggregator3PLGlobal TransactionOperationService = "OperationMarketplaceAgencyFeeAggregator3PLGlobal"
)
type PaymentTypeGroupName string
@@ -807,3 +817,109 @@ const (
PaymentTypeGroupPaymentToCurrentAccount PaymentTypeGroupName = "payment to current account"
PaymentTypeGroupSberpay PaymentTypeGroupName = "Sberpay"
)
type VisualStatus string
const (
// dispute with the customer has been opened
VisualStatusDisputeOpened VisualStatus = "DisputeOpened"
// pending with the seller
VisualStatusOnSellerApproval VisualStatus = "OnSellerApproval"
// at the pick-up point
VisualStatusArrivedAtReturnPlace VisualStatus = "ArrivedAtReturnPlace"
// pending clarification by the seller
VisualStatusOnSellerClarification VisualStatus = "OnSellerClarification"
// pending clarification by the seller after partial compensation
VisualStatusOnSellerClarificationPartial VisualStatus = "OnSellerClarificationAfterPartialCompensation"
// partial compensation offered
VisualStatusOfferedPartial VisualStatus = "OfferedPartialCompensation"
// refund approved
VisualStatusReturnMoneyApproved VisualStatus = "ReturnMoneyApproved"
// partial compensation provided
VisualStatusPartialReturned VisualStatus = "PartialCompensationReturned"
// refund rejected, dispute isn't opened
VisualStatusCancelledDisputeNotOpen VisualStatus = "CancelledDisputeNotOpen"
// request rejected
VisualStatusRejected VisualStatus = "Rejected"
// request rejected by Ozon
VisualStatusCrmRejected VisualStatus = "CrmRejected"
// request canceled
VisualStatusCancelled VisualStatus = "Cancelled"
// request approved by the seller
VisualStatusApproved VisualStatus = "Approved"
// request approved by Ozon
VisualStatusApprovedByOzon VisualStatus = "ApprovedByOzon"
// seller received the return
VisualStatusReceivedBySeller VisualStatus = "ReceivedBySeller"
// return is on its way to the seller
VisualStatusMovingToSeller VisualStatus = "MovingToSeller"
// seller received the refund
VisualStatusReturnCompensated VisualStatus = "ReturnCompensated"
// courier is taking the return to the seller
VisualStatusReturningByCourier VisualStatus = "ReturningByCourier"
// on disposal
VisualStatusUtilizing VisualStatus = "Utilizing"
// disposed of
VisualStatusUtilized VisualStatus = "Utilized"
// customer received full refund
VisualStatusMoneyReturned VisualStatus = "MoneyReturned"
// partial refund has been approved
VisualStatusPartialInProcess VisualStatus = "PartialCompensationInProcess"
// seller opened a dispute
VisualStatusDisputeYouOpened VisualStatus = "DisputeYouOpened"
// compensation rejected
VisualStatusCompensationRejected VisualStatus = "CompensationRejected"
// support request sent
VisualStatusDisputeOpening VisualStatus = "DisputeOpening"
// awaiting your decision on compensation
VisualStatusCompensationOffered VisualStatus = "CompensationOffered"
// awaiting compensation
VisualStatusWaitingCompensation VisualStatus = "WaitingCompensation"
// an error occurred when sending the support request
VisualStatusSendingError VisualStatus = "SendingError"
// decision period has expired
VisualStatusCompensationRejectedBySla VisualStatus = "CompensationRejectedBySla"
// seller has refused compensation
VisualStatusCompensationRejectedBySeller VisualStatus = "CompensationRejectedBySeller"
// on the way to the Ozon warehouse
VisualStatusMovingToOzon VisualStatus = "MovingToOzon"
// arrived at the Ozon warehouse
VisualStatusReturnedToOzon VisualStatus = "ReturnedToOzon"
// quick refund
VisualStatusMoneyReturnedBySystem VisualStatus = "MoneyReturnedBySystem"
// awaiting shipping
VisualStatusWaitingShipment VisualStatus = "WaitingShipment"
)

View File

@@ -101,9 +101,6 @@ type GetFBOShipmentsListResultAdditionalData struct {
}
type GetFBOShipmentsListResultAnalyticsData struct {
// Delivery city
City string `json:"city"`
// Delivery method
DeliveryType string `json:"delivery_type"`
@@ -118,9 +115,6 @@ type GetFBOShipmentsListResultAnalyticsData struct {
// Payment method
PaymentTypeGroupName PaymentTypeGroupName `json:"payment_type_group_name"`
// Delivery region
Region string `json:"region"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
@@ -247,9 +241,6 @@ type GetShipmentDetailsResultAdditionalData struct {
}
type GetShipmentDetailsResultAnalyticsData struct {
// Delivery city
City string `json:"city"`
// Delivery method
DeliveryType string `json:"delivery_type"`
@@ -264,9 +255,6 @@ type GetShipmentDetailsResultAnalyticsData struct {
// Payment method
PaymentTypeGroupName string `json:"payment_type_group_name"`
// Delivery region
Region string `json:"region"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
@@ -290,108 +278,38 @@ func (c FBO) GetShipmentDetails(ctx context.Context, params *GetShipmentDetailsP
}
type ListSupplyRequestsParams struct {
// Number of the page returned in the request
Page int32 `json:"page"`
// Filter
Filter *ListSupplyRequestsFilter `json:"filter"`
// Number of elements on the page
PageSize int32 `json:"page_size"`
// Customizing the display of the requests list
Paging *ListSupplyRequestsPaging `json:"paging"`
}
// Filter on status of a supply by request
States []SupplyRequestState `json:"states"`
type ListSupplyRequestsFilter struct {
States []string `json:"states"`
}
type ListSupplyRequestsPaging struct {
// Supply number from which the list of requests will start
FromOrderId int64 `json:"from_supply_order_id"`
// Number of requests in the response
Limit int32 `json:"limit"`
}
type ListSupplyRequestsResponse struct {
core.CommonResponse
// Indicates that the response contains not the entire array of supply requests:
// - true — make a new request with a different page and page_size values to get information on the remaining requests;
// - false — the entire array of requests for the filter specified in the request was returned in the response
HasNext bool `json:"has_next"`
// Supply requests list
SupplyOrders []SupplyRequestCommonResponse `json:"supply_orders"`
// Total requests number
TotalSupplyOrdersCount int32 `json:"total_supply_orders_count"`
}
type SupplyRequestCommonResponse struct {
// Supply request creation date
CreatedAt string `json:"created_at"`
// Local time supply interval
LocalTimeslot SupplyRequestCommonResponseLocalTimeslot `json:"local_timeslot"`
// Date from which you want to bring the supply to the warehouse. Only for supplies via vDC
PreferredSupplyDateFrom string `json:"preferred_supply_date_from"`
// Date by which you want to bring the supply to the warehouse. Only for supplies via vDC
PreferredSupplyDateTo string `json:"preferred_supply_date_to"`
// Your own warehouse from which you'll take the products to the supply warehouse.
// Only for supplies via vDC
SellerWarehouse SupplyRequestSellerWarehouse `json:"seller_warehouse"`
// Status of a supply by request
State string `json:"state"`
// Supply request identifier you last requested
LastSupplyOrderId int64 `json:"last_supply_order_id"`
// Supply request identifier
SupplyOrderId int64 `json:"supply_order_id"`
// Supply request number
SupplyOrderNumber string `json:"supply_order_number"`
// Supply warehouse
SupplyWarehouse SupplyRequestCommonResponseSupplyWarehouse `json:"supply_warehouse"`
// time_left_to_prepare_supply
TimeLeftToPrepareSupply int64 `json:"time_left_to_prepare_supply"`
// Time in seconds left to select the supply option. Only for supplies via vDC
TimeLeftToSelectSupplyVariant int64 `json:"time_left_to_select_supply_variant"`
// total_items_count
TotalItemsCount int32 `json:"total_items_count"`
// Total number of items in the request
TotalQuantity int32 `json:"total_quantity"`
SupplyOrderId []string `json:"supply_order_id"`
}
type SupplyRequestSellerWarehouse struct {
// Warehouse address
Address string `json:"address"`
// Warehouse name
Name string `json:"name"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
type SupplyRequestCommonResponseLocalTimeslot struct {
// Interval start
From string `json:"from"`
// Interval end
To string `json:"to"`
}
type SupplyRequestCommonResponseSupplyWarehouse struct {
// Warehouse address
Address string `json:"address"`
// Warehouse name
Name string `json:"name"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
// Method for getting a list of supply requests to the Ozon warehouse.
// Requests with supply both to a specific warehouse and via a virtual
// distribution center (vDC) are taken into account
// Requests with supply to a specific warehouse and through a virtual distribution center (vDC) are taken into account
func (c FBO) ListSupplyRequests(ctx context.Context, params *ListSupplyRequestsParams) (*ListSupplyRequestsResponse, error) {
url := "/v1/supply-order/list"
url := "/v2/supply-order/list"
resp := &ListSupplyRequestsResponse{}
@@ -405,17 +323,111 @@ func (c FBO) ListSupplyRequests(ctx context.Context, params *ListSupplyRequestsP
}
type GetSupplyRequestInfoParams struct {
// Supply request identifier
SupplyOrderId int64 `json:"supply_order_id"`
// Supply request identifier in the Ozon system
OrderIds []string `json:"order_ids"`
}
type GetSupplyRequestInfoResponse struct {
core.CommonResponse
SupplyRequestCommonResponse
// Supply request details
Orders []SupplyOrder `json:"orders"`
// Driver and car information
VehicleInfo GetSupplyRequestInfoVehicle `json:"vehicle_info"`
// Warehouse details
Warehouses []SupplyWarehouse `json:"warehouses"`
}
type SupplyOrder struct {
// Date of supply request creation
CreationDate string `json:"creation_date"`
// Request source
CreationFlow string `json:"creation_flow"`
// Time remaining in seconds to fill in the supply details. Only for requests from the vDC
DataFillingDeadline time.Time `json:"data_filling_deadline_utc"`
// Supply warehouse identifier
DropoffWarehouseId int64 `json:"dropoff_warehouse_id"`
// Filter by supply status
State string `json:"state"`
// Supply request contents
Supplies []Supply `json:"supplies"`
// Supply request identifier
Id int64 `json:"supply_order_id"`
// Request number
OrderNumber string `json:"supply_order_number"`
// Supply time slot
Timeslot []SupplyTimeslot `json:"timeslot"`
// Driver and vehicle details
Vehicle []SupplyVehicle `json:"vehicle"`
}
type Supply struct {
// Supply contents identifier. Used in the /v1/supply-order/bundle method
BundleId string `json:"bundle_id"`
// Storage warehouse identifier
StorageWarehouseId int64 `json:"storage_warehouse_id"`
// Supply identifier
Id int64 `json:"supply_id"`
}
type SupplyTimeslot struct {
// Reason why you can't select the supply time slot
Reasons []string `json:"can_not_set_reasons"`
// true, if you can select or edit the supply time slot
CanSet bool `json:"can_set"`
// true, if the characteristic is required
IsRequired bool `json:"is_required"`
Value SupplyTimeslotValue `json:"value"`
}
type SupplyVehicle struct {
// Reason why you can't select the supply time slot
Reasons []string `json:"can_not_set_reasons"`
// true, if you can select or edit the supply time slot
CanSet bool `json:"can_set"`
// true, if the characteristic is required
IsRequired bool `json:"is_required"`
Value []GetSupplyRequestInfoVehicle `json:"value"`
}
type SupplyTimeslotValue struct {
// Supply time slot in local time
Timeslot []SupplyTimeslotValueTimeslot `json:"timeslot"`
// Time zone
Timezone []SupplyTimeslotValueTimezone `json:"timezone_info"`
}
type SupplyTimeslotValueTimeslot struct {
// Supply time slot start
From time.Time `json:"from"`
// Supply time slot end
To time.Time `json:"to"`
}
type SupplyTimeslotValueTimezone struct {
// Time zone name
Name string `json:"iana_name"`
// Time zone offset from UTC-0 in seconds
Offset string `json:"offset"`
}
type GetSupplyRequestInfoVehicle struct {
@@ -432,11 +444,22 @@ type GetSupplyRequestInfoVehicle struct {
VehicleNumber string `json:"vehicle_number"`
}
type SupplyWarehouse struct {
// Warehouse address
Address string `json:"address"`
// Warehouse name
Name string `json:"name"`
// Warehouse identifier
Id int64 `json:"warehouse_id"`
}
// Method for getting detailed information on a supply request.
// Requests with supply both to a specific warehouse and via a
// virtual distribution center (vDC) are taken into account
func (c FBO) GetSupplyRequestInfo(ctx context.Context, params *GetSupplyRequestInfoParams) (*GetSupplyRequestInfoResponse, error) {
url := "/v1/supply-order/get"
url := "/v2/supply-order/get"
resp := &GetSupplyRequestInfoResponse{}
@@ -563,3 +586,555 @@ func (c FBO) GetWarehouseWorkload(ctx context.Context) (*GetWarehouseWorkloadRes
return resp, nil
}
type GetSupplyOrdersByStatusParams struct {
}
type GetSupplyOrdersByStatusResponse struct {
core.CommonResponse
Items []SupplyOrdersByStatus `json:"items"`
}
type SupplyOrdersByStatus struct {
// Number of supply requests in this status
Count int32 `json:"count"`
// Supply status
OrderState string `json:"order_state"`
}
// Returns the number of supply requests in a specific status.
func (c FBO) GetSupplyOrdersByStatus(ctx context.Context) (*GetSupplyOrdersByStatusResponse, error) {
url := "/v1/supply-order/status/counter"
resp := &GetSupplyOrdersByStatusResponse{}
response, err := c.client.Request(ctx, http.MethodGet, url, &GetSupplyOrdersByStatusParams{}, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type GetSupplyTimeslotsParams struct {
// Supply request identifier
SupplyOrderId int64 `json:"supply_order_id"`
}
type GetSupplyTimeslotsResponse struct {
core.CommonResponse
// Supply time slot
Timeslots []SupplyTimeslotValueTimeslot `json:"timeslots"`
// Time zone
Timezones []SupplyTimeslotValueTimezone `json:"timezone"`
}
func (c FBO) GetSupplyTimeslots(ctx context.Context, params *GetSupplyTimeslotsParams) (*GetSupplyTimeslotsResponse, error) {
url := "/v1/supply-order/timeslot/get"
resp := &GetSupplyTimeslotsResponse{}
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 UpdateSupplyTimeslotParams struct {
// Supply request identifier
SupplyOrderId int64 `json:"supply_order_id"`
// Supply time slot details
Timeslot SupplyTimeslotValueTimeslot `json:"timeslot"`
}
type UpdateSupplyTimeslotResponse struct {
core.CommonResponse
// Possible errors
Errors []string `json:"errors"`
// Operation identifier
OperationId string `json:"operation_id"`
}
func (c FBO) UpdateSupplyTimeslot(ctx context.Context, params *UpdateSupplyTimeslotParams) (*UpdateSupplyTimeslotResponse, error) {
url := "/v1/supply-order/timeslot/update"
resp := &UpdateSupplyTimeslotResponse{}
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 GetSupplyTimeslotStatusParams struct {
// Operation identifier
OperationId string `json:"operation_id"`
}
type GetSupplyTimeslotStatusResponse struct {
core.CommonResponse
// Possible errors
Errors []string `json:"errors"`
// Data status
Status string `json:"status"`
}
func (c FBO) GetSupplyTimeslotStatus(ctx context.Context, params *GetSupplyTimeslotStatusParams) (*GetSupplyTimeslotStatusResponse, error) {
url := "/v1/supply-order/timeslot/status"
resp := &GetSupplyTimeslotStatusResponse{}
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 CreatePassParams struct {
// Supply request identifier
SupplyOrderId int64 `json:"supply_order_id"`
// Driver and car information
Vehicle GetSupplyRequestInfoVehicle `json:"vehicle"`
}
type CreatePassResponse struct {
core.CommonResponse
// Possible errors
Errors []string `json:"error_reasons"`
// Operation identifier
OperationId string `json:"operation_id"`
}
func (c FBO) CreatePass(ctx context.Context, params *CreatePassParams) (*CreatePassResponse, error) {
url := "/v1/supply-order/pass/create"
resp := &CreatePassResponse{}
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 GetPassParams struct {
// Operation identifier
OperationId string `json:"operation_id"`
}
type GetPassResponse struct {
core.CommonResponse
// Possible errors
Errors []string `json:"errors"`
// Status of driver and vehicle data entry
Result string `json:"result"`
}
func (c FBO) GetPass(ctx context.Context, params *GetPassParams) (*GetPassResponse, error) {
url := "/v1/supply-order/pass/status"
resp := &GetPassResponse{}
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 GetSupplyContentParams struct {
// Identifiers of supply contents. Minimum is 1, maximum is 1000. You can get them using the /v2/supply-order/get method
BundleIds []string `json:"bundle_ids"`
// true, to sort in ascending order
IsAsc bool `json:"is_asc"`
// Identifier of the last value on the page
LastId string `json:"last_id"`
// Number of values on the page. Minimum is 1, maximum is 1000
Limit int32 `json:"limit"`
// Search query, for example: by name, article code, or SKU
Query string `json:"query"`
// Sorting by parameters
SortField string `json:"sort_field"`
}
type GetSupplyContentResponse struct {
core.CommonResponse
// List of products in the supply request
Items []SupplyContentItem `json:"items"`
// Quantity of products in the request
TotalCount int32 `json:"total_count"`
// Indication that the response hasn't returned all products
HasNext bool `json:"has_next"`
// Identifier of the last value on the page
LastId string `json:"last_id"`
}
type SupplyContentItem struct {
// Link to product image
IconPath string `json:"icon_path"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Product name
Name string `json:"name"`
// Product items quantity
Quantity int32 `json:"quantity"`
// Barcode
Barcode string `json:"barcode"`
// Product identifier
ProductId int64 `json:"product_id"`
// Quantity of products in one package
Quant int32 `json:"quant"`
// true if the quantity of products in one package can be edited
IsQuantEditable bool `json:"is_quant_editable"`
// Volume of products in liters
VolumeInLiters float64 `json:"volume_in_litres"`
// Volume of all products in liters
TotalVolumeInLiters float64 `json:"total_volume_in_litres"`
// Product article code
ContractorItemCode string `json:"contractor_item_code"`
// Super product label
SFBOAttribute string `json:"sfbo_attribute"`
// Type of wrapper
ShipmentType string `json:"shipment_type"`
}
func (c FBO) GetSupplyContent(ctx context.Context, params *GetSupplyContentParams) (*GetSupplyContentResponse, error) {
url := "/v1/supply-order/bundle"
resp := &GetSupplyContentResponse{}
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 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
}

View File

@@ -58,8 +58,6 @@ func TestGetFBOShipmentsList(t *testing.T) {
}
],
"analytics_data": {
"region": "РОСТОВСКАЯ ОБЛАСТЬ",
"city": "Ростов-на-Дону",
"delivery_type": "PVZ",
"is_premium": false,
"payment_type_group_name": "Карты оплаты",
@@ -191,8 +189,6 @@ func TestGetShipmentDetails(t *testing.T) {
}
],
"analytics_data": {
"region": "МОСКВА",
"city": "Москва",
"delivery_type": "Courier",
"is_premium": false,
"payment_type_group_name": "Карты оплаты",
@@ -301,36 +297,19 @@ func TestListSupplyRequests(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListSupplyRequestsParams{
Page: 0,
PageSize: 0,
States: []SupplyRequestState{AcceptanceAtStorageWarehouse},
Filter: &ListSupplyRequestsFilter{
States: []string{"ORDER_STATE_DATA_FILLING"},
},
Paging: &ListSupplyRequestsPaging{
FromOrderId: 0,
Limit: 0,
},
},
`{
"has_next": true,
"supply_orders": [
{
"created_at": "string",
"local_timeslot": {
"from": "string",
"to": "string"
},
"preferred_supply_date_from": "string",
"preferred_supply_date_to": "string",
"state": "string",
"supply_order_id": 0,
"supply_order_number": "string",
"supply_warehouse": {
"address": "string",
"name": "string",
"warehouse_id": 0
},
"time_left_to_prepare_supply": 0,
"time_left_to_select_supply_variant": 0,
"total_items_count": 0,
"total_quantity": 0
}
],
"total_supply_orders_count": 0
"last_supply_order_id": 0,
"supply_order_id": [
"string"
]
}`,
},
// Test No Client-Id or Api-Key
@@ -377,39 +356,74 @@ func TestGetSupplyRequestInfo(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetSupplyRequestInfoParams{
SupplyOrderId: 0,
OrderIds: []string{"string"},
},
`{
"created_at": "string",
"local_timeslot": {
"from": "string",
"to": "string"
},
"preferred_supply_date_from": "string",
"preferred_supply_date_to": "string",
"seller_warehouse": {
"address": "string",
"name": "string",
"warehouse_id": 0
},
"state": "string",
"supply_order_id": 0,
"supply_order_number": "string",
"supply_warehouse": {
"address": "string",
"name": "string",
"warehouse_id": 0
},
"time_left_to_prepare_supply": 0,
"time_left_to_select_supply_variant": 0,
"total_items_count": 0,
"total_quantity": 0,
"vehicle_info": {
"driver_name": "string",
"driver_phone": "string",
"vehicle_model": "string",
"vehicle_number": "string"
}
"orders": [
{
"creation_date": "string",
"creation_flow": "string",
"data_filling_deadline_utc": "2019-08-24T14:15:22Z",
"dropoff_warehouse_id": 0,
"state": "ORDER_STATE_UNSPECIFIED",
"supplies": [
{
"bundle_id": "string",
"storage_warehouse_id": 0,
"supply_id": 0
}
],
"supply_order_id": 0,
"supply_order_number": "string",
"timeslot": [
{
"can_not_set_reasons": [
"string"
],
"can_set": true,
"is_required": true,
"value": {
"timeslot": [
{
"from": "2019-08-24T14:15:22Z",
"to": "2019-08-24T14:15:22Z"
}
],
"timezone_info": [
{
"iana_name": "string",
"offset": "string"
}
]
}
}
],
"vehicle": [
{
"can_not_set_reasons": [
"string"
],
"can_set": true,
"is_required": true,
"value": [
{
"driver_name": "string",
"driver_phone": "string",
"vehicle_model": "string",
"vehicle_number": "string"
}
]
}
]
}
],
"warehouses": [
{
"address": "string",
"name": "string",
"warehouse_id": 0
}
]
}`,
},
// Test No Client-Id or Api-Key
@@ -565,3 +579,694 @@ func TestGetWarehouseWorkload(t *testing.T) {
}
}
}
func TestGetSupplyOrdersByStatus(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"items": [
{
"count": 0,
"order_state": "ORDER_STATE_UNSPECIFIED"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
`{
"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().GetSupplyOrdersByStatus(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetSupplyOrdersByStatusResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetSupplyTimeslots(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetSupplyTimeslotsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetSupplyTimeslotsParams{
SupplyOrderId: 0,
},
`{
"timeslots": [
{
"from": "2019-08-24T14:15:22Z",
"to": "2019-08-24T14:15:22Z"
}
],
"timezone": [
{
"iana_name": "string",
"offset": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetSupplyTimeslotsParams{},
`{
"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().GetSupplyTimeslots(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetSupplyTimeslotsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestUpdateSupplyTimeslot(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *UpdateSupplyTimeslotParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&UpdateSupplyTimeslotParams{
SupplyOrderId: 0,
Timeslot: SupplyTimeslotValueTimeslot{
From: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
To: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
},
},
`{
"errors": [
"UPDATE_TIMESLOT_ERROR_UNSPECIFIED"
],
"operation_id": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&UpdateSupplyTimeslotParams{},
`{
"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().UpdateSupplyTimeslot(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UpdateSupplyTimeslotResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetSupplyTimeslotStatus(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetSupplyTimeslotStatusParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetSupplyTimeslotStatusParams{
OperationId: "string",
},
`{
"errors": [
"UPDATE_TIMESLOT_ERROR_UNSPECIFIED"
],
"status": "STATUS_UNSPECIFIED"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetSupplyTimeslotStatusParams{},
`{
"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().GetSupplyTimeslotStatus(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetSupplyTimeslotStatusResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestCreatePass(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *CreatePassParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&CreatePassParams{
SupplyOrderId: 123,
Vehicle: GetSupplyRequestInfoVehicle{
DriverName: "string",
DriverPhone: "string",
VehicleModel: "string",
VehicleNumber: "string",
},
},
`{
"error_reasons": [
"SET_VEHICLE_ERROR_UNSPECIFIED"
],
"operation_id": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&CreatePassParams{},
`{
"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().CreatePass(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CreatePassResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetPass(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetPassParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetPassParams{
OperationId: "string",
},
`{
"errors": [
"SET_VEHICLE_ERROR_UNSPECIFIED"
],
"result": "Unknown"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetPassParams{},
`{
"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().GetPass(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetPassResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetSupplyContent(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetSupplyContentParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetSupplyContentParams{
BundleIds: []string{"string"},
IsAsc: true,
Limit: 0,
Query: "string",
SortField: "UNSPECIFIED",
},
`{
"items": [
{
"icon_path": "string",
"sku": 0,
"name": "string",
"quantity": 0,
"barcode": "string",
"product_id": 0,
"quant": 0,
"is_quant_editable": true,
"volume_in_litres": 0,
"total_volume_in_litres": 0,
"contractor_item_code": "string",
"sfbo_attribute": "ITEM_SFBO_ATTRIBUTE_UNSPECIFIED",
"shipment_type": "BUNDLE_ITEM_SHIPMENT_TYPE_UNSPECIFIED"
}
],
"total_count": 0,
"has_next": true,
"last_id": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetSupplyContentParams{},
`{
"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().GetSupplyContent(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetSupplyContentResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
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)
}
}
}

View File

@@ -53,6 +53,11 @@ type ListUnprocessedShipmentsFilter struct {
// Delivery method identifier
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:
//
// Default value is all.
@@ -106,6 +111,9 @@ type FBSPosting struct {
// Analytics data
AnalyticsData FBSPostingAnalyticsData `json:"analytics_data"`
// Available actions and shipment information
AvailableActions []string `json:"available_actions"`
// Shipment barcodes
Barcodes FBSBarcode `json:"barcodes"`
@@ -178,6 +186,44 @@ type FBSPosting struct {
// Shipment tracking number
TrackingNumber string `json:"tracking_number"`
// Details on shipping rate
Tariffication []FBSPostingTariffication `json:"tariffication"`
// Economy product identifier
QuantumId int64 `json:"quantum_id"`
}
type FBSPostingTariffication struct {
// Current shipping rate as a percentage
CurrentTariffRate float64 `json:"current_tariff_rate"`
// Type of shipping rate adjustment: discount or surcharge
CurrentTariffType string `json:"current_tariff_type"`
// Current amount of discount or surcharge
CurrentTariffCharge string `json:"current_tariff_charge"`
// Currency of the amount
CurrencyTariffCurrencyCode string `json:"current_tariff_charge_currency_code"`
// Percentage by which the shipping rate is adjusted
// after the time specified in the next_tariff_starts_at parameter
NextTariffRate float64 `json:"next_tariff_rate"`
// The adjustment type applied to the shipping rate
// after the time specified in the next_tariff_starts_at parameter:
// discount or surcharge
NextTariffType string `json:"next_tariff_type"`
// Discount or surcharge amount applied during the next shipping rate adjustment step
NextTariffCharge string `json:"next_tariff_charge"`
// Date and time when the new shipping rate is applied
NextTariffStartsAt time.Time `json:"next_tariff_starts_at"`
// New shipping rate currency
NextTariffCurrencyCode string `json:"next_tariff_charge_currency_code"`
}
type FBSPostingAddressee struct {
@@ -191,7 +237,7 @@ type FBSPostingAddressee struct {
}
type FBSPostingAnalyticsData struct {
// Delivery city
// Delivery city. Only for rFBS shipments
City string `json:"city"`
// Delivery start date and time
@@ -215,7 +261,7 @@ type FBSPostingAnalyticsData struct {
// Payment method
PaymentTypeGroupName PaymentTypeGroupName `json:"payment_type_group_name"`
// Delivery region
// Delivery region. Only for rFBS shipments
Region string `json:"region"`
// Delivery service
@@ -541,6 +587,11 @@ type GetFBSShipmentsListFilter struct {
// Order identifier
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
ProviderId []int64 `json:"provider_id"`
@@ -912,6 +963,9 @@ type GetShipmentDataByIdentifierResult struct {
// Analytics data
AnalyticsData GetShipmentDataByIdentifierResultAnalyticsData `json:"analytics_data"`
// Available actions and shipment information
AvailableActions []string `json:"available_actions"`
// Shipment barcodes
Barcodes FBSBarcode `json:"barcodes"`
@@ -999,6 +1053,9 @@ type GetShipmentDataByIdentifierResult struct {
// Shipment tracking number
TrackingNumber string `json:"tracking_number"`
// Details on shipping rate
Tariffication []FBSPostingTariffication `json:"tariffication"`
}
type GetShipmentDataByIdentifierResultAdditionalData struct {
@@ -1020,7 +1077,7 @@ type GetShipmentDataByIdentifierResultAddressee struct {
}
type GetShipmentDataByIdentifierResultAnalyticsData struct {
// Delivery city
// Delivery city. Only for rFBS shipments
City string `json:"city"`
// Delivery start date and time
@@ -1043,7 +1100,7 @@ type GetShipmentDataByIdentifierResultAnalyticsData struct {
// Payment method
PaymentTypeGroupName string `json:"payment_type_group_name"`
// Delivery region
// Delivery region. Only for rFBS shipments
Region string `json:"region"`
// Delivery service
@@ -2188,6 +2245,12 @@ type AvailableFreightsListResult struct {
// Number of already packaged shipments
MandatoryPackagedCount int32 `json:"mandatory_packaged_count"`
// Recommended local time of shipping to the pick-up point
RecommendedTimeLocal string `json:"recommended_time_local"`
// Time zone offset of the recommended shipping time from UTC-0 in minutes
RecommendedTimeUTCOffset int32 `json:"recommended_time_utc_offset_in_minutes"`
// Delivery service icon link
TPLProviderIconURL string `json:"tpl_provider_icon_url"`
@@ -2525,20 +2588,11 @@ type CancelSendingResponse struct {
// Use this method if you cannot send some of the products from the shipment.
//
// If you are using the rFBS scheme, you have the following cancellation reason identifiers (`cancel_reason_id`) available:
//
// 352—product is out of stock;
// 400—only defective products left;
// 401—cancellation from arbitration;
// 402—other reason;
// 665—the customer did not pick the order;
// 666—delivery is not available in the region;
// 667—order was lost by the delivery service.
// The last 4 reasons are available for shipments in the "Delivering" and "Courier on the way" statuses.
// To get the cancel_reason_id cancellation reason identifiers
// when working with the FBS or rFBS schemes,
// use the /v2/posting/fbs/cancel-reason/list method.
//
// You can't cancel presumably delivered orders.
//
// If `cancel_reason_id` parameter value is 402, fill the `cancel_reason_message` field.
func (c FBS) CancelSending(ctx context.Context, params *CancelSendingParams) (*CancelSendingResponse, error) {
url := "/v2/posting/fbs/product/cancel"
@@ -3016,3 +3070,167 @@ func (c FBS) GetCarriage(ctx context.Context, params *GetCarriageParams) (*GetCa
return resp, nil
}
type GetCancellationReasonsResponse struct {
core.CommonResponse
// Method result
Result []GetCancellationReasonsResult `json:"result"`
}
type GetCancellationReasonsResult struct {
// Cancellation reasons identifier
Id int64 `json:"id"`
// Shipment cancellation result. true if the request is available for cancellation
IsAvailableForCancellation bool `json:"is_available_for_cancellation"`
// Category name
Title string `json:"title"`
// Shipment cancellation initiator
TypeId string `json:"type_id"`
}
func (c FBS) GetCancellationReasons(ctx context.Context) (*GetCancellationReasonsResponse, error) {
url := "/v1/posting/fbo/cancel-reason/list"
resp := &GetCancellationReasonsResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type SetShippingDateParams struct {
// New shipping date
NewCutoffDate time.Time `json:"new_cutoff_date"`
// Shipment number
PostingNumber string `json:"posting_number"`
}
type SetShippingDateResponse struct {
core.CommonResponse
// true if the new date is set
Result bool `json:"result"`
}
func (c FBS) SetShippingDate(ctx context.Context, params *SetShippingDateParams) (*SetShippingDateResponse, error) {
url := "/v1/posting/cutoff/set"
resp := &SetShippingDateResponse{}
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 SplitOrderParams struct {
// Shipment number
PostingNumber string `json:"posting_number"`
// Shipments list the order will be split into. You can split one order per one request
Postings []SplitOrderParamPosting `json:"postings"`
}
type SplitOrderParamPosting struct {
Products []SplitOrderPostingProduct `json:"products"`
}
type SplitOrderResponse struct {
core.CommonResponse
// Original shipment details
ParentPosting SplitOrderPosting `json:"parent_posting"`
// List of shipments the order was split into
Postings []SplitOrderPosting `json:"postings"`
}
type SplitOrderPosting struct {
// Shipment number
PostingNumber string `json:"posting_number"`
// List of products in the shipment
Products []SplitOrderPostingProduct `json:"products"`
}
type SplitOrderPostingProduct struct {
// FBS product identifier in the Ozon system, SKU
ProductId int64 `json:"product_id"`
// Product quantity
Quantity int64 `json:"quantity"`
}
func (c FBS) SplitOrder(ctx context.Context, params *SplitOrderParams) (*SplitOrderResponse, error) {
url := "/v1/posting/fbs/split"
resp := &SplitOrderResponse{}
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 ListUnpaidProductsParams struct {
// Cursor for the next data sample
Cursor string `json:"cursor"`
// Number of values in the response
Limit int32 `json:"limit,omitempty"`
}
type ListUnpaidProductsResponse struct {
core.CommonResponse
Products []UnpaidProduct `json:"products"`
// Cursor for the next data sample
Cursor string `json:"cursor"`
}
type UnpaidProduct struct {
// Product identifier
ProductId int64 `json:"product_id"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product quantity, pcs
Quantity int32 `json:"quantity"`
// Product name
Name string `json:"name"`
// Link to product image
ImageURL string `json:"image_url"`
}
func (c FBS) ListUnpaidProducts(ctx context.Context, params *ListUnpaidProductsParams) (*ListUnpaidProductsResponse, error) {
url := "/v1/posting/unpaid-legal/product/list"
resp := &ListUnpaidProductsResponse{}
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
}

View File

@@ -66,6 +66,7 @@ func TestListUnprocessedShipments(t *testing.T) {
"cancellation_initiator": ""
},
"customer": null,
"quantum_id": 0,
"products": [
{
"currency_code": "RUB",
@@ -146,7 +147,20 @@ func TestListUnprocessedShipments(t *testing.T) {
"requirements": {
"products_requiring_gtd": [],
"products_requiring_country": []
}
},
"tariffication": [
{
"current_tariff_rate": 0,
"current_tariff_type": "",
"current_tariff_charge": "",
"current_tariff_charge_currency_code": "",
"next_tariff_rate": 0,
"next_tariff_type": "",
"next_tariff_charge": "",
"next_tariff_starts_at": "2023-11-13T08:05:57.657Z",
"next_tariff_charge_currency_code": ""
}
]
}
],
"count": 55
@@ -264,11 +278,25 @@ func TestGetFBSShipmentsList(t *testing.T) {
"analytics_data": null,
"financial_data": null,
"is_express": false,
"quantum_id": 0,
"requirements": {
"products_requiring_gtd": [],
"products_requiring_country": [],
"products_requiring_mandatory_mark": []
}
},
"tariffication": [
{
"current_tariff_rate": 0,
"current_tariff_type": "",
"current_tariff_charge": "",
"current_tariff_charge_currency_code": "",
"next_tariff_rate": 0,
"next_tariff_type": "",
"next_tariff_charge": "",
"next_tariff_starts_at": "2023-11-13T08:05:57.657Z",
"next_tariff_charge_currency_code": ""
}
]
}
],
"has_next": true
@@ -616,7 +644,20 @@ func TestGetShipmentDataByIdentifier(t *testing.T) {
"products_requiring_gtd": [],
"products_requiring_country": []
},
"product_exemplars": null
"product_exemplars": null,
"tariffication": [
{
"current_tariff_rate": 0,
"current_tariff_type": "",
"current_tariff_charge": "",
"current_tariff_charge_currency_code": "",
"next_tariff_rate": 0,
"next_tariff_type": "",
"next_tariff_charge": "",
"next_tariff_starts_at": "2023-11-13T08:05:57.657Z",
"next_tariff_charge_currency_code": ""
}
]
}
}`,
},
@@ -1883,6 +1924,8 @@ func TestAvailableFreightsList(t *testing.T) {
"has_entrusted_acceptance": true,
"mandatory_postings_count": 0,
"mandatory_packaged_count": 0,
"recommended_time_local": "string",
"recommended_time_utc_offset_in_minutes": 0,
"tpl_provider_icon_url": "string",
"tpl_provider_name": "string",
"warehouse_city": "string",
@@ -3010,3 +3053,262 @@ func TestGetCarriage(t *testing.T) {
}
}
}
func TestGetCancellationReasons(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"result": [
{
"id": 352,
"title": "The products ran out at the seller's warehouse",
"type_id": "seller",
"is_available_for_cancellation": true
},
{
"id": 401,
"title": "Seller rejects arbitration",
"type_id": "seller",
"is_available_for_cancellation": false
},
{
"id": 402,
"title": "Other (seller's fault)",
"type_id": "seller",
"is_available_for_cancellation": true
},
{
"id": 666,
"title": "Return from the delivery service: there is no delivery to the specified region",
"type_id": "seller",
"is_available_for_cancellation": false
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
`{
"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.FBS().GetCancellationReasons(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetCancellationReasonsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestSetShippingDate(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SetShippingDateParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SetShippingDateParams{
NewCutoffDate: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
},
`{
"result": true
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SetShippingDateParams{},
`{
"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.FBS().SetShippingDate(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &SetShippingDateResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestSplitOrder(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SplitOrderParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SplitOrderParams{
PostingNumber: "string",
Postings: []SplitOrderParamPosting{
{
Products: []SplitOrderPostingProduct{
{
ProductId: 1,
Quantity: 1,
},
},
},
},
},
`{
"parent_posting": {
"posting_number": "string",
"products": [
{
"product_id": 0,
"quantity": 0
}
]
},
"postings": [
{
"posting_number": "string",
"products": [
{
"product_id": 0,
"quantity": 0
}
]
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SplitOrderParams{},
`{
"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.FBS().SplitOrder(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &SplitOrderResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestListUnpaidProducts(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListUnpaidProductsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListUnpaidProductsParams{
Cursor: "hCGiPPopcBFMgMErdzaCEpzQfinuPyEhUoSmBMADuoFAhBjXeA==",
Limit: 1000,
},
`{
"products": [
{
"product_id": 145123054,
"offer_id": "10032",
"quantity": 1,
"name": "Телевизор LG",
"image_url": "https://cdn1.ozon.ru/multimedia/10741275.jpg"
}
],
"cursor": "hCGiPPopcBFMgMErdzaCEpzQfinuPyEhUoSmBMADuoFAhBjXeA=="
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListUnpaidProductsParams{},
`{
"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.FBS().ListUnpaidProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListUnpaidProductsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

@@ -13,8 +13,11 @@ type Finance struct {
}
type ReportOnSoldProductsParams struct {
// Time period in the `YYYY-MM` format
Date string `json:"date"`
// Month
Month int32 `json:"month"`
// Year
Year int32 `json:"year"`
}
type ReportOnSoldProductsResponse struct {
@@ -34,7 +37,7 @@ type ReportonSoldProductsResult struct {
type ReportOnSoldProductsResultHeader struct {
// Report ID
Id string `json:"num"`
Id string `json:"number"`
// Report generation date
DocDate string `json:"doc_date"`
@@ -43,10 +46,10 @@ type ReportOnSoldProductsResultHeader struct {
ContractDate string `json:"contract_date"`
// Offer agreement number
ContractNum string `json:"contract_num"`
ContractNum string `json:"contract_number"`
// Currency of your prices
CurrencyCode string `json:"currency_code"`
CurrencySysName string `json:"currency_sys_name"`
// Amount to accrue
DocAmount float64 `json:"doc_amount"`
@@ -64,13 +67,13 @@ type ReportOnSoldProductsResultHeader struct {
PayerName string `json:"payer_name"`
// Recipient's TIN
RecipientINN string `json:"rcv_inn"`
RecipientINN string `json:"receiver_inn"`
// Recipient's Tax Registration Reason Code (KPP)
RecipientKPP string `json:"rcv_kpp"`
RecipientKPP string `json:"receiver_kpp"`
// Recipient's name
RecipientName string `json:"rcv_name"`
RecipientName string `json:"receiver_name"`
// Period start in the report
StartDate string `json:"start_date"`
@@ -81,13 +84,28 @@ type ReportOnSoldProductsResultHeader struct {
type ReportOnSoldProductsResultRow struct {
// Row number
RowNumber int32 `json:"row_number"`
RowNumber int32 `json:"rowNumber"`
// Product ID
ProductId int64 `json:"product_id"`
// Product Information
Item ReturnOnSoldProduct `json:"item"`
// Commission including the quantity of products, discounts and extra charges.
// Ozon compensates it for the returned products
ReturnCommission ReturnCommission `json:"return_commission"`
// Percentage of sales commission by category
CommissionRatio float64 `json:"commission_ratio"`
// Delivery fee
DeliveryCommission ReturnCommission `json:"delivery_commission"`
// Seller's discounted price
SellerPricePerInstance float64 `json:"seller_price_per_instance"`
}
type ReturnOnSoldProduct struct {
// Product name
ProductName string `json:"product_name"`
ProductName string `json:"name"`
// Product barcode
Barcode string `json:"barcode"`
@@ -95,58 +113,39 @@ type ReportOnSoldProductsResultRow struct {
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Sales commission by category
CommissionPercent float64 `json:"commission_percent"`
SKU int64 `json:"sku"`
}
// Seller's price with their discount
Price float64 `json:"price"`
type ReturnCommission struct {
// Amount
Amount float64 `json:"amount"`
// Selling price: the price at which the customer purchased the product. For sold products
PriceSale float64 `json:"price_sale"`
// Sold for amount.
//
// Sold products cost considering the quantity and regional coefficients. Calculation is made by the sale_amount price
SaleAmount float64 `json:"sale_amount"`
// Points for discounts
Bonus float64 `json:"bonus"`
// Commission for sold products, including discounts and extra charges
SaleCommission float64 `json:"sale_commission"`
Commission float64 `json:"commission"`
// Extra charge at the expense of Ozon.
//
// Amount that Ozon will compensate the seller if the Ozon discount is greater than or equal to the sales commission
SaleDiscount float64 `json:"sale_discount"`
// Additional payment at the expense of Ozon
Compensation float64 `json:"compensation"`
// Total accrual for the products sold.
//
// Amount after deduction of sales commission, application of discounts and extra charges
SalePriceSeller float64 `json:"sale_price_seller"`
// Price per item
PricePerInstance float64 `json:"price_per_instance"`
// Quantity of products sold at the price_sale price
SaleQuantity int32 `json:"sale_qty"`
// Product quantity
Quantity int32 `json:"quantity"`
// Price at which the customer purchased the product. For returned products
ReturnSale float64 `json:"return_sale"`
// Ozon referral fee
StandardFee float64 `json:"standard_fee"`
// Cost of returned products, taking into account the quantity and regional coefficients.
// Calculation is carried out at the return_sale price
ReturnAmount float64 `json:"return_amount"`
// Payouts on partner loyalty mechanics: green prices
BankCoinvestment float64 `json:"bank_coinvestment"`
// Commission including the quantity of products, discounts and extra charges.
// Ozon compensates it for the returned products
ReturnCommission float64 `json:"return_commission"`
// Payouts on partner loyalty mechanics: stars
Stars float64 `json:"stars"`
// Extra charge at the expense of Ozon.
//
// Amount of the discount at the expense of Ozon on returned products.
// Ozon will compensate it to the seller if the Ozon discount is greater than or equal to the sales commission
ReturnDiscount float64 `json:"return_discount"`
// Amount charged to the seller for returned products after deducing sales commissions, applying discounts and extra charges
ReturnPriceSeller float64 `json:"return_price_seller"`
// Quantity of returned products
ReturnQuantity int32 `json:"return_qty"`
// Total accrual
Total float64 `json:"total"`
}
// Returns information on products sold and returned within a month. Canceled or non-purchased products are not included.
@@ -404,3 +403,57 @@ func (c Finance) ListTransactions(ctx context.Context, params *ListTransactionsP
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
}

View File

@@ -23,52 +23,67 @@ func TestReportOnSoldProducts(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ReportOnSoldProductsParams{
Date: "2022-09",
Month: 9,
Year: 2022,
},
`{
"result": {
"header": {
"doc_date": "2022-09-22",
"num": "string",
"start_date": "2022-09-02",
"stop_date": "2022-09-22",
"contract_date": "2022-09-02",
"contract_num": "string",
"payer_name": "string",
"payer_inn": "string",
"payer_kpp": "string",
"rcv_name": "string",
"rcv_inn": "string",
"rcv_kpp": "string",
"doc_amount": 1,
"vat_amount": 1,
"currency_code": "string"
},
"rows": [
{
"row_number": 0,
"product_id": 0,
"product_name": "string",
"offer_id": "string",
"barcode": "string",
"price": 0,
"commission_percent": 0,
"price_sale": 0,
"sale_qty": 0,
"sale_amount": 0,
"sale_discount": 0,
"sale_commission": 0,
"sale_price_seller": 0,
"return_sale": 0,
"return_qty": 0,
"return_amount": 0,
"return_discount": 0,
"return_commission": 0,
"return_price_seller": 0
}
]
}
}`,
"result": {
"header": {
"contract_date": "string",
"contract_number": "string",
"currency_sys_name": "string",
"doc_amount": 0,
"doc_date": "string",
"number": "string",
"payer_inn": "string",
"payer_kpp": "string",
"payer_name": "string",
"receiver_inn": "string",
"receiver_kpp": "string",
"receiver_name": "string",
"start_date": "string",
"stop_date": "string",
"vat_amount": 0
},
"rows": [
{
"commission_ratio": 0,
"delivery_commission": {
"amount": 0,
"bonus": 0,
"commission": 0,
"compensation": 0,
"price_per_instance": 0,
"quantity": 0,
"standard_fee": 0,
"bank_coinvestment": 0,
"stars": 0,
"total": 0
},
"item": {
"barcode": "string",
"name": "string",
"offer_id": "string",
"sku": 0
},
"return_commission": {
"amount": 0,
"bonus": 0,
"commission": 0,
"compensation": 0,
"price_per_instance": 0,
"quantity": 0,
"standard_fee": 0,
"bank_coinvestment": 0,
"stars": 0,
"total": 0
},
"rowNumber": 0,
"seller_price_per_instance": 0
}
]
}
}`,
"",
},
// Test No Client-Id or Api-Key
@@ -271,3 +286,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)
}
}
}
}

View File

@@ -42,6 +42,8 @@ type Client struct {
strategies *Strategies
barcodes *Barcodes
passes *Passes
clusters *Clusters
quants *Quants
}
func (c Client) Analytics() *Analytics {
@@ -124,6 +126,14 @@ func (c Client) Passes() *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)
func WithHttpClient(httpClient core.HttpClient) ClientOption {
@@ -188,6 +198,8 @@ func NewClient(opts ...ClientOption) *Client {
strategies: &Strategies{client: coreClient},
barcodes: &Barcodes{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},
barcodes: &Barcodes{client: coreClient},
passes: &Passes{client: coreClient},
clusters: &Clusters{client: coreClient},
quants: &Quants{client: coreClient},
}
}

View File

@@ -525,6 +525,14 @@ type UpdateQuantityStockProductsStock struct {
// Product identifier
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
Stock int64 `json:"stock"`
@@ -549,6 +557,12 @@ type UpdateQuantityStockProductsResult struct {
// Product identifier
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
Updated bool `json:"updated"`
@@ -686,6 +700,14 @@ type UpdatePricesPrice struct {
// Product identifier
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 {
@@ -714,8 +736,8 @@ type UpdatePricesResultError struct {
}
// Allows you to change a price of one or more products.
// You can change prices for 1000 products in one request.
// To reset old_price, set 0 for this parameter
// The price of each product can be updated no more than 10 times per hour.
// To reset old_price, set 0 for this parameter.
func (c Products) UpdatePrices(ctx context.Context, params *UpdatePricesParams) (*UpdatePricesResponse, error) {
url := "/v1/product/import/prices"
@@ -822,6 +844,12 @@ type CreateOrUpdateProductItem struct {
// - IS_NO_CODE_SERVICE
ServiceType string `json:"service_type" default:"IS_CODE_SERVICE"`
// Product type identifier.
// You can get values from the type_id parameter in the `/v1/description-category/tree` method response.
// When filling this parameter in,
// you can leave out the attributes attribute if it has the `id:8229` parameter
TypeId int64 `json:"type_id"`
// VAT rate for the product:
// - 0 — not subject to VAT,
// - 0.1 — 10%,
@@ -958,6 +986,10 @@ type GetListOfProductsResultItem struct {
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) {
url := "/v2/product/list"
@@ -1168,9 +1200,11 @@ type CreateProductByOzonIDResult struct {
UnmatchedSKUList []int64 `json:"unmatched_sku_list"`
}
// Creates a product by the specified Ozon ID. The number of products is unlimited.
// The method creates a copy of the product description page with the specified SKU.
//
// It's not possible to update products using Ozon ID
// You cannot create a copy if the seller has prohibited the copying of their PDPs.
//
// It's not possible to update products using SKU.
func (c Products) CreateProductByOzonID(ctx context.Context, params *CreateProductByOzonIDParams) (*CreateProductByOzonIDResponse, error) {
url := "/v1/product/import-by-sku"
@@ -1412,9 +1446,6 @@ type GetDescriptionOfProductResult struct {
// Product characteristic identifier
Id int64 `json:"id"`
// Identifier for subsequent batch loading of images
ImageGroupId string `json:"image_group_id"`
// Array of links to product images
Images []GetDescriptionOfProductResultImage `json:"images"`
@@ -1430,6 +1461,9 @@ type GetDescriptionOfProductResult struct {
// Array of PDF files
PDFList []GetDescriptionOfProductResultPDF `json:"pdf_list"`
// Product type identifier
TypeId int64 `json:"type_id"`
// Weight of product in the package
Weight int32 `json:"weight"`
@@ -1524,6 +1558,92 @@ func (c Products) GetDescriptionOfProduct(ctx context.Context, params *GetDescri
return resp, nil
}
type GetDescriptionOfProductsFilter struct {
ProductId []string `json:"product_id,omitempty"`
OfferId []string `json:"offer_id,omitempty"`
Sku []string `json:"sku,omitempty"`
Visibility string `json:"visibility,omitempty"`
}
type GetDescriptionOfProductsParams struct {
Filter GetDescriptionOfProductsFilter `json:"filter"`
LastId string `json:"last_id,omitempty"`
Limit int64 `json:"limit,omitempty"`
SortBy string `json:"sort_by,omitempty"`
SortDirection string `json:"sort_dir,omitempty"`
}
type GetDescriptionOfProductsResponse struct {
core.CommonResponse
Result []GetDescriptionOfProductsResult `json:"result"`
Total int32 `json:"total"`
LastId string `json:"last_id"`
}
type GetDescriptionOfProductsResult struct {
Id int64 `json:"id"`
Barcode string `json:"barcode"`
Name string `json:"name"`
OfferId string `json:"offer_id"`
Height int32 `json:"height"`
Depth int32 `json:"depth"`
Width int32 `json:"width"`
DimensionUnit string `json:"dimension_unit"`
Weight int32 `json:"weight"`
WeightUnit string `json:"weight_unit"`
DescriptionCategoryId int64 `json:"description_category_id"`
TypeId int64 `json:"type_id"`
PrimaryImage string `json:"primary_image"`
// new "model_info" structure
ModelInfo *ModelInfo `json:"model_info,omitempty"`
Images []string `json:"images"`
PDFList []string `json:"pdf_list"`
Attributes []GetDescriptionOfProductsAttribute `json:"attributes"`
ComplexAttributes []GetDescriptionOfProductsComplexAttribute `json:"complex_attributes"`
ColorImage string `json:"color_image"`
}
type ModelInfo struct {
ModelId int64 `json:"model_id"`
Count int64 `json:"count"`
}
type GetDescriptionOfProductsAttribute struct {
Id int64 `json:"id"`
ComplexId int64 `json:"complex_id"`
Values []GetDescriptionOfProductsAttributeValue `json:"values"`
}
type GetDescriptionOfProductsAttributeValue struct {
DictionaryValueId int64 `json:"dictionary_value_id"`
Value string `json:"value"`
}
type GetDescriptionOfProductsComplexAttribute struct {
Id int64 `json:"id,omitempty"`
ComplexId int64 `json:"complex_id,omitempty"`
Values []GetDescriptionOfProductsAttributeValue `json:"values,omitempty"`
}
// /v4/product/info/attributes
func (c Products) GetDescriptionOfProducts(ctx context.Context, params *GetDescriptionOfProductsParams) (*GetDescriptionOfProductsResponse, error) {
url := "/v4/product/info/attributes"
resp := &GetDescriptionOfProductsResponse{}
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 GetProductDescriptionParams struct {
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
@@ -1756,67 +1876,6 @@ func (c Products) RemoveProductWithoutSKU(ctx context.Context, params *RemovePro
return resp, nil
}
type ListGeoRestrictionsParams struct {
// Filter. To get all geo-restrictions, leave names blank and specify true in the only_visible parameter
Filter ListGeoRestrictionsFilter `json:"filter"`
// Order number of geo-restriction from which to output data in the response.
//
// If you specify 23 in this parameter, the first item in the restrictions list will output order_number = 24.
// If you want to get all geo-restrictions, pass 0 in this parameter
LastOrderNumber int64 `json:"last_order_number"`
// Number of items in the response
Limit int64 `json:"limit"`
}
type ListGeoRestrictionsFilter struct {
// List with city names
Names []string `json:"names"`
// Value visibility. We recommend always passing true in this parameter
OnlyVisible bool `json:"only_visible"`
}
type ListGeoRestrictionsResponse struct {
core.CommonResponse
// Restrictions
Restrictions []ListGeoRestrictionsRestriction `json:"restrictions"`
}
type ListGeoRestrictionsRestriction struct {
// Geo-restriction identifier
Id string `json:"id"`
// Item visibility
IsVisible bool `json:"is_visible"`
// City name
Name string `json:"name"`
// Geo-restriction order number.
//
// If you specify 23 in the last_order_number parameter in the request,
// the first item in the restrictions list will have order_number = 24
OrderNumber int64 `json:"order_number"`
}
// Deprecated: Get a list of geo-restrictions for services
func (c Products) ListGeoRestrictions(ctx context.Context, params *ListGeoRestrictionsParams) (*ListGeoRestrictionsResponse, error) {
url := "/v1/products/geo-restrictions-catalog-by-filter"
resp := &ListGeoRestrictionsResponse{}
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 UploadActivationCodesParams struct {
// Digital activation codes
DigitalCodes []string `json:"digital_codes"`
@@ -2403,3 +2462,183 @@ func (c Products) GetRelatedSKUs(ctx context.Context, params *GetRelatedSKUsPara
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
}

View File

@@ -1611,7 +1611,6 @@ func TestGetDescriptionOfProduct(t *testing.T) {
"index": 2
}
],
"image_group_id": "",
"images360": [],
"pdf_list": [],
"attributes": [
@@ -1790,6 +1789,113 @@ func TestGetDescriptionOfProduct(t *testing.T) {
}
}
func TestGetDescriptionOfProductV4(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetDescriptionOfProductsParams
response string
}{
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetDescriptionOfProductsParams{
Filter: GetDescriptionOfProductsFilter{
ProductId: []string{"330186294"},
Visibility: "ALL",
},
Limit: 100,
SortDirection: "ASC",
},
`{
"result": [
{
"id": 330186294,
"barcode": "OZN653473453",
"name": "PC ЮКОМС Ryzen 7 5700G ...",
"offer_id": "ju-cas2-r5700g-bl",
"height": 360,
"depth": 420,
"width": 220,
"dimension_unit": "mm",
"weight": 4500,
"weight_unit": "g",
"description_category_id": 17028619,
"type_id": 91476,
"primary_image": "https://cdn1.ozone.ru/s3/multimedia-1-3/7084786431.jpg",
"model_info": {
"model_id": 379410772,
"count": 126
},
"images": [
"https://cdn1.ozone.ru/s3/multimedia-1-0/7084786428.jpg",
"https://cdn1.ozone.ru/s3/multimedia-1-k/7084786304.jpg"
],
"pdf_list": [],
"attributes": [
{
"id": 85,
"complex_id": 0,
"values": [
{
"dictionary_value_id": 971195426,
"value": "ЮКОМС"
}
]
}
],
"complex_attributes": [],
"color_image": ""
}
],
"total": 1,
"last_id": ""
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetDescriptionOfProductsParams{},
`{
"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, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()
resp, err := c.Products().GetDescriptionOfProducts(ctx, test.params)
if err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
compareJsonResponse(t, test.response, &GetDescriptionOfProductsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("wrong status code: got: %d, want: %d", resp.StatusCode, test.statusCode)
}
if test.statusCode == http.StatusOK {
if len(resp.Result) == 0 {
t.Error("expected non-empty result in success case")
}
}
}
}
func TestGetProductDescription(t *testing.T) {
t.Parallel()
@@ -2141,79 +2247,6 @@ func TestRemoveProductWithoutSKU(t *testing.T) {
}
}
func TestListGeoRestrictions(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListGeoRestrictionsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListGeoRestrictionsParams{
Filter: ListGeoRestrictionsFilter{
OnlyVisible: true,
},
LastOrderNumber: 0,
Limit: 3,
},
`{
"restrictions": [
{
"id": "world",
"name": "Весь Мир",
"is_visible": true,
"order_number": 1
},
{
"id": "42fb1c32-0cfe-5c96-9fb5-7f8e8449f28c",
"name": "Все города РФ",
"is_visible": true,
"order_number": 2
},
{
"id": "moscow",
"name": "Москва",
"is_visible": true,
"order_number": 3
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListGeoRestrictionsParams{},
`{
"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().ListGeoRestrictions(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListGeoRestrictionsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestUploadActivationCodes(t *testing.T) {
t.Parallel()
@@ -2760,3 +2793,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
View 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
View 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)
}
}
}

View File

@@ -18,6 +18,11 @@ type GetCurrentSellerRatingInfoResponse struct {
// Rating groups list
Groups []GetCurrentSellerRatingInfoGroup `json:"groups"`
// Localization index details.
// If you had no sales in the last 14 days,
// the parameter fields will be empty
LocalizationIndex []LocalizationIndex `json:"localization_index"`
// An indication that the penalty points balance is exceeded
PenaltyScoreExceeded bool `json:"penalty_score_exceeded"`
@@ -25,6 +30,14 @@ type GetCurrentSellerRatingInfoResponse struct {
Premium bool `json:"premium"`
}
type LocalizationIndex struct {
// Date of localization index calculation
CalculationDate time.Time `json:"calculation_date"`
// Localization index value
LocalizationPercentage int32 `json:"localization_percentage"`
}
type GetCurrentSellerRatingInfoGroup struct {
// Ratings group name
GroupName string `json:"group_name"`

View File

@@ -41,6 +41,12 @@ func TestGetCurrentRatingInfo(t *testing.T) {
]
}
],
"localization_index": [
{
"calculation_date": "2019-08-24T14:15:22Z",
"localization_percentage": 0
}
],
"penalty_score_exceeded": true,
"premium": true
}`,

View File

@@ -463,8 +463,15 @@ type GetReturnsReportsFilter struct {
// Order delivery scheme: fbs — delivery from seller's warehouse
DeliverySchema string `json:"delivery_schema"`
// Order identifier
OrderId int64 `json:"order_id"`
// Date from which the data is displayed in the report.
//
// Available for the last three months only
DateFrom time.Time `json:"date_from"`
// Date up to which the data is displayed in the report.
//
// Available for the last three months only
DateTo time.Time `json:"date_to"`
// Order status
Status string `json:"status"`
@@ -473,20 +480,13 @@ type GetReturnsReportsFilter struct {
type GetReturnsReportResponse struct {
core.CommonResponse
// Method result
Result GetReturnReportResult `json:"result"`
}
type GetReturnReportResult struct {
// Unique report identifier. The report is available for downloading within 3 days after making a request.
Code string `json:"code"`
}
// The report contains information about returned products that were accepted from the customer, ready for pickup, or delivered to the seller.
//
// The method is only suitable for orders shipped from the seller's warehouse
// Method for getting a report on FBO and FBS returns
func (c Reports) GetReturns(ctx context.Context, params *GetReturnsReportParams) (*GetReturnsReportResponse, error) {
url := "/v1/report/returns/create"
url := "/v2/report/returns/create"
resp := &GetReturnsReportResponse{}

View File

@@ -380,9 +380,7 @@ func TestGetReturnsReport(t *testing.T) {
},
},
`{
"result": {
"code": "d55f4517-8347-4e24-9d93-d6e736c1c07c"
}
"code": "REPORT_seller_products_924336_1720170405_a9ea2f27-a473-4b13-99f9-d0cfcb5b1a69"
}`,
},
// Test No Client-Id or Api-Key
@@ -407,16 +405,12 @@ func TestGetReturnsReport(t *testing.T) {
continue
}
compareJsonResponse(t, test.response, &GetReturnsReportResponse{})
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.Result.Code == "" {
t.Errorf("Code cannot be empty")
}
compareJsonResponse(t, test.response, &GetReturnsReportResponse{})
}
}
}

View File

@@ -12,125 +12,6 @@ type Returns struct {
client *core.Client
}
type GetFBOReturnsParams struct {
// Filter
Filter *GetFBOReturnsFilter `json:"filter,omitempty"`
// Identifier of the last value on the page. Leave this field blank in the first request.
//
// To get the next values, specify the recieved value in the next request in the `last_id` parameter
LastId int64 `json:"last_id"`
// Number of values in the response
Limit int64 `json:"limit"`
}
type GetFBOReturnsFilter struct {
// Shipment number
PostingNumber string `json:"posting_number"`
// Return status
Status []GetFBOReturnsFilterStatus `json:"status"`
}
type GetFBOReturnsResponse struct {
core.CommonResponse
// Identifier of the last value on the page
LastId int64 `json:"last_id"`
// Returns information
Returns []GetFBOReturnsReturn `json:"returns"`
}
type GetFBOReturnsReturn struct {
// Time when a return was received from the customer
AcceptedFromCustomerMoment time.Time `json:"accepted_from_customer_moment"`
// Seller identifier
CompanyId int64 `json:"company_id"`
// Current return location
CurrentPlaceName string `json:"current_place_name"`
// Return destination
DestinationPlaceName string `json:"dst_place_name"`
// Return shipment identifier
Id int64 `json:"id"`
// Indication that the package has been opened. true, if it has been
IsOpened bool `json:"is_opened"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Unique return record identifier
ReturnId int64 `json:"return_id"`
// Return reason
ReturnReasonName string `json:"return_reason_name"`
// Return delivery time to the Ozon warehouse
ReturnedToOzonMoment time.Time `json:"returned_to_ozon_moment"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Return status
Status GetFBOReturnsReturnStatus `json:"status_name"`
}
// Method for getting information on returned products that are sold from the Ozon warehouse
func (c Returns) GetFBOReturns(ctx context.Context, params *GetFBOReturnsParams) (*GetFBOReturnsResponse, error) {
url := "/v3/returns/company/fbo"
resp := &GetFBOReturnsResponse{}
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 GetFBSReturnsParams struct {
// Filter
Filter *GetFBSReturnsFilter `json:"filter,omitempty"`
// Number of values in the response:
// - maximum — 1000,
// - minimum — 1
Limit int64 `json:"limit"`
// Return identifier that was loaded the last time.
// Return identifiers with the higher value than `last_id`
// will be returned in the response.
LastId int64 `json:"offset"`
}
type GetFBSReturnsFilter struct {
// Last day of free storage
LastFreeWaitingDay GetFBSReturnsFilterTimeRange `json:"last_free_waiting_dat"`
// Order ID
OrderId int64 `json:"order_id"`
// Shipment ID
PostingNumber []string `json:"posting_number"`
// Product name
ProductName string `json:"product_name"`
// Product ID
ProductOfferId string `json:"product_offer_id"`
// Return status
Status GetFBSReturnsFilterStatus `json:"status"`
}
type GetFBSReturnsFilterTimeRange struct {
// The beginning of the period.
//
@@ -147,118 +28,6 @@ type GetFBSReturnsFilterTimeRange struct {
TimeTo time.Time `json:"time_to"`
}
type GetFBSReturnsResponse struct {
core.CommonResponse
// Return identifier that was loaded the last time.
// Return identifiers with the higher value than `last_id`
// will be returned in the response
LastId int64 `json:"last_id"`
// Returns information
Returns []GetFBSReturnResultReturn `json:"returns"`
}
type GetFBSReturnResultReturn struct {
// Bottom barcode on the product label
ClearingId int64 `json:"clearing_id"`
// Commission fee
Commission float64 `json:"commission"`
// Commission percentage
CommissionPercent float64 `json:"commission_percent"`
// Product item identifier in the Ozon logistics system
ExemplarId int64 `json:"exemplar_id"`
// Return identifier in the Ozon accounting system
Id int64 `json:"id"`
// If the product is in transit — true
IsMoving bool `json:"is_moving"`
// Indication that the package has been opened. true, if it has been
IsOpened bool `json:"is_opened"`
// Last day of free storage
LastFreeWaitingDay string `json:"last_free_waiting_day"`
// ID of the warehouse the product is being transported to
PlaceId int64 `json:"place_id"`
// Intermediate return point
MovingToPlaceName string `json:"moving_to_place_name"`
// Delivery cost
PickingAmount float64 `json:"picking_amount"`
// Shipment number
PostingNumber string `json:"posting_number"`
PickingTag string `json:"picking_tag"`
// Current product price without a discount
Price float64 `json:"price"`
// Product price without commission
PriceWithoutCommission float64 `json:"price_without_commission"`
// Product identifier — SKU
ProductId int64 `json:"product_id"`
// Product name
ProductName string `json:"product_name"`
// Product quantity
Quantity int64 `json:"quantity"`
// Barcode on the return label. Use this parameter value to work with the return label
ReturnBarcode string `json:"return_barcode"`
// Package unit identifier in the Ozon logistics system
ReturnClearingId int64 `json:"return_clearing_id"`
// Product return date
ReturnDate string `json:"return_date"`
// Return reason
ReturnReasonName string `json:"return_reason_name"`
// Date when the product is ready to be handed over to the seller
WaitingForSellerDate string `json:"waiting_for_seller_date_time"`
// Date of handing over the product to the seller
ReturnedToSellerDate string `json:"returned_to_seller_date_time"`
// Return storage period in days
WaitingForSellerDays int64 `json:"waiting_for_seller_days"`
// Return storage cost
ReturnsKeepingCost float64 `json:"returns_keeping_cost"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Return status
Status string `json:"status"`
}
// Method for getting information on returned products that are sold from the seller's warehouse
func (c Returns) GetFBSReturns(ctx context.Context, params *GetFBSReturnsParams) (*GetFBSReturnsResponse, error) {
url := "/v3/returns/company/fbs"
resp := &GetFBSReturnsResponse{}
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 GetRFBSReturnsParams struct {
// Filter
Filter *GetRFBSReturnsFilter `json:"filter,omitempty"`
@@ -943,7 +712,250 @@ func (c Returns) FBSQuantity(ctx context.Context, params *GetFBSQuantityReturnsP
resp := &GetFBSQuantityReturnsResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, nil)
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 ListReturnsParams struct {
// Filter
Filter *ListReturnsFilter `json:"filter,omitempty"`
// Number of loaded returns. The maximum value is 500
Limit int32 `json:"limit"`
// Identifier of the last loaded return
LastId int64 `json:"last_id"`
}
type ListReturnsFilter struct {
// Filter by return creation date
LogisticReturnDate *GetFBSReturnsFilterTimeRange `json:"logistic_return_date"`
// Filter by storage fees start date
StorageTarifficationDate *GetFBSReturnsFilterTimeRange `json:"storage_tariffication_start_date"`
// Filter by date the return status changed
VisualStatusChangeMoment *GetFBSReturnsFilterTimeRange `json:"visual_status_change_moment"`
// Filter by order identifier
OrderId int64 `json:"order_id,omitempty"`
// Filter by shipment number
PostingNumbers []string `json:"posting_numbers,omitempty"`
// Filter by product name
ProductName string `json:"product_name,omitempty"`
// Filter by product identifier in the seller's system
OfferId string `json:"offer_id,omitempty"`
// Filter by return status
VisualStatusName VisualStatus `json:"visual_status_name,omitempty"`
// Filter by warehouse identifier
WarehouseId int64 `json:"warehouse_id,omitempty"`
// Filter by return label barcode
Barcode string `json:"barcode,omitempty"`
// Filter by delivery scheme: FBS or FBO
ReturnSchema string `json:"return_schema,omitempty"`
}
type ListReturnsResponse struct {
core.CommonResponse
// Returns details
Returns []Return `json:"returns"`
// true, if the seller has other returns
HasNext bool `json:"has_next"`
}
type Return struct {
// Product items data
Exemplars []ReturnExemplar `json:"exemplars"`
// Return identifier
Id int64 `json:"id"`
// Company identifier
CompanyId int64 `json:"company_id"`
// Return reason
ReturnReasonName string `json:"return_reason_name"`
// Return type
Type string `json:"type"`
// Return scheme
Schema string `json:"schema"`
// Order identifier
OrderId int64 `json:"order_id"`
// Order number
OrderNumber string `json:"order_number"`
// Warehouse where the return is stored
Place ReturnPlace `json:"place"`
// Warehouse where returns are sent to
TargetPlace ReturnPlace `json:"target_place"`
// Storage details
Storage ReturnStorage `json:"storage"`
// Product details
Product ReturnProduct `json:"product"`
// Return details
Logistic ReturnLogistic `json:"logistic"`
// Return status details
Visual ReturnVisual `json:"visual"`
// Additional information
AdditionalInfo ReturnAdditionalInfo `json:"additional_info"`
// Previous return identifier
SourceId int64 `json:"source_id"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Original shipment barcode
ClearingId int64 `json:"clearing_id"`
// Package unit identifier in the Ozon logistics system
ReturnClearingId int64 `json:"return_clearing_id"`
}
type ReturnExemplar struct {
// Product identifier
Id int64 `json:"id"`
}
type ReturnPlace struct {
// Warehouse identifier
Id int64 `json:"id"`
// Warehouse name
Name string `json:"name"`
// Warehouse address
Address string `json:"address"`
}
type ReturnStorage struct {
// Storage cost details
Sum ReturnSum `json:"sum"`
// First day of charging for storage
TarifficationsFirstDate time.Time `json:"tariffication_first_date"`
// Start date for storage fees
TarifficationsStartDate time.Time `json:"tariffication_start_date"`
// Date when the return was ready for handover
ArrivedMoment time.Time `json:"arrived_moment"`
// Number of days the return has been waiting for handover
Days int64 `json:"days"`
// Disposal cost details
UtilizationSum ReturnSum `json:"utilization_sum"`
// Planned disposal date
UtilizationForecastDate string `json:"utilization_forecast_date"`
}
type ReturnSum struct {
// Currency
CurrencyCode string `json:"currency_code"`
// Disposal cost
Price float64 `json:"price"`
}
type ReturnProduct struct {
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// product name
Name string `json:"name"`
// Product price details
Price ReturnSum `json:"price"`
// Product cost without commission
PriceWithoutCommission ReturnSum `json:"price_without_commission"`
// Sales commission by category
CommissionPercent float64 `json:"commission_percent"`
// Commission details
Commission ReturnSum `json:"commission"`
}
type ReturnLogistic struct {
// Date when the order was placed for technical return
TechnicalReturnMoment time.Time `json:"technical_return_moment"`
// Date when the return arrived to the warehouse or was handed over to the seller
FinalMoment time.Time `json:"final_moment"`
// Date when the seller received compensation for the return
CancelledWithCompensationMoment time.Time `json:"cancelled_with_compensation_moment"`
// Date when the customer returned the product
ReturnDate time.Time `json:"return_date"`
// Return label barcode
Barcode string `json:"barcode"`
}
type ReturnVisual struct {
// Return status
Status ReturnVisualStatus `json:"status"`
// Date the return status changed
ChangeMoment time.Time `json:"change_moment"`
}
type ReturnVisualStatus struct {
// Return status identifier
Id int32 `json:"id"`
// Return status name
DisplayName string `json:"display_name"`
// System name of the return status
SystemName string `json:"sys_name"`
}
type ReturnAdditionalInfo struct {
// true, if the return package is opened
IsOpened bool `json:"is_opened"`
// true, if the return belongs to Super Economy products
IsSuperEconom bool `json:"is_super_econom"`
}
func (c Returns) List(ctx context.Context, params *ListReturnsParams) (*ListReturnsResponse, error) {
url := "/v1/returns/list"
resp := &ListReturnsResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}

View File

@@ -8,193 +8,6 @@ import (
core "github.com/diphantxm/ozon-api-client"
)
func TestGetFBOReturns(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetFBOReturnsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetFBOReturnsParams{
Filter: &GetFBOReturnsFilter{
PostingNumber: "some number",
},
LastId: 123,
Limit: 100,
},
`{
"last_id": 0,
"returns": [
{
"accepted_from_customer_moment": "2019-08-24T14:15:22Z",
"company_id": 123456789,
"current_place_name": "my-place",
"dst_place_name": "that-place",
"id": 123456789,
"is_opened": true,
"posting_number": "some number",
"return_reason_name": "ripped",
"returned_to_ozon_moment": "2019-08-24T14:15:22Z",
"sku": 123456789,
"status_name": "delivering"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetFBOReturnsParams{},
`{
"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.Returns().GetFBOReturns(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetFBOReturnsResponse{})
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 len(resp.Returns) > 0 {
if resp.Returns[0].Id == 0 {
t.Errorf("Id cannot be 0")
}
if resp.Returns[0].CompanyId == 0 {
t.Errorf("Company id cannot be 0")
}
if resp.Returns[0].SKU == 0 {
t.Errorf("SKU cannot be 0")
}
}
}
}
}
func TestGetFBSReturns(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetFBSReturnsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetFBSReturnsParams{
Filter: &GetFBSReturnsFilter{
PostingNumber: []string{"07402477-0022-2"},
Status: "returned_to_seller",
},
Limit: 1000,
LastId: 0,
},
`{
"last_id": 0,
"returns": [
{
"clearing_id": 23,
"commission": 21,
"commission_percent": 0,
"exemplar_id": 42,
"id": 123,
"is_moving": true,
"is_opened": true,
"last_free_waiting_day": "string",
"place_id": 122,
"moving_to_place_name": "string",
"picking_amount": 0,
"posting_number": "string",
"picking_tag": "string",
"price": 0,
"price_without_commission": 0,
"product_id": 2222,
"product_name": "string",
"quantity": 0,
"return_barcode": "string",
"return_clearing_id": 0,
"return_date": "string",
"return_reason_name": "string",
"waiting_for_seller_date_time": "string",
"returned_to_seller_date_time": "string",
"waiting_for_seller_days": 0,
"returns_keeping_cost": 0,
"sku": 33332,
"status": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetFBSReturnsParams{},
`{
"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.Returns().GetFBSReturns(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetFBSReturnsResponse{})
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 len(resp.Returns) > 0 {
if resp.Returns[0].Id == 0 {
t.Errorf("Id cannot be 0")
}
if resp.Returns[0].ProductId == 0 {
t.Errorf("Product id cannot be 0")
}
if resp.Returns[0].SKU == 0 {
t.Errorf("SKU cannot be 0")
}
if resp.Returns[0].Status == "" {
t.Errorf("Status cannot be empty")
}
}
}
}
}
func TestGetRFBSReturns(t *testing.T) {
t.Parallel()
@@ -1059,3 +872,153 @@ func TestFBSQuantity(t *testing.T) {
}
}
}
func TestListReturns(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListReturnsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListReturnsParams{
Filter: &ListReturnsFilter{
LogisticReturnDate: &GetFBSReturnsFilterTimeRange{
TimeFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
TimeTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
},
StorageTarifficationDate: &GetFBSReturnsFilterTimeRange{
TimeFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
TimeTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
},
VisualStatusChangeMoment: &GetFBSReturnsFilterTimeRange{
TimeFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
TimeTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
},
WarehouseId: 911,
ReturnSchema: "FBO",
ProductName: "string",
},
Limit: 500,
LastId: 0,
},
`{
"returns": [
{
"exemplars": [
{
"id": 1019562967545956
}
],
"id": 1000015552,
"company_id": 3058,
"return_reason_name": "Customer refused on receipt: not satisfied with the quality of the product",
"type": "FullReturn",
"schema": "Fbs",
"order_id": 24540784250,
"order_number": "58544282-0057",
"place": {
"id": 23869688194000,
"name": "СЦ_Львовский_Возвраты",
"address": "Россия, обл. Московская, г. Подольск, промышленная зона Львовский, ул. Московская, д. 69, стр. 5"
},
"target_place": {
"id": 23869688194000,
"name": "СЦ_Львовский_Возвраты",
"address": "Россия, обл. Московская, г. Подольск, промышленная зона Львовский, ул. Московская, д. 69, стр. 5"
},
"storage": {
"sum": {
"currency_code": "RUB",
"price": 1231
},
"tariffication_first_date": "2024-07-30T06:15:48.998146Z",
"tariffication_start_date": "2024-07-29T06:15:48.998146Z",
"arrived_moment": "2024-07-29T06:15:48.998146Z",
"days": 0,
"utilization_sum": {
"currency_code": "RUB",
"price": 1231
},
"utilization_forecast_date": "2024-07-29T06:15:48.998146Z"
},
"product": {
"sku": 1100526203,
"offer_id": "81451",
"name": "Кукла Дотти Плачущий младенец Cry Babies Dressy Dotty",
"price": {
"currency_code": "RUB",
"price": 3318
},
"price_without_commission": {
"currency_code": "RUB",
"price": 3318
},
"commission_percent": 1.2,
"commission": {
"currency_code": "RUB",
"price": 2312
}
},
"logistic": {
"technical_return_moment": "2024-07-29T06:15:48.998146Z",
"final_moment": "2024-07-29T06:15:48.998146Z",
"cancelled_with_compensation_moment": "2024-07-29T06:15:48.998146Z",
"return_date": "2024-07-29T06:15:48.998146Z",
"barcode": "ii5275210303"
},
"visual": {
"status": {
"id": 3,
"display_name": "At the pick-up point",
"sys_name": "ArrivedAtReturnPlace"
},
"change_moment": "2024-07-29T06:15:48.998146Z"
},
"additional_info": {
"is_opened": true,
"is_super_econom": false
},
"source_id": 90426223,
"posting_number": "58544282-0057-1",
"clearing_id": 21190893156000,
"return_clearing_id": null
}
],
"has_next": false
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListReturnsParams{},
`{
"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.Returns().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListReturnsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

@@ -48,6 +48,9 @@ type GetListOfWarehousesResult struct {
// Indication that the warehouse accepts bulky products
IsKGT bool `json:"is_kgt"`
// true if the warehouse handles economy products
IsEconomy bool `json:"is_economy"`
// Indication that warehouse schedule can be changed
IsTimetableEditable bool `json:"is_timetable_editable"`
@@ -189,3 +192,58 @@ func (c Warehouses) GetListOfDeliveryMethods(ctx context.Context, params *GetLis
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
}

View File

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