15 Commits

Author SHA1 Message Date
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
Kirill
7c0e18681b Updates August 26, 2024 and August 28, 2024 (#103) 2024-09-10 00:57:30 +03:00
Pireirik
895ef8be52 Change the data structure of chat response according to the updates from Ozon (#102) 2024-09-09 22:09:31 +03:00
Pireirik
ccd3610c76 Change variable type for list of chats (#101) 2024-09-08 19:39:20 +03:00
s1berc0de
090b2afb63 added /v1/description-category/attribute/values/search support (#100) 2024-08-29 17:53:31 +03:00
diPhantxm
823386edf2 remove states 2024-08-09 19:57:56 +03:00
Kirill
5ecf131061 Update August 2, 2024 (#99) 2024-08-09 19:55:36 +03:00
16 changed files with 1125 additions and 183 deletions

View File

@@ -4,6 +4,7 @@
- [x] Product category tree
- [x] Category characteristics list
- [x] Characteristics value directory
- [x] Search characteristics value directory
## Uploading and updating products
- [x] Create or update a product

View File

@@ -200,6 +200,9 @@ type GetStocksOnWarehousesResultRow struct {
// Name of the warehouse where the products are stored
WarehouseName string `json:"warehouse_name"`
// Number of days the stock will last based on your average daily sales
IDC float64 `json:"idc"`
}
// Report on stocks and products movement at Ozon warehouses
@@ -216,3 +219,54 @@ 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
}

View File

@@ -145,3 +145,67 @@ 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")
}
}
}
}

View File

@@ -224,3 +224,63 @@ func (c *Categories) AttributesDictionary(ctx context.Context, params *GetAttrib
return resp, nil
}
type SearchAttributeDictionaryParams struct {
// Characteristics identifier
AttributeId int64 `json:"attribute_id"`
// Category identifier
DescriptionCategoryId int64 `json:"description_category_id"`
// The value to be searched for
// - minimum—2 characters
Value string `json:"value"`
// Number of values in the response:
//
// - maximum—100,
// - minimum—1.
Limit int64 `json:"limit,omitempty"`
// Product type identifier
TypeId int64 `json:"type_id"`
}
type SearchAttributeDictionaryResponse struct {
core.CommonResponse
// Characteristic values
Result []SearchAttributeDictionaryResult `json:"result"`
}
type SearchAttributeDictionaryResult struct {
// Characteristic value identifier
Id int64 `json:"id"`
// Additional description
Info string `json:"info"`
// Image link
Picture string `json:"picture"`
// Product characteristic value
Value string `json:"value"`
}
// Returns found characteristics value directory.
//
// To check if an attribute has a nested directory,
// use the `/v1/description-category/attribute` method.
func (c *Categories) SearchAttributesDictionary(ctx context.Context, params *SearchAttributeDictionaryParams) (*SearchAttributeDictionaryResponse, error) {
url := "/v1/description-category/attribute/values/search"
resp := &SearchAttributeDictionaryResponse{}
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

@@ -203,3 +203,71 @@ func TestGetAttributeDictionary(t *testing.T) {
}
}
}
func TestSearchAttributeDictionary(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SearchAttributeDictionaryParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SearchAttributeDictionaryParams{
AttributeId: 123456,
DescriptionCategoryId: 12,
Value: "34",
Limit: 5,
TypeId: 6,
},
`{
"has_next": true,
"result": [
{
"id": 0,
"info": "string",
"picture": "string",
"value": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SearchAttributeDictionaryParams{},
`{
"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.Categories().SearchAttributesDictionary(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetAttributeDictionaryResponse{})
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.Result) > int(test.params.Limit) {
t.Errorf("Length of response result is bigger than limit")
}
}
}
}

View File

