63 Commits

Author SHA1 Message Date
Kirill
1c0c203097 Update February 14, 2025 (#144) 2025-03-02 01:34:57 +03:00
Kirill
b08f17f3f1 Update February 6, 2025 (#143) 2025-03-02 01:25:59 +03:00
Kirill
4831ad70d6 Update January 30, 2025 (#142) 2025-03-02 01:25:37 +03:00
Kirill
739f672caf Update January 17, 2025 (#141) 2025-03-02 01:18:53 +03:00
Kirill
0fa0986178 Update January 15, 2025 (#140) 2025-03-02 01:07:49 +03:00
Kirill
76e54922fa Update January 13, 2025 (#139) 2025-03-02 00:59:57 +03:00
Kirill
24cc2cbe93 Update December 28, 2024 (#138) 2025-03-02 00:58:09 +03:00
benice2me11
38e8446187 removed endpoint /v2/product/list update to /v3/product/list (#136)
Co-authored-by: ypoqou <benice2me11+ypoqou@gmail.com>
2025-02-14 20:43:09 +03:00
Kirill
77c3cf5462 Carriages: Update December 27, 2024 (#135) 2025-02-09 02:52:04 +03:00
Kirill
bc228dd6e1 Reviews: Update December 27, 2024 (#134) 2025-02-09 00:47:39 +03:00
Kirill
3a67391d71 Remove Endpoints file (#133) 2025-01-23 01:24:43 +03:00
Kirill
040bc23ebc Update December 26, 2024 (#132) 2025-01-23 01:22:28 +03:00
Kirill
804a4f3c2b Update December 24, 2024 (#131) 2025-01-23 01:07:04 +03:00
Kirill
c38e9f19a9 Update December 19, 2024 (#130) 2025-01-23 01:03:20 +03:00
Kirill
7654f5b7c5 Update December 17, 2024 (#129) 2025-01-10 21:21:03 +03:00
Kirill
7f705a4eb5 Update December 11, 2024 (#128) 2025-01-10 20:51:55 +03:00
Kirill
8173450413 Update December 9, 2024 (#127) 2025-01-10 18:07:03 +03:00
Kirill
32bd7748ec Update December 6, 2024 (#126) 2025-01-10 17:51:16 +03:00
Kirill
14986eb627 Update December 4, 2024 (#125) 2025-01-10 17:45:54 +03:00
diPhantxm
3c17a365a3 update 2024-12-27 23:12:33 +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
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
Kirill
dfbb93f438 Update June 27, 2024 (#98) 2024-07-29 04:26:59 +03:00
Zloy_Leshiy
b0c133ba40 replace method from GET to POST for /v1/actions/products/activate request (#97)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-07-22 14:31:28 +03:00
Kirill
680a155294 Update June 18, 2024 (#96) 2024-07-05 01:52:13 +03:00
Kirill
26e2c8b9a7 Update June 10, 2024 (#95) 2024-06-16 02:14:27 +03:00
Zloy_Leshiy
b26dd5bbee Archived fields for product details and fix filter for GetStockInfo (#94)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-06-03 13:40:55 +03:00
Kirill
9ea138003c Update May 24, 2024 (#93) 2024-05-24 15:59:51 +03:00
Kirill
5d62c8ec14 Update May 23, 2024 (#92) 2024-05-24 15:50:40 +03:00
Kirill
2e284d9667 Time format fixes and optional fields (#91) 2024-05-24 15:42:48 +03:00
Kirill
7ffcf3f235 Update 16, 2024 (#90) 2024-05-18 12:07:52 +03:00
Kirill
ad2eb19325 Update May 2, 2024 (#89) 2024-05-06 19:57:44 +03:00
Kirill
99b0a24d48 Tests for Time Format (#87) 2024-04-30 14:15:03 +03:00
Zloy_Leshiy
e5867813ab Custom data type for time representation (#86)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-04-30 13:03:08 +03:00
Kirill
b0e1210f34 Fix type in GetStocksInfoParams.GetStocksInfoFilter #84 (#85) 2024-04-27 13:22:18 +05:00
Kirill
049b02835b Fix response types in GetListOfWarehouses (#83) 2024-04-21 18:41:18 +03:00
Kirill
6cf4ae89e3 Fix empty body (#81) 2024-04-20 18:55:49 +03:00
Kirill
8a6ca3b2eb Update April 16, 2024 (#78) 2024-04-18 16:39:33 +03:00
Kirill
25051ee802 Updates April 8, 2024 and April 9, 2024 (#77) 2024-04-10 17:53:31 +03:00
42 changed files with 7297 additions and 2044 deletions

View File

@@ -1,177 +0,0 @@
# Supported Endpoints
## Ozon attributes and characteristics
- [x] Product category tree
- [x] Category characteristics list
- [x] Characteristics value directory
## Uploading and updating products
- [x] Create or update a product
- [x] Get the product import status
- [x] Create a product by Ozon ID
- [x] Upload and update product images
- [x] Check products images uploading status
- [x] List of products
- [x] Product details
- [x] Get products' content rating by SKU
- [x] Get a list of products by identifiers
- [x] Get a description of the product characteristics
- [x] Get product description
- [x] Product range limit, limits on product creation and update
- [x] Change product identifiers from the seller's system
- [x] Archive a product
- [x] Unarchive a product
- [x] Remove a product without an SKU from the archive
- [x] Get a list of geo-restrictions for services
- [x] Upload activation codes for services and digital products
- [x] Status of uploading activation codes
## Prices and Stocks
- [x] Update stocks
- [x] Update the quantity of products in stock
- [x] Information about product quantity
- [x] Stocks in seller's warehouses (FBS и rFBS)
- [x] Update prices
- [x] Get product price information
- [x] Get information about the markdown and the main product by the markdown product SKU
- [x] Set a discount on a markdown product
## Promotions
- [x] Available promotions
- [x] Products that can participate in a promotion
- [x] Products in a promotion
- [x] Add products to promotion
- [x] Remove products from promotion
- [x] List of available Hot Sale promotions
- [x] List of products participating in the Hot Sale promotion
- [x] Add products to the Hot Sale promotion
- [x] Remove product from the Hot Sale promotion
- [x] List of discount requests
- [x] Approve a discount request
- [x] Decline a discount request
## Brand certificates
- [x] List of certified brands
## Quality certificates
- [x] List of accordance types (version 2)
- [x] Directory of document types
- [x] List of certified categories
- [x] Adding certificates for products
- [x] Link the certificate to the product
- [x] Delete certificate
- [x] Certificate information
- [x] Certificates list
- [x] Product statuses list
- [x] List of products associated with the certificate
- [x] Unbind products from a certificate
- [x] Possible certificate rejection reasons
- [x] Possible certificate statuses
## Warehouses
- [x] List of warehouses
- [x] List of delivery methods for a warehouse
## Polygons
- [x] Create delivery polygon
- [x] Link delivery method to a delivery polygon
- [x] Delete polygon
## FBO
- [x] Shipments list
- [x] Shipment details
## FBS and rFBS products labeling
- [x] Validate labeling codes
- [x] Check and save product items data
- [x] Get product items check statuses
- [x] Pack the order (version 4)
## FBS and rFBS
- [x] List of unprocessed shipments (version 3)
- [x] Shipments list (version 3)
- [x] Get shipment details by identifier (version 3)
- [x] Get shipment data by barcode
- [x] List of manufacturing countries
- [x] Set the manufacturing country
- [x] Specify number of boxes for multi-box shipments
- [x] Get drop-off point restrictions
- [x] Partial pack the order
- [x] Create an acceptance and transfer certificate and a waybill
- [x] Status of acceptance and transfer certificate and waybill
- [x] Available freights list
- [x] Get acceptance and transfer certificate and waybill
- [x] Generating status of digital acceptance and transfer certificate and waybill
- [x] Get digital shipment certificate
- [x] Print the labeling
- [x] Create a task to generate labeling
- [x] Get a labeling file
- [x] Package unit labels
- [x] Open a dispute over a shipment
- [x] Pass the shipment to shipping
- [x] Shipment cancellation reasons
- [x] Shipments cancellation reasons
- [x] Cancel the shipment
- [x] Add weight for bulk products in a shipment
- [x] Cancel sending some products in the shipment
- [x] List of shipment certificates
- [x] Sign shipment certificates
- [x] List of shipments in the certificate
- [x] Change the status to "Delivering"
- [x] Add tracking numbers
- [x] Change the status to "Last Mile"
- [x] Change the status to "Delivered"
- [x] Change status to "Sent by seller"
- [x] Dates available for delivery reschedule
- [x] Reschedule shipment delivery date
- [x] ETGB customs declarations
## Returns
- [x] Get information about FBO returns (version 3)
- [x] Get information about FBS returns
## Cancellations
- [x] Get information about a rFBS cancellation request
- [x] Get a list of rFBS cancellation requests
- [x] Approve a rFBS cancellation request
- [x] Reject a rFBS cancellation request
## Chats with customers
- [x] Chats list
- [x] Send message
- [x] Send file
- [x] Chat history
- [x] Update chat
- [x] Create a new chat
- [x] Mark messages as read
## Invoices
- [x] Create or edit proforma invoice link
- [x] Get a proforma invoice link
- [x] Delete the proforma invoice link
## Reports
- [x] Report details
- [x] Reports list
- [x] Products report
- [x] Stocks report
- [x] Report on products movement
- [x] Returns report
- [x] Shipment report
- [x] Financial report
- [x] Issue a report on discounted products
- [x] Report on discounted products
- [x] List of reports on discounted products
## Analytics
- [x] Analytics data
- [x] Stocks and products report (version 2)
## Finance
- [x] Report on sold products
- [x] Transactions list (version 3)
- [x] Total transactions sum
## Seller rating
- [x] Get information on current seller ratings
- [x] Get information on seller ratings for the period

View File

@@ -8,8 +8,6 @@ A Ozon Seller API client written in Golang
Read full [documentation](https://docs.ozon.ru/api/seller/en/#tag/Introduction)
You can check [list of supported endpoints](ENDPOINTS.md)
## How to start
### API
Get Client-Id and Api-Key in your seller profile [here](https://seller.ozon.ru/app/settings/api-keys?locale=en)
@@ -91,8 +89,3 @@ func main() {
}
}
```
## Contribution
If you need some endpoints ASAP, create an issue and list all the endpoints. I will add them to library soon.
Or you can implement them and contribute to the project. Contribution to the project is welcome.

View File

@@ -37,17 +37,20 @@ func NewMockClient(handler http.HandlerFunc) *Client {
}
func (c Client) newRequest(ctx context.Context, method string, uri string, body interface{}) (*http.Request, error) {
var err error
var bodyJson []byte
// Set default values for empty fields if `default` tag is present
// And body is not nil
if body != nil {
if err := getDefaultValues(reflect.ValueOf(body)); err != nil {
return nil, err
}
}
bodyJson, err := json.Marshal(body)
if err != nil {
return nil, err
bodyJson, err = json.Marshal(body)
if err != nil {
return nil, err
}
}
uri, err = url.JoinPath(c.baseUrl, uri)

42
core.go
View File

@@ -5,6 +5,7 @@ import (
"net/http"
"reflect"
"strconv"
"strings"
"testing"
"time"
)
@@ -162,3 +163,44 @@ func TimeFromString(t *testing.T, format, datetime string) time.Time {
}
return dt
}
const ShortDateLayout = "2006-01-02"
// Do not use this structure for responses
// as there are no ways to unmarshal to any layout
// and leave nil if json field is null
type TimeFormat struct {
time.Time
layout string
}
func NewTimeFormat(t time.Time, layout string) *TimeFormat {
return &TimeFormat{
Time: t,
layout: layout,
}
}
func newTimeLayout(layout string) *TimeFormat {
return &TimeFormat{
layout: layout,
}
}
func (rd *TimeFormat) UnmarshalJSON(b []byte) error {
var err error
s := strings.Trim(string(b), `"`) // remove quotes
// Added for extra accuracy
// encoding/json won't invoke this method if field is null
if s == "null" {
return nil
}
rd.Time, err = time.Parse(rd.layout, s)
return err
}
func (rd *TimeFormat) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`%q`, rd.Time.Format(rd.layout))), nil
}

View File

@@ -1,8 +1,10 @@
package core
import (
"encoding/json"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
@@ -54,3 +56,97 @@ func TestDefaultValues(t *testing.T) {
assert.Equal(t, "empty_string", req.OptionalStructure.EmptyField)
assert.Equal(t, (*DefaultStructure)(nil), req.EmptyOptionalStructure)
}
func TestTimeFormat(t *testing.T) {
t.Run("Time Format Marshalling", func(t *testing.T) {
tests := []struct {
ft *TimeFormat
layout string
expectedJSON string
diff time.Duration
}{
{
ft: NewTimeFormat(time.Date(2024, 4, 30, 15, 42, 12, 55, time.FixedZone("Test Zone", 0)), ShortDateLayout),
layout: ShortDateLayout,
expectedJSON: `"2024-04-30"`,
diff: time.Hour * 24,
},
{
ft: NewTimeFormat(time.Date(2024, 4, 30, 0, 0, 0, 0, time.FixedZone("Test Zone", 0)), ShortDateLayout),
layout: ShortDateLayout,
expectedJSON: `"2024-04-30"`,
diff: time.Hour * 24,
},
{
ft: NewTimeFormat(time.Time{}, ShortDateLayout),
layout: ShortDateLayout,
expectedJSON: `"0001-01-01"`,
diff: time.Hour * 24,
},
{
ft: nil,
layout: ShortDateLayout,
expectedJSON: `null`,
diff: time.Hour * 24,
},
}
for _, tc := range tests {
marshaled, err := json.Marshal(tc.ft)
assert.Equal(t, nil, err)
assert.Equal(t, tc.expectedJSON, string(marshaled))
unmarshaled := newTimeLayout(tc.layout)
err = json.Unmarshal(marshaled, unmarshaled)
assert.Equal(t, nil, err)
if tc.ft != nil {
diffedTime := tc.ft.Add(-tc.diff)
assert.Equal(t, true, diffedTime.Before(unmarshaled.Time) || diffedTime.Equal(unmarshaled.Time))
assert.Equal(t, true, tc.ft.After(unmarshaled.Time) || tc.ft.Equal(unmarshaled.Time))
}
}
})
t.Run("Time Format in structure Marshalling", func(t *testing.T) {
type test struct {
Date *TimeFormat `json:"date"`
}
tests := []struct {
structure *test
layout string
expectedJSON string
diff time.Duration
}{
{
structure: &test{Date: NewTimeFormat(time.Date(2024, 4, 30, 5, 4, 7, 20, time.FixedZone("Test Zone", 0)), ShortDateLayout)},
layout: ShortDateLayout,
expectedJSON: `{"date":"2024-04-30"}`,
diff: time.Hour * 24,
},
{
structure: &test{Date: nil},
layout: ShortDateLayout,
expectedJSON: `{"date":null}`,
diff: time.Hour * 24,
},
}
for _, tc := range tests {
marshaled, err := json.Marshal(tc.structure)
assert.Equal(t, nil, err)
assert.Equal(t, tc.expectedJSON, string(marshaled))
unmarshaled := &test{Date: newTimeLayout(tc.layout)}
err = json.Unmarshal(marshaled, unmarshaled)
assert.Equal(t, nil, err)
if tc.structure != nil && tc.structure.Date != nil {
diffedTime := tc.structure.Date.Add(-tc.diff)
assert.Equal(t, true, diffedTime.Before(unmarshaled.Date.Time) || diffedTime.Equal(unmarshaled.Date.Time))
assert.Equal(t, true, tc.structure.Date.After(unmarshaled.Date.Time) || tc.structure.Date.Equal(unmarshaled.Date.Time))
}
}
})
}

View File

@@ -3,7 +3,6 @@ package ozon
import (
"context"
"net/http"
"time"
core "github.com/diphantxm/ozon-api-client"
)
@@ -14,10 +13,10 @@ type Analytics struct {
type GetAnalyticsDataParams struct {
// Date from which the data will be in the report
DateFrom time.Time `json:"date_from"`
DateFrom *core.TimeFormat `json:"date_from"`
// Date up to which the data will be in the report
DateTo time.Time `json:"date_to"`
DateTo *core.TimeFormat `json:"date_to"`
// Items Enum: "unknownDimension" "sku" "spu" "day" "week" "month" "year" "category1" "category2" "category3" "category4" "brand" "modelID"
// Data grouping available to all sellers:
@@ -164,7 +163,7 @@ type GetStocksOnWarehousesParams struct {
// Number of elements that will be skipped in the response. For example, if `offset=10`, the response will start with the 11th element found
Offset int64 `json:"offset"`
// Warehouse type filter:
// Warehouse type filter
WarehouseType WarehouseType `json:"warehouse_type" default:"ALL"`
}
@@ -201,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
@@ -217,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

@@ -4,6 +4,7 @@ import (
"context"
"net/http"
"testing"
"time"
core "github.com/diphantxm/ozon-api-client"
)
@@ -22,8 +23,8 @@ func TestGetAnalyticsData(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetAnalyticsDataParams{
DateFrom: core.TimeFromString(t, "2006-01-02", "2020-09-01"),
DateTo: core.TimeFromString(t, "2006-01-02", "2021-10-15"),
DateFrom: core.NewTimeFormat(time.Now().Add(time.Duration(30)*24*time.Hour), core.ShortDateLayout),
DateTo: core.NewTimeFormat(time.Now(), core.ShortDateLayout),
Dimension: []GetAnalyticsDataDimension{SKUDimension, DayDimension},
Metrics: []GetAnalyticsDataFilterMetric{HistViewPDP},
Sort: []GetAnalyticsDataSort{
@@ -144,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)
}
}
}

View File

@@ -113,15 +113,15 @@ type ListCancellationsParams struct {
type ListCancellationsFilter struct {
// Filter by cancellation initiator
CancellationInitiator []string `json:"cancellation_initiator"`
CancellationInitiator []string `json:"cancellation_initiator,omitempty"`
// Filter by shipment number.
//
// Optional parameter. You can pass several values here
PostingNumber string `json:"posting_number"`
PostingNumber string `json:"posting_number,omitempty"`
// Filter by cancellation request status
State string `json:"state"`
State string `json:"state,omitempty"`
}
type ListCancellationWith struct {

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

@@ -99,11 +99,6 @@ type ListOfCertifiedCategoriesParams struct {
type ListOfCertifiedCategoriesResponse struct {
core.CommonResponse
// Method result
Result ListOfCertifiedCategoriesResult `json:"result"`
}
type ListOfCertifiedCategoriesResult struct {
// Certified categories details
Certification []ListOfCertifiedCategoriesResultCert `json:"certification"`
@@ -112,16 +107,25 @@ type ListOfCertifiedCategoriesResult struct {
}
type ListOfCertifiedCategoriesResultCert struct {
// Identifier of the certified category
CategoryId int64 `json:"category_id"`
// Category name
CategoryName string `json:"category_name"`
// Indication of a mandatory category
IsRequired bool `json:"is_required"`
// Type identifier of the certified category
TypeId int64 `json:"type_id"`
// Name of the type of certified category
TypeName string `json:"type_name"`
}
// List of certified categories
func (c Certificates) ListOfCertifiedCategories(ctx context.Context, params *ListOfCertifiedCategoriesParams) (*ListOfCertifiedCategoriesResponse, error) {
url := "/v1/product/certification/list"
url := "/v2/product/certification/list"
resp := &ListOfCertifiedCategoriesResponse{}

View File

@@ -151,15 +151,16 @@ func TestListOfCertifiedCategories(t *testing.T) {
PageSize: 100,
},
`{
"result": {
"certification": [
{
"is_required": true,
"category_name": "Витаминно-минеральные комплексы для взрослых"
}
],
"total": 1
}
"certification": [
{
"category_id": 0,
"category_name": "string",
"is_required": true,
"type_id": 0,
"type_name": "string"
}
],
"total": 1
}`,
},
// Test No Client-Id or Api-Key

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
@@ -386,7 +381,7 @@ func (c Chats) Create(ctx context.Context, params *CreateNewChatParams) (*Create
type MarkAsReadParams struct {
// Chat identifier
Chatid string `json:"chat_id"`
ChatId string `json:"chat_id"`
// Message identifier
FromMessageId uint64 `json:"from_message_id"`

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")
}
}
@@ -400,7 +398,7 @@ func TestMarkAsRead(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&MarkAsReadParams{
Chatid: "99feb3fc-a474-469f-95d5-268b470cc607",
ChatId: "99feb3fc-a474-469f-95d5-268b470cc607",
FromMessageId: 3000000000118032000,
},
`{

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

@@ -1,6 +1,8 @@
package ozon
import "time"
import (
"time"
)
const (
testTimeout = 5 * time.Second
@@ -91,16 +93,16 @@ const (
Purchased ListDiscountRequestsStatus = "PURCHASED"
)
type WorkingDay string
type WorkingDay int
const (
Mon WorkingDay = "1"
Tue WorkingDay = "2"
Wed WorkingDay = "3"
Thu WorkingDay = "4"
Fri WorkingDay = "5"
Sat WorkingDay = "6"
Sun WorkingDay = "7"
Mon WorkingDay = 1
Tue WorkingDay = 2
Wed WorkingDay = 3
Thu WorkingDay = 4
Fri WorkingDay = 5
Sat WorkingDay = 6
Sun WorkingDay = 7
)
type GetAnalyticsDataDimension string
@@ -124,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"
@@ -349,7 +333,7 @@ const (
ServiceProcessingIdentifiedSurplus DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedSurplus"
ServiceProcessingIdentifiedDiscrepancies DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedDiscrepancies"
ServiceItemInternetSiteAdvertising DetailsServiceItemName = "MarketplaceServiceItemInternetSiteAdvertising"
ServiceItemPremiumSubscribtion DetailsServiceItemName = "MarketplaceServiceItemPremiumSubscribtion"
ServiceItemPremiumSubscribtion DetailsServiceItemName = "MarketplaceServiceItemSubscribtionPremium"
AgencyFeeAggregator3PLGlobalItem DetailsServiceItemName = "MarketplaceAgencyFeeAggregator3PLGlobalItem"
)
@@ -461,11 +445,16 @@ const (
type GetFBSReturnsFilterStatus string
const (
Moving GetFBSReturnsFilterStatus = "moving"
ReturnedToSeller GetFBSReturnsFilterStatus = "returned_to_seller"
WaitingForSeller GetFBSReturnsFilterStatus = "waiting_for_seller"
AcceptedFromCustomer GetFBSReturnsFilterStatus = "accepted_from_customer"
CancelledWithCompensation GetFBSReturnsFilterStatus = "cancelled_with_compensation"
ReadyForShipment GetFBSReturnsFilterStatus = "ready_for_shipment"
Disposing GetFBSReturnsFilterStatus = "disposing"
Disposed GetFBSReturnsFilterStatus = "disposed"
ArrivedForResale GetFBSReturnsFilterStatus = "arrived_for_resale"
MovingToResale GetFBSReturnsFilterStatus = "moving_to_resale"
)
type GetFBOReturnsFilterStatus string
@@ -572,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
@@ -684,3 +679,257 @@ const (
// Check is failed
MandatoryMarkStatusFailed MandatoryMarkStatus = "failed"
)
type GetCarriageStatus string
const (
// acceptance in progress
GetCarriageStatusReceived GetCarriageStatus = "received"
// closed after acceptance
GetCarriageStatusClosed GetCarriageStatus = "closed"
GetCarriageStatusSended GetCarriageStatus = "sended"
GetCarriageStatusCancelled GetCarriageStatus = "cancelled"
)
type TransactionOperationService string
const (
// return of unclaimed products from the customer to the warehouse
TransactionNotDelivered TransactionOperationService = "MarketplaceNotDeliveredCostItem"
// return from the customer to the warehouse after delivery
TransactionReturnAfterDelivery TransactionOperationService = "TransactionOperationServiceNotDelivered"
// product delivery to the customer
TransactionDelivery TransactionOperationService = "MarketplaceDeliveryCostItem"
// purchasing reviews on the platform
TransactionSaleReviews TransactionOperationService = "MarketplaceSaleReviewsItem"
// products delivery to the Ozon warehouse (cross docking)
TransactionItemAdForSupplierLogistic TransactionOperationService = "ItemAdvertisementForSupplierLogistic"
// product placement service
TransactionServiceStorageItem TransactionOperationService = "OperationMarketplaceServiceStorage"
// products promotion
TransactionMarketingActionCost TransactionOperationService = "MarketplaceMarketingActionCostItem"
// promotion and selling on an instalment plan
TransactionServiceItemInstallment TransactionOperationService = "MarketplaceServiceItemInstallment"
// mandatory products labeling
TransactionServiceMarkingItems TransactionOperationService = "MarketplaceServiceItemMarkingItems"
// flexible payment schedule
TransactionServiceFlexiblePaymentSchedule TransactionOperationService = "MarketplaceServiceItemFlexiblePaymentSchedule"
// picking up products for removal by the seller
TransactionServiceReturnFromStock TransactionOperationService = "MarketplaceServiceItemReturnFromStock"
TransactionServiceStarsMembership TransactionOperationService = "ItemAgentServiceStarsMembership"
// forwarding trade
TransactionItemAdForSupplierLogisticSeller TransactionOperationService = "ItemAdvertisementForSupplierLogisticSeller"
// last mile
TransactionServiceDeliveryToCustomer TransactionOperationService = "MarketplaceServiceItemDelivToCustomer"
// pipeline
TransactionServiceDirectFlowTrans TransactionOperationService = "MarketplaceServiceItemDirectFlowTrans"
// shipment processing
TransactionServiceDropoffFF TransactionOperationService = "MarketplaceServiceItemDropoffFF"
// shipment processing
TransactionServiceDropoffPVZ TransactionOperationService = "MarketplaceServiceItemDropoffPVZ"
// shipment processing
TransactionServiceDropoffSC TransactionOperationService = "MarketplaceServiceItemDropoffSC"
// order packaging
TransactionServiceFulfillment TransactionOperationService = "MarketplaceServiceItemFulfillment"
// picking products up by car from the seller's address (Pick-up)
TransactionServicePickup TransactionOperationService = "MarketplaceServiceItemPickup"
// return processing
TransactionServiceReturnAfterDelivToCustomer TransactionOperationService = "MarketplaceServiceItemReturnAfterDelivToCustomer"
// reverse pipeline
TransactionServiceReturnFlowTrans TransactionOperationService = "MarketplaceServiceItemReturnFlowTrans"
// cancellation processing
TransactionServiceReturnNotDelivToCustomer TransactionOperationService = "MarketplaceServiceItemReturnNotDelivToCustomer"
// unredeemed order processing
TransactionServiceReturnPartGoodsCustomer TransactionOperationService = "MarketplaceServiceItemReturnPartGoodsCustomer"
// acquiring payment
TransactionRedistributionOfAcquiringOperation TransactionOperationService = "MarketplaceRedistributionOfAcquiringOperation"
// FBS return short-term placement
TransactionServiceAtPickupPointFBS TransactionOperationService = "MarketplaceReturnStorageServiceAtThePickupPointFbsItem"
// FBS return long-term placement
TransactionServiceInWarehouseFBS TransactionOperationService = "MarketplaceReturnStorageServiceInTheWarehouseFbsItem"
// bulky products delivery
TransactionServiceDeliveryKGT TransactionOperationService = "MarketplaceServiceItemDeliveryKGT"
// logistics
TransactionServiceDirectFlowLogistic TransactionOperationService = "MarketplaceServiceItemDirectFlowLogistic"
// reverse logistics
TransactionServiceReturnFlowLogistic TransactionOperationService = "MarketplaceServiceItemReturnFlowLogistic"
// "Seller's Bonus" promotion service
TransactionServicePremiumCashbackIndPoints TransactionOperationService = "MarketplaceServicePremiumCashbackIndividualPoints"
// Premium promotion service, fixed commission
TransactionServicePremiumPromotion TransactionOperationService = "MarketplaceServicePremiumPromotion"
// withholding for product shortage
TransactionServiceWithHoldingForUndeliverableGoods TransactionOperationService = "OperationMarketplaceWithHoldingForUndeliverableGoods"
// drop-off service at the pick-up point
TransactionServiceDropoffPPZ TransactionOperationService = "MarketplaceServiceItemDropoffPPZ"
// reissue of returns at the pick-up point
TransactionServiceRedistributionReturnsPVZ TransactionOperationService = "MarketplaceServiceItemRedistributionReturnsPVZ"
// Agregator 3PL Globalagency service tariffication
TransactionServiceAgencyFeeAggregator3PLGlobal TransactionOperationService = "OperationMarketplaceAgencyFeeAggregator3PLGlobal"
)
type PaymentTypeGroupName string
const (
PaymentTypeGroupByCardOnline PaymentTypeGroupName = "by card online"
PaymentTypeGroupOzonCard PaymentTypeGroupName = "Ozon Card"
PaymentTypeGroupOzonCardAtCheckout PaymentTypeGroupName = "Ozon Card at checkout"
PaymentTypeGroupBySavedBankCardUponPickup PaymentTypeGroupName = "by saved bank card upon pick-up"
PaymentTypeGroupFasterPaymentSystem PaymentTypeGroupName = "Faster payment system"
PaymentTypeGroupOzonInstallment PaymentTypeGroupName = "Ozon Installment"
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"
)
type VAT string
const (
VAT0 VAT = "0"
VAT005 VAT = "0.05"
VAT007 VAT = "0.07"
VAT01 VAT = "0.1"
VAT02 VAT = "0.2"
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -37,28 +37,33 @@ type ListUnprocessedShipmentsFilter struct {
// Filter by the time by which the seller should pack the order. Period start.
//
// Format: YYYY-MM-DDThh: mm:ss. mcsZ. Example: 2020-03-18T07:34:50.359 Z
CutoffFrom time.Time `json:"cutoff_from"`
CutoffFrom *core.TimeFormat `json:"cutoff_from,omitempty"`
// Filter by the time by which the seller should pack the order. Period end.
//
// Format: YYYY-MM-DDThh: mm:ss. mcsZ. Example: 2020-03-18T07:34:50.359 Z
CutoffTo time.Time `json:"cutoff_to"`
CutoffTo *core.TimeFormat `json:"cutoff_to,omitempty"`
// Minimum date when shipment should be handed over for delivery
DeliveringDateFrom time.Time `json:"delivering_date_from"`
DeliveringDateFrom *core.TimeFormat `json:"delivering_date_from,omitempty"`
// Maximum date when shipment should be handed over for delivery
DeliveringDateTo time.Time `json:"delivering_date_to"`
DeliveringDateTo *core.TimeFormat `json:"delivering_date_to,omitempty"`
// 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.
//
// The FBP scheme is available only for sellers from China
FBPFilter FBPFilter `json:"fbpFilter"`
FBPFilter FBPFilter `json:"fbpFilter" default:"all"`
// Delivery service identifier
ProviderId []int64 `json:"provider_id"`
@@ -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"`
@@ -146,6 +154,9 @@ type FBSPosting struct {
// Number of the parent shipment which split resulted in the current shipment
ParentPostingNumber string `json:"parent_posting_number"`
// Date and time of successful verification of the courier code
PickupCodeVerifiedAt time.Time `json:"pickup_code_verified_at"`
// Shipment number
PostingNumber string `json:"posting_number"`
@@ -178,6 +189,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 +240,7 @@ type FBSPostingAddressee struct {
}
type FBSPostingAnalyticsData struct {
// Delivery city
// Delivery city. Only for rFBS shipments
City string `json:"city"`
// Delivery start date and time
@@ -213,9 +262,9 @@ type FBSPostingAnalyticsData struct {
IsPremium bool `json:"is_premium"`
// Payment method
PaymentTypeGroupName string `json:"payment_type_group_name"`
PaymentTypeGroupName PaymentTypeGroupName `json:"payment_type_group_name"`
// Delivery region
// Delivery region. Only for rFBS shipments
Region string `json:"region"`
// Delivery service
@@ -350,9 +399,6 @@ type FBSCustomer struct {
// Delivery address details
Address FBSCustomerAddress `json:"address"`
// Customer e-mail
CustomerEmail string `json:"customer_email"`
// Customer identifier
CustomerId int64 `json:"customer_id"`
@@ -361,7 +407,7 @@ type FBSCustomer struct {
// Customer phone number.
//
// Returns an empty string
// Returns an empty string ""
Phone string `json:"phone"`
}
@@ -452,6 +498,8 @@ type FinancialDataProduct struct {
CommissionsCurrencyCode string `json:"commissions_currency_code"`
// Services
//
// Deprecated: The parameter is outdated. To get information on accruals, use the `ListTransactions` method
ItemServices MarketplaceServices `json:"item_services"`
// Currency of your prices. It matches the currency set in the personal account settings
@@ -511,7 +559,7 @@ func (c FBS) ListUnprocessedShipments(ctx context.Context, params *ListUnprocess
type GetFBSShipmentsListParams struct {
// Sorting direction
Direction string `json:"dir,omitempty"`
Direction Order `json:"dir,omitempty"`
// Filter
Filter GetFBSShipmentsListFilter `json:"filter"`
@@ -532,16 +580,21 @@ type GetFBSShipmentsListFilter struct {
// Delivery method identifier
DeliveryMethodId []int64 `json:"delivery_method_id"`
// Filter for shipments delivered from partner warehouse (FBP). You can pass one of the following values:
// Filter for shipments delivered from partner warehouse (FBP)
//
// Default value is all.
//
// The FBP scheme is available only for sellers from China
FBPFilter FBPFilter `json:"fbpFilter"`
FBPFilter FBPFilter `json:"fbpFilter" default:"all"`
// 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"`
@@ -564,6 +617,13 @@ type GetFBSShipmentsListFilter struct {
// Warehouse identifier
WarehouseId []int64 `json:"warehouse_id"`
LastChangedStatusDate GetFBSShipmentsListFilterLastChangeDate `json:"last_changed_status_date"`
}
type GetFBSShipmentsListFilterLastChangeDate struct {
From time.Time `json:"from"`
To time.Time `json:"to"`
}
type GetFBSShipmentsListWith struct {
@@ -906,6 +966,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"`
@@ -956,6 +1019,9 @@ type GetShipmentDataByIdentifierResult struct {
// Shipment number
PostingNumber string `json:"posting_number"`
// Date and time of successful verification of the courier code
PickupCodeVerifiedAt time.Time `json:"pickup_code_verified_at"`
// Information on products and their instances.
//
// The response contains the field product_exemplars, if the attribute with.product_exemplars = true is passed in the request
@@ -967,6 +1033,9 @@ type GetShipmentDataByIdentifierResult struct {
// Delivery service status
ProviderStatus string `json:"provider_status"`
// Previous sub-status of the shipment
PreviousSubstatus string `json:"previous_substatus"`
// Information on lifting service. Only relevant for bulky products
// with a delivery by a third-party or integrated service
PRROption GetShipmentDataByIdentifierResultPRROption `json:"prr_option"`
@@ -993,6 +1062,9 @@ type GetShipmentDataByIdentifierResult struct {
// Shipment tracking number
TrackingNumber string `json:"tracking_number"`
// Details on shipping rate
Tariffication []FBSPostingTariffication `json:"tariffication"`
}
type GetShipmentDataByIdentifierResultAdditionalData struct {
@@ -1007,12 +1079,14 @@ type GetShipmentDataByIdentifierResultAddressee struct {
// Recipient name
Name string `json:"name"`
// Recipient phone number
// Recipient phone number.
//
// Returns an empty string ""
Phone string `json:"phone"`
}
type GetShipmentDataByIdentifierResultAnalyticsData struct {
// Delivery city
// Delivery city. Only for rFBS shipments
City string `json:"city"`
// Delivery start date and time
@@ -1035,7 +1109,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
@@ -1062,6 +1136,8 @@ type GetShipmentDataByIdentifierResultCourier struct {
Name string `json:"name"`
// Courier's phone number
//
// Returns an empty string ""
Phone string `json:"phone"`
}
@@ -1338,38 +1414,6 @@ func (c FBS) ListOfShipmentCertificates(ctx context.Context, params *ListOfShipm
return resp, nil
}
type SignShipmentCertificateParams struct {
// Certificate identifier
Id int64 `json:"id"`
// Type of shipment certificate:
// - act_of_mismatch — discrepancy certificate,
// - act_of_excess — surplus certificate
DocType string `json:"doc_type"`
}
type SignShipmentCertificateResponse struct {
core.CommonResponse
// Request processing
Result string `json:"result"`
}
// Signs shipment certificates electronically via the electronic documents (ED) system of Ozon logistics
func (c FBS) SignShipmentCertificate(ctx context.Context, params *SignShipmentCertificateParams) (*SignShipmentCertificateResponse, error) {
url := "/v2/posting/fbs/digital/act/document-sign"
resp := &SignShipmentCertificateResponse{}
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 ChangeStatusToParams struct {
// Shipment identifier
PostingNumber []string `json:"posting_number"`
@@ -1454,7 +1498,7 @@ func (c FBS) ChangeStatusToSendBySeller(ctx context.Context, params *ChangeStatu
}
type PassShipmentToShippingParams struct {
// Shipment identifier
// Shipment identifier. The maximum number of values in one request is 100
PostingNumber []string `json:"posting_number"`
}
@@ -1672,15 +1716,25 @@ type CreateTaskForGeneratingLabelResponse struct {
}
type CreateTaskForGeneratingLabelResult struct {
Tasks []CreateTaskForGeneratingLabelTask `json:"tasks"`
}
type CreateTaskForGeneratingLabelTask struct {
// Task identifier for labeling generation
TaskId int64 `json:"task_id"`
// Type of label generation task:
//
// big_label — for a regular label,
// small_label — for a small label
TaskType string `json:"task_type"`
}
// Method for creating a task for asynchronous labeling generation.
//
// To get labels created as a result of the method, use the /v1/posting/fbs/package-label/get method
func (c FBS) CreateTaskForGeneratingLabel(ctx context.Context, params *CreateTaskForGeneratingLabelParams) (*CreateTaskForGeneratingLabelResponse, error) {
url := "/v1/posting/fbs/package-label/create"
url := "/v2/posting/fbs/package-label/create"
resp := &CreateTaskForGeneratingLabelResponse{}
@@ -2100,7 +2154,8 @@ type PartialPackOrderAdditionalData struct {
// The primary unassembled shipment will contain some of the products
// that weren't passed to the request.
//
// Default status of created shipments is awaiting_deliver.
// Default status of created shipments is awaiting_packaging,
// which indicates that the shipment is awaiting assembly.
//
// The status of the original shipment will only change
// when the split shipments status changes
@@ -2167,6 +2222,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"`
@@ -2504,20 +2565,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"
@@ -2962,7 +3014,7 @@ type GetCarriageResponse struct {
RetryCount int32 `json:"retry_count"`
// Freight status
Status string `json:"status"`
Status GetCarriageStatus `json:"status"`
// Delivery method identifier
TPLProviderId int64 `json:"tpl_provider_id"`
@@ -2995,3 +3047,266 @@ 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
}
type ChangeShipmentCompositionParams struct {
// Freight identifier
CarriageId int64 `json:"carriage_id"`
// Current list of shipments
PostingNumbers []string `json:"posting_numbers"`
}
type ChangeShipmentCompositionResponse struct {
core.CommonResponse
Result []ChangeShipmentCompositionResult `json:"result"`
}
type ChangeShipmentCompositionResult struct {
// Error message
Error string `json:"error"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Request processing result. true if the request was executed successfully
Result bool `json:"result"`
}
// Method overwrites the list of orders in the shipment. Pass only orders in the awaiting_deliver status and ones which are ready for shipping.
func (c FBS) ChangeShipmentComposition(ctx context.Context, params *ChangeShipmentCompositionParams) (*ChangeShipmentCompositionResponse, error) {
url := "/v1/carriage/set-postings"
resp := &ChangeShipmentCompositionResponse{}
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 DeleteShipmentParams struct {
// Freight identifier
CarriageId int64 `json:"carriage_id"`
}
type DeleteShipmentResponse struct {
core.CommonResponse
// Error message
Error string `json:"error"`
// Carriage status
Status string `json:"carriage_status"`
}
func (c FBS) DeleteShipment(ctx context.Context, params *DeleteShipmentParams) (*DeleteShipmentResponse, error) {
url := "/v1/carriage/cancel"
resp := &DeleteShipmentResponse{}
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 VerifyCourierCodeParams struct {
// Courier code
PickupCode string `json:"pickup_code"`
// Shipment number
PostingNumber string `json:"posting_number"`
}
type VerifyCourierCodeResponse struct {
core.CommonResponse
// true, if the code is correct
Valid bool `json:"valid"`
}
// Use this method to verify the courier code when handing over realFBS Express shipments
func (c FBS) VerifyCourierCode(ctx context.Context, params *VerifyCourierCodeParams) (*VerifyCourierCodeResponse, error) {
url := "/v1/posting/fbs/pick-up-code/verify"
resp := &VerifyCourierCodeResponse{}
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

@@ -24,8 +24,8 @@ func TestListUnprocessedShipments(t *testing.T) {
&ListUnprocessedShipmentsParams{
Direction: "ASC",
Filter: ListUnprocessedShipmentsFilter{
CutoffFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-08-24T14:15:22Z"),
CutoffTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-08-31T14:15:22Z"),
CutoffFrom: core.NewTimeFormat(core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-08-24T14:15:22Z"), "2006-01-02T15:04:05Z"),
CutoffTo: core.NewTimeFormat(core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-08-31T14:15:22Z"), "2006-01-02T15:04:05Z"),
Status: "awaiting_packaging",
},
Limit: 100,
@@ -43,6 +43,7 @@ func TestListUnprocessedShipments(t *testing.T) {
"posting_number": "23713478-0018-3",
"order_id": 559293114,
"order_number": "33713378-0051",
"pickup_code_verified_at": "2025-01-17T11:03:00.124Z",
"status": "awaiting_packaging",
"delivery_method": {
"id": 15110442724000,
@@ -66,6 +67,7 @@ func TestListUnprocessedShipments(t *testing.T) {
"cancellation_initiator": ""
},
"customer": null,
"quantum_id": 0,
"products": [
{
"currency_code": "RUB",
@@ -146,7 +148,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
@@ -224,6 +239,7 @@ func TestGetFBSShipmentsList(t *testing.T) {
"posting_number": "05708065-0029-1",
"order_id": 680420041,
"order_number": "05708065-0029",
"pickup_code_verified_at": "2025-01-17T10:59:26.614Z",
"status": "awaiting_deliver",
"substatus": "posting_awaiting_passport_data",
"delivery_method": {
@@ -264,16 +280,30 @@ 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
}
}`,
}`,
},
// Test No Client-Id or Api-Key
{
@@ -563,7 +593,10 @@ func TestGetShipmentDataByIdentifier(t *testing.T) {
"posting_number": "57195475-0050-3",
"order_id": 438764970,
"order_number": "57195475-0050",
"pickup_code_verified_at": "2025-01-17T11:04:59.958Z",
"status": "awaiting_packaging",
"substatus": "posting_awaiting_passport_data",
"previous_substatus": "posting_transferring_to_delivery",
"delivery_method": {
"id": 18114520187000,
"name": "Ozon Логистика самостоятельно, Москва",
@@ -615,7 +648,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": ""
}
]
}
}`,
},
@@ -836,62 +882,6 @@ func TestListOfShipmentCertificates(t *testing.T) {
}
}
func TestSignShipmentCertificate(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SignShipmentCertificateParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SignShipmentCertificateParams{
Id: 900000250859000,
DocType: "act_of_mismatch",
},
`{
"result": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SignShipmentCertificateParams{},
`{
"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().SignShipmentCertificate(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &SignShipmentCertificateResponse{})
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 == "" {
t.Errorf("Result cannot be empty")
}
}
}
}
func TestChangeStatusTo(t *testing.T) {
t.Parallel()
@@ -1278,7 +1268,16 @@ func TestCreateTaskForGeneratingLabel(t *testing.T) {
},
`{
"result": {
"task_id": 5819327210249
"tasks": [
{
"task_id": 5819327210248,
"task_type": "big_label"
},
{
"task_id": 5819327210249,
"task_type": "small_label"
}
]
}
}`,
},
@@ -1310,7 +1309,7 @@ func TestCreateTaskForGeneratingLabel(t *testing.T) {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.TaskId == 0 {
if len(resp.Result.Tasks) != 0 && resp.Result.Tasks[0].TaskId == 0 {
t.Errorf("Task id cannot be 0")
}
}
@@ -1873,6 +1872,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",
@@ -3000,3 +3001,428 @@ 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)
}
}
}
func TestChangeShipmentComposition(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ChangeShipmentCompositionParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ChangeShipmentCompositionParams{
CarriageId: 10,
PostingNumbers: []string{"something"},
},
`{
"result": [
{
"error": "string",
"posting_number": "something",
"result": true
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ChangeShipmentCompositionParams{},
`{
"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().ChangeShipmentComposition(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ChangeShipmentCompositionResponse{})
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) > 0 {
if resp.Result[0].PostingNumber != test.params.PostingNumbers[0] {
t.Errorf("posting numbers are different. Expected: %s, got: %s", test.params.PostingNumbers[0], resp.Result[0].PostingNumber)
}
}
}
}
}
func TestDeleteShipment(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *DeleteShipmentParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&DeleteShipmentParams{
CarriageId: 10,
},
`{
"error": "string",
"carriage_status": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&DeleteShipmentParams{},
`{
"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().DeleteShipment(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DeleteShipmentResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestVerifyCourierCode(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *VerifyCourierCodeParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&VerifyCourierCodeParams{
PickupCode: "string",
PostingNumber: "string",
},
`{
"valid": true
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&VerifyCourierCodeParams{},
`{
"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().VerifyCourierCode(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &VerifyCourierCodeResponse{})
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,65 +113,46 @@ 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.
//
// Report is returned no later than the 5th day of the next month
func (c Finance) ReportOnSoldProducts(ctx context.Context, params *ReportOnSoldProductsParams) (*ReportOnSoldProductsResponse, error) {
url := "/v1/finance/realization"
url := "/v2/finance/realization"
resp := &ReportOnSoldProductsResponse{}
@@ -306,10 +305,10 @@ type ListTransactionsResult struct {
// Transactions infromation
Operations []ListTransactionsResultOperation `json:"operations"`
// Number of pages
// Number of pages. If 0, there are no more pages
PageCount int64 `json:"page_count"`
// Number of products
// Number of transactions on all pages. If 0, there are no more transactions
RowCount int64 `json:"row_count"`
}
@@ -382,7 +381,7 @@ type ListTransactionsResultOperationPosting struct {
type ListTransactionsResultOperationService struct {
// Service name
Name string `json:"name"`
Name TransactionOperationService `json:"name"`
// Price
Price float64 `json:"price"`
@@ -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

@@ -16,11 +16,11 @@ type CreateUpdateProformaLinkParams struct {
// Shipment number
PostingNumber string `json:"posting_number"`
// Proforma invoice link
// Invoice link. Use the `v1/invoice/file/upload` method to create a link
URL string `json:"url"`
// Invoice HS-code. Pass a number up to 12 characters long
HSCode string `json:"hs_code"`
// Product HS-codes
HSCodes []CreateUpdateProformaLinkHSCode `json:"hs_codes"`
// Invoice date
Date time.Time `json:"date"`
@@ -28,13 +28,21 @@ type CreateUpdateProformaLinkParams struct {
// Invoice number. The number can contain letters and digits, maximum length is 50 characters
Number string `json:"number"`
// Cost stated in the invoice. The fractional part is separated by decimal point, up to two digits after the decimal poin
// Cost stated in the invoice. The fractional part is separated by decimal point, up to two digits after the decimal point
Price float64 `json:"price"`
// Invoice currency
PriceCurrency InvoiceCurrency `json:"price_currency" default:"USD"`
}
type CreateUpdateProformaLinkHSCode struct {
// Product HS code
Code string `json:"code"`
// Product identifier in the Ozon system, SKU
SKU string `json:"sku"`
}
type CreateUpdateProformaLinkResponse struct {
core.CommonResponse
@@ -42,9 +50,9 @@ type CreateUpdateProformaLinkResponse struct {
Result bool `json:"result"`
}
// Create or edit proforma invoice link for VAT refund to Turkey sellers
// Create or edit an invoice for VAT refund to Turkey sellers
func (c Invoices) CreateUpdate(ctx context.Context, params *CreateUpdateProformaLinkParams) (*CreateUpdateProformaLinkResponse, error) {
url := "/v1/invoice/create-or-update"
url := "/v2/invoice/create-or-update"
resp := &CreateUpdateProformaLinkResponse{}
@@ -70,13 +78,32 @@ type GetProformaLinkResponse struct {
}
type GetProformaLinkResult struct {
// Proforma invoice link
// Invoice uploading date
Date time.Time `json:"date"`
// Invoice link
FileURL string `json:"file_url"`
// Product HS-codes
HSCodes []CreateUpdateProformaLinkHSCode `json:"hs_codes"`
// Invoice number
Number string `json:"number"`
// Cost stated in the invoice.
// The fractional part is separated by decimal point,
// up to two digits after the decimal point.
//
// Example: 199.99
Price float64 `json:"price"`
// Invoice currency
PriceCurrency InvoiceCurrency `json:"price_currency"`
}
// Get a proforma invoice link
func (c Invoices) Get(ctx context.Context, params *GetProformaLinkParams) (*GetProformaLinkResponse, error) {
url := "/v1/invoice/get"
url := "/v2/invoice/get"
resp := &GetProformaLinkResponse{}
@@ -114,3 +141,33 @@ func (c Invoices) Delete(ctx context.Context, params *DeleteProformaLinkParams)
return resp, nil
}
type UploadInvoiceParams struct {
// Base64 encoded invoice
Content string `json:"base64_content"`
// Shipment number
PostingNumber string `json:"posting_number"`
}
type UploadInvoiceResponse struct {
core.CommonResponse
// Link to invoice
URL string `json:"url"`
}
// Available file types: JPEG and PDF. Maximum file size: 10 MB
func (c Invoices) Upload(ctx context.Context, params *UploadInvoiceParams) (*UploadInvoiceResponse, error) {
url := "/v1/invoice/file/upload"
resp := &UploadInvoiceResponse{}
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

@@ -24,7 +24,16 @@ func TestCreateUpdateProformaLink(t *testing.T) {
&CreateUpdateProformaLinkParams{
PostingNumber: "33920146-0252-1",
URL: "https://cdn.ozone.ru/s3/ozon-disk-api/techdoc/seller-api/earsivfatura_1690960445.pdf",
HSCode: "2134322",
HSCodes: []CreateUpdateProformaLinkHSCode{
{
Code: "534758761999",
SKU: "SKU123",
},
{
Code: "534758761000",
SKU: "SKU456",
},
},
Date: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2023-08-01T12:08:44.342Z"),
Number: "424fdsf234",
Price: 234.34,
@@ -82,7 +91,17 @@ func TestGetProformaLink(t *testing.T) {
},
`{
"result": {
"file_url": "string"
"date": "2019-08-24T14:15:22Z",
"file_url": "string",
"hs_codes": [
{
"code": "string",
"sku": "string"
}
],
"number": "string",
"price": 0,
"price_currency": "string"
}
}`,
},
@@ -165,3 +184,54 @@ func TestDeleteProformaLink(t *testing.T) {
}
}
}
func TestUploadInvoice(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *UploadInvoiceParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&UploadInvoiceParams{
PostingNumber: "posting number",
Content: "content",
},
`{
"url": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&UploadInvoiceParams{},
`{
"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.Invoices().Upload(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UploadInvoiceResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

@@ -42,6 +42,9 @@ type Client struct {
strategies *Strategies
barcodes *Barcodes
passes *Passes
clusters *Clusters
quants *Quants
reviews *Reviews
}
func (c Client) Analytics() *Analytics {
@@ -124,6 +127,18 @@ func (c Client) Passes() *Passes {
return c.passes
}
func (c Client) Clusters() *Clusters {
return c.clusters
}
func (c Client) Quants() *Quants {
return c.quants
}
func (c Client) Reviews() *Reviews {
return c.reviews
}
type ClientOption func(c *ClientOptions)
func WithHttpClient(httpClient core.HttpClient) ClientOption {
@@ -188,6 +203,9 @@ 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},
reviews: &Reviews{client: coreClient},
}
}
@@ -216,5 +234,8 @@ 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},
reviews: &Reviews{client: coreClient},
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -98,7 +98,7 @@ type AddProductToPromotionParams struct {
type AddProductToPromotionProduct struct {
// Product identifier
ProductId float64 `json:"produt_id"`
ProductId float64 `json:"product_id"`
// Promotional product price
ActionPrice float64 `json:"action_price"`
@@ -136,7 +136,7 @@ func (c Promotions) AddToPromotion(ctx context.Context, params *AddProductToProm
resp := &AddProductToPromotionResponse{}
response, err := c.client.Request(ctx, http.MethodGet, url, params, resp, nil)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}

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{}
@@ -526,10 +526,10 @@ type GetShipmentReportFilter struct {
OfferId string `json:"offer_id"`
// Order processing start date and time
ProcessedAtFrom time.Time `json:"processed_at_from"`
ProcessedAtFrom *core.TimeFormat `json:"processed_at_from,omitempty"`
// Time when the order appeared in your personal account
ProcessedAtTo time.Time `json:"processed_at_to"`
ProcessedAtTo *core.TimeFormat `json:"processed_at_to,omitempty"`
// Product identifier in the Ozon system, SKU
SKU []int64 `json:"sku"`

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{})
}
}
}
@@ -437,8 +431,8 @@ func TestGetShipmentReport(t *testing.T) {
&GetShipmentReportParams{
Filter: &GetShipmentReportFilter{
DeliverySchema: []string{"fbs", "fbo", "crossborder"},
ProcessedAtFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-09-02T17:10:54.861Z"),
ProcessedAtTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-02T17:10:54.861Z"),
ProcessedAtFrom: core.NewTimeFormat(core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-09-02T17:10:54.861Z"), "2006-01-02T15:04:05Z"),
ProcessedAtTo: core.NewTimeFormat(core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-02T17:10:54.861Z"), "2006-01-02T15:04:05Z"),
},
},
`{

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"`
// Name of the warehouse the product is being transported to
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
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"`
@@ -898,9 +667,6 @@ type GetFBSQuantityReturnsPagination struct {
type GetFBSQuantityReturnsResponse struct {
core.CommonResponse
// Seller identifier
CompanyId int64 `json:"company_id"`
DropoffPoints []GetFBSQuantityDropoffPoint `json:"drop_off_points"`
// true if there are any other points where sellers have orders waiting
@@ -928,6 +694,12 @@ type GetFBSQuantityDropoffPoint struct {
// Seller's warehouses identifiers
WarehouseIds []string `json:"warehouses_ids"`
// Number of boxes in drop-off point
BoxCount int32 `json:"box_count"`
// Time zone offset of the shipping time from UTC-0
UTCOffset string `json:"utc_offset"`
}
type GetFBSQuantityDropoffPointPassInfo struct {
@@ -943,7 +715,253 @@ 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 {
// Filters. Use only one filter per request. Otherwise it returns an error
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. Pass no more than 50 postings
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"`
// Product quantity
Quantity int32 `json:"quantity"`
}
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()
@@ -1010,10 +823,10 @@ func TestFBSQuantity(t *testing.T) {
},
},
`{
"company_id": 0,
"drop_off_points": [
{
"address": "string",
"box_count": 0,
"id": 0,
"name": "string",
"pass_info": {
@@ -1022,6 +835,7 @@ func TestFBSQuantity(t *testing.T) {
},
"place_id": 0,
"returns_count": 0,
"utc_offset": "string",
"warehouses_ids": [
"string"
]
@@ -1059,3 +873,154 @@ 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
},
"quantity": 1
},
"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)
}
}
}

334
ozon/reviews.go Normal file
View File

@@ -0,0 +1,334 @@
package ozon
import (
"context"
"net/http"
"time"
core "github.com/diphantxm/ozon-api-client"
)
type Reviews struct {
client *core.Client
}
type LeaveCommentParams struct {
// Review status update
MarkReviewAsProcesses bool `json:"mark_review_as_processed"`
// Identifier of the parent comment you're replying to
ParentCommentId string `json:"parent_comment_id"`
// Review identifier
ReviewId string `json:"review_id"`
// Comment text
Text string `json:"text"`
}
type LeaveCommentResponse struct {
core.CommonResponse
// Comment identifier
CommentId string `json:"comment_id"`
}
// Only available to sellers with the Premium Plus subscription
func (c Reviews) LeaveComment(ctx context.Context, params *LeaveCommentParams) (*LeaveCommentResponse, error) {
url := "/v1/review/comment/create"
resp := &LeaveCommentResponse{}
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 DeleteCommentParams struct {
// Comment identifier
CommentId string `json:"comment_id"`
}
type DeleteCommentResponse struct {
core.CommonResponse
}
// Only available to sellers with the Premium Plus subscription
func (c Reviews) DeleteComment(ctx context.Context, params *DeleteCommentParams) (*DeleteCommentResponse, error) {
url := "/v1/review/comment/delete"
resp := &DeleteCommentResponse{}
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 ListCommentsParams struct {
// Limit of values in the response. Minimum is 20. Maximum is 100
Limit int32 `json:"limit"`
// Number of elements that is skipped in the response.
// For example, if offset = 10, the response starts with the 11th element found
Offset int32 `json:"offset"`
// Review identifier
ReviewId string `json:"review_id"`
// Sorting direction
SortDir Order `json:"sort_dir"`
}
type ListCommentsResponse struct {
core.CommonResponse
// Number of elements in the response
Offset int32 `json:"offset"`
// Comment details
Comments []Comment `json:"comments"`
}
type Comment struct {
// Comment identifier
Id string `json:"id"`
// true, if the comment was left by an official, false if a customer left it
IsOfficial bool `json:"is_official"`
// true, if the comment was left by a seller, false if a customer left it
IsOwner bool `json:"is_owner"`
// Identifier of the parent comment to reply to
ParentCommentId string `json:"parent_comment_id"`
// Date the comment was published
PublishedAt time.Time `json:"published_at"`
// Comment text
Text string `json:"text"`
}
// Only available to sellers with the Premium Plus subscription
//
// Method returns information about comments on reviews that have passed moderation
func (c Reviews) ListComments(ctx context.Context, params *ListCommentsParams) (*ListCommentsResponse, error) {
url := "/v1/review/comment/list"
resp := &ListCommentsResponse{}
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
}
// Only available to sellers with the Premium Plus subscription
type ChangeStatusParams struct {
// Array with review identifiers from 1 to 100
ReviewIds []string `json:"review_ids"`
// Review status
Status string `json:"status"`
}
type ChangeStatusResponse struct {
core.CommonResponse
}
// Only available to sellers with the Premium Plus subscription
func (c Reviews) ChangeStatus(ctx context.Context, params *ChangeStatusParams) (*ChangeStatusResponse, error) {
url := "/v1/review/change-status"
resp := &ChangeStatusResponse{}
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 CountReviewsResponse struct {
core.CommonResponse
// Number of processed review
Processed int32 `json:"processed"`
// Number of all reviews
Total int32 `json:"total"`
// Number of unprocessed reviews
Unprocessed int32 `json:"unprocessed"`
}
// Only available to sellers with the Premium Plus subscription
func (c Reviews) Count(ctx context.Context) (*CountReviewsResponse, error) {
url := "/v1/review/count"
resp := &CountReviewsResponse{}
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 GetReviewParams struct {
// Review identifier
ReviewId string `json:"review_id"`
}
type GetReviewResponse struct {
core.CommonResponse
ReviewDetails
// Number of dislikes on the review
DislikesAmount int32 `json:"dislikes_amount"`
// Number of likes on the review
LikesAmount int32 `json:"likes_amount"`
// Image details
Photos []ReviewPhoto `json:"photos"`
// Video details
Videos []ReviewVideo `json:"videos"`
}
type ReviewDetails struct {
// Number of comments on the review
CommentsAmount int32 `json:"comments_amount"`
// Review identifier
Id string `json:"id"`
// true, if the review affects the rating calculation
IsRatingParticipant bool `json:"is_rating_participant"`
// Status of the order for which the customer left a review
OrderStatus string `json:"order_status"`
// Number of images in the review
PhotosAmount int32 `json:"photos_amount"`
// Review publication date
PublishedAt time.Time `json:"published_at"`
// Review rating
Rating int32 `json:"rating"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Review status
Status string `json:"status"`
// Review text
Text string `json:"text"`
// Number of videos for the review
VideosAmount int32 `json:"videos_amount"`
}
type ReviewPhoto struct {
// Height
Height int32 `json:"height"`
// Link to image
URL string `json:"url"`
// Width
Width int32 `json:"width"`
}
type ReviewVideo struct {
// Height
Height int64 `json:"height"`
// Link to video preview
PreviewURL string `json:"preview_url"`
// Link to short video
ShortVideoPreviewURL string `json:"short_video_preview_url"`
// Video link
URL string `json:"url"`
// Width
Width int64 `json:"width"`
}
// Only available to sellers with the Premium Plus subscription
func (c Reviews) Get(ctx context.Context, params *GetReviewParams) (*GetReviewResponse, error) {
url := "/v1/review/info"
resp := &GetReviewResponse{}
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 ListReviewsParams struct {
// Identifier of the last review on the page
LastId string `json:"last_id"`
// Number of reviews in the response. Minimum is 20, maximum is 100
Limit int32 `json:"limit"`
// Sorting direction
SortDir Order `json:"sort_dir"`
// Review statuses
Status string `json:"status"`
}
type ListReviewsResponse struct {
core.CommonResponse
// true, if not all reviews were returned in the response
HasNext bool `json:"has_next"`
// Identifier of the last review on the page
LastId string `json:"last_id"`
// Review details
Reviews []ReviewDetails `json:"reviews"`
}
// Only available to sellers with the Premium Plus subscription
func (c Reviews) List(ctx context.Context, params *ListReviewsParams) (*ListReviewsResponse, error) {
url := "/v1/review/list"
resp := &ListReviewsResponse{}
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
}

416
ozon/reviews_test.go Normal file
View File

@@ -0,0 +1,416 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestLeaveComment(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *LeaveCommentParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&LeaveCommentParams{
MarkReviewAsProcesses: true,
ParentCommentId: "string",
ReviewId: "string1",
Text: "some string",
},
`{
"comment_id": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&LeaveCommentParams{},
`{
"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.Reviews().LeaveComment(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &LeaveCommentResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestDeleteComment(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *DeleteCommentParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&DeleteCommentParams{
CommentId: "string",
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&DeleteCommentParams{},
`{
"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.Reviews().DeleteComment(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DeleteCommentResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestListComments(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListCommentsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListCommentsParams{
Limit: 0,
Offset: 0,
ReviewId: "string",
SortDir: Ascending,
},
`{
"comments": [
{
"id": "string",
"is_official": true,
"is_owner": true,
"parent_comment_id": "string",
"published_at": "2019-08-24T14:15:22Z",
"text": "string"
}
],
"offset": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListCommentsParams{},
`{
"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.Reviews().ListComments(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListCommentsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestChangeStatus(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ChangeStatusParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ChangeStatusParams{
ReviewIds: []string{"string"},
Status: "PROCESSED",
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ChangeStatusParams{},
`{
"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.Reviews().ChangeStatus(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ChangeStatusResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestCountReviews(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"},
`{
"processed": 2,
"total": 3,
"unprocessed": 1
}`,
},
// 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.Reviews().Count(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CountReviewsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetReview(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetReviewParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetReviewParams{
ReviewId: "string",
},
`{
"comments_amount": 0,
"dislikes_amount": 0,
"id": "string",
"is_rating_participant": true,
"likes_amount": 0,
"order_status": "string",
"photos": [
{
"height": 0,
"url": "string",
"width": 0
}
],
"photos_amount": 0,
"published_at": "2019-08-24T14:15:22Z",
"rating": 0,
"sku": 0,
"status": "string",
"text": "string",
"videos": [
{
"height": 0,
"preview_url": "string",
"short_video_preview_url": "string",
"url": "string",
"width": 0
}
],
"videos_amount": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetReviewParams{},
`{
"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.Reviews().Get(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetReviewResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestListReviews(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListReviewsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListReviewsParams{
LastId: "string",
Limit: 0,
SortDir: Ascending,
Status: "ALL",
},
`{
"has_next": true,
"last_id": "string",
"reviews": [
{
"comments_amount": 0,
"id": "string",
"is_rating_participant": true,
"order_status": "string",
"photos_amount": 0,
"published_at": "2019-08-24T14:15:22Z",
"rating": 0,
"sku": 0,
"status": "string",
"text": "string",
"videos_amount": 0
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListReviewsParams{},
`{
"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.Reviews().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListReviewsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

@@ -377,6 +377,7 @@ type GetCompetitorPriceResult struct {
StrategyCompetitorProductURL string `json:"strategy_competitor_product_url"`
}
// If you add a product to your pricing strategy, the method returns you the price and a link to the competitor's product
func (c Strategies) GetCompetitorPrice(ctx context.Context, params *GetCompetitorPriceParams) (*GetCompetitorPriceResponse, error) {
url := "/v1/pricing-strategy/product/info"

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"`
@@ -158,6 +161,9 @@ type GetListOfDeliveryMethodsResult struct {
// Delivery service identifier
ProviderId int64 `json:"provider_id"`
// Minimum time to package an order in minutes according to warehouse settings
SLACutIn int64 `json:"sla_cut_in"`
// Delivery method status:
// - NEW—created,
// - EDITED—being edited,
@@ -189,3 +195,60 @@ 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"`
}
// Use the method to find sorting centres, pick-up points, and drop-off points available for cross-docking and direct supplies.
//
// You can view the addresses of all points on the map and in a table in the Knowledge Base.
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

@@ -21,27 +21,37 @@ func TestGetListOfWarehouses(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"result": [
{
"warehouse_id": 15588127982000,
"name": "Proffi (Панорама Групп)",
"is_rfbs": false
},
{
"warehouse_id": 22142605386000,
"name": "Склад на производственной",
"is_rfbs": true
},
{
"warehouse_id": 22208673494000,
"name": "Тест 37349",
"is_rfbs": true
},
{
"warehouse_id": 22240462819000,
"name": "Тест12",
"is_rfbs": true
}
"result": [
{
"warehouse_id": 1020000177886000,
"name": "This is a test",
"is_rfbs": false,
"has_entrusted_acceptance": false,
"first_mile_type": {
"dropoff_point_id": "",
"dropoff_timeslot_id": 0,
"first_mile_is_changing": false,
"first_mile_type": ""
},
"is_kgt": false,
"can_print_act_in_advance": false,
"min_working_days": 5,
"is_karantin": false,
"has_postings_limit": false,
"postings_limit": -1,
"working_days": [
1,
2,
3,
4,
5,
6,
7
],
"min_postings_limit": 10,
"is_timetable_editable": true,
"status": "disabled"
}
]
}`,
},
@@ -117,7 +127,8 @@ func TestGetListOfDeliveryMethods(t *testing.T) {
"template_id": 0,
"warehouse_id": 15588127982000,
"created_at": "2019-04-04T15:22:31.048202Z",
"updated_at": "2021-08-15T10:21:44.854209Z"
"updated_at": "2021-08-15T10:21:44.854209Z",
"sla_cut_in": 1440
}
],
"has_next": false
@@ -169,3 +180,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)
}
}
}