@@ -39,7 +39,7 @@ type ListChatsResponse struct {
core.CommonResponse
// Chats data
Chats []ListChatsChat `json:"chats"`
Chats []ListChatsChatData `json:"chats"`
// Total number of chats
TotalChatsCount int64 `json:"total_chats_count"`
@@ -48,20 +48,6 @@ type ListChatsResponse struct {
TotalUnreadCount int64 `json:"total_unread_count"`
}
type ListChatsChat struct {
// Chat data
Chat ListChatsChatData `json:"chat"`
// Identifier of the first unread chat message
FirstUnreadMessageId string `json:"first_unread_message_id"`
// Identifier of the last message in the chat
LastMessageId string `json:"last_message_id"`
// Number of unread messages in the chat
UnreadCount int64 `json:"unread_count"`
}
type ListChatsChatData struct {
// Chat identifier
ChatId string `json:"chat_id"`
@@ -79,6 +65,15 @@ type ListChatsChatData struct {
// Chat creation date
CreatedAt time.Time `json:"created_at"`
// Identifier of the first unread chat message
FirstUnreadMessageId uint64 `json:"first_unread_message_id"`
// Identifier of the last message in the chat
LastMessageId uint64 `json:"last_message_id"`
// Number of unread messages in the chat
UnreadCount int64 `json:"unread_count"`
}
// Returns information about chats by specified filters

View File

@@ -32,15 +32,13 @@ func TestListChats(t *testing.T) {
`{
"chats": [
{
"chat": {
"created_at": "2022-07-22T08:07:19.581Z",
"chat_id": "5e767w03-b400-4y1b-a841-75319ca8a5c8",
"chat_status": "Opened",
"chat_type": "Seller_Support"
},
"first_unread_message_id": "3000000000118021931",
"last_message_id": "30000000001280042740",
"unread_count": 1
"chat_id": "5e767w03-b400-4y1b-a841-75319ca8a5c8",
"chat_status": "Opened",
"chat_type": "Seller_Support",
"created_at": "2022-07-22T08:07:19.581Z",
"unread_count": 1,
"last_message_id": 3000000000128004274,
"first_unread_message_id": 3000000000118021931
}
],
"total_chats_count": 25,
@@ -77,10 +75,10 @@ func TestListChats(t *testing.T) {
if resp.StatusCode == http.StatusOK {
if len(resp.Chats) > 0 {
if resp.Chats[0].Chat.ChatStatus == "" {
if resp.Chats[0].ChatStatus == "" {
t.Errorf("Chat status cannot be empty")
}
if resp.Chats[0].Chat.ChatType == "" {
if resp.Chats[0].ChatType == "" {
t.Errorf("Chat type cannot be empty")
}
}

View File

@@ -126,24 +126,6 @@ const (
type SupplyRequestState string
const (
// request draft. Only for supplies via vDC
Draft SupplyRequestState = "DRAFT"
// selecting supply options. Only for supplies via vDC
SupplyVariantsArranging SupplyRequestState = "SUPPLY_VARIANTS_ARRANGING"
// no supply options, the request is archived. Only for supplies via vDC
HasNoSupplyVariantsArchive SupplyRequestState = "HAS_NO_SUPPLY_VARIANTS_ARCHIVE"
// no supply options. Only for supplies via vDC
HasNoSupplyVariantsNew SupplyRequestState = "HAS_NO_SUPPLY_VARIANTS_NEW"
// supply being approved. Only for supplies via vDC
SupplyVariantsConfirmation SupplyRequestState = "SUPPLY_VARIANTS_CONFIRMATION"
// time reservation
TimeslotBooking SupplyRequestState = "TIMESLOT_BOOKING"
// filling in the data
DATA_FILLING SupplyRequestState = "DATA_FILLING"
@@ -471,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
@@ -740,6 +724,8 @@ const (
// picking up products for removal by the seller
TransactionServiceReturnFromStock TransactionOperationService = "MarketplaceServiceItemReturnFromStock"
TransactionServiceStarsMembership TransactionOperationService = "ItemAgentServiceStarsMembership"
// forwarding trade
TransactionItemAdForSupplierLogisticSeller TransactionOperationService = "ItemAdvertisementForSupplierLogisticSeller"
@@ -808,6 +794,9 @@ const (
// reissue of returns at the pick-up point
TransactionServiceRedistributionReturnsPVZ TransactionOperationService = "MarketplaceServiceItemRedistributionReturnsPVZ"
// Agregator 3PL Globalagency service tariffication
TransactionServiceAgencyFeeAggregator3PLGlobal TransactionOperationService = "OperationMarketplaceAgencyFeeAggregator3PLGlobal"
)
type PaymentTypeGroupName string
@@ -822,3 +811,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

@@ -106,6 +106,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"`
@@ -912,6 +915,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"`
@@ -3016,3 +3022,119 @@ 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
}

View File

@@ -3010,3 +3010,202 @@ 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)
}
}
}

View File

@@ -747,6 +747,9 @@ type CreateOrUpdateProductItem struct {
// Category identifier
DescriptionCategoryId int64 `json:"description_category_id"`
// New category identifier. Specify it if you want to change the current product category
NewDescriptinoCategoryId int64 `json:"new_description_category_id"`
// Marketing color.
//
// Pass the link to the image in the public cloud storage. The image format is JPG
@@ -819,6 +822,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%,
@@ -1409,9 +1418,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"`
@@ -1427,6 +1433,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"`
@@ -1753,67 +1762,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"`
}
// 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"`

View File

@@ -1611,7 +1611,6 @@ func TestGetDescriptionOfProduct(t *testing.T) {
"index": 2
}
],
"image_group_id": "",
"images360": [],
"pdf_list": [],
"attributes": [
@@ -2141,79 +2140,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()

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

@@ -943,7 +943,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

@@ -1059,3 +1059,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)
}
}
}