23 Commits

Author SHA1 Message Date
diPhantxm
dcf366d7d4 update 2025-03-02 15:35:05 +03:00
diPhantxm
f5d2d0197b update 2025-03-02 15:28:41 +03:00
Kirill
bd280b54f4 Update February 17, 2025 (#146) 2025-03-02 15:11:00 +03:00
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
21 changed files with 2556 additions and 1217 deletions

View File

@@ -1,178 +0,0 @@
# Supported Endpoints
## Ozon attributes and characteristics
- [x] Product category tree
- [x] Category characteristics list
- [x] Characteristics value directory
- [x] Search characteristics value directory
## Uploading and updating products
- [x] Create or update a product
- [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

@@ -200,12 +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
// Method for getting a report on leftover stocks and products movement at Ozon warehouses
func (c Analytics) GetStocksOnWarehouses(ctx context.Context, params *GetStocksOnWarehousesParams) (*GetStocksOnWarehousesResponse, error) {
url := "/v2/analytics/stock_on_warehouses"
@@ -340,3 +337,114 @@ func (c Analytics) Stock(ctx context.Context, params *GetStockManagementParams)
return resp, nil
}
type GetProductQueriesParams struct {
// Date when analytics generation starts
DateFrom string `json:"date_from"`
//Date when analytics generation ends
DateTo string `json:"date_to"`
// Number of page returned in the request
Page int32 `json:"page"`
// Number of items on the pag
PageSize int32 `json:"page_size"`
// List of SKUs—product identifiers in the Ozon system.
// Analytics on requests is returned for them.
// Maximum value is 1,000 SKUs
SKUs []string `json:"skus"`
// Parameter by which products are sorted
SortBy string `json:"sort_by"`
// Sorting direction
SortDir string `json:"sort_dir"`
}
type GetProductQueriesResponse struct {
core.CommonResponse
// Period for which the analytics is generated
AnalyticsPeriod AnalyticsPeriod `json:"analytics_period"`
// Product list
Items []GetProductQueriesItem `json:"items"`
// Number of pages
PageCount int64 `json:"page_count"`
// Total number of queries
Total int64 `json:"total"`
}
type AnalyticsPeriod struct {
// Date when analytics generation starts
DateFrom string `json:"date_from"`
// Date when analytics generation ends
DateTo string `json:"date_to"`
}
type GetProductQueriesItem struct {
// Category name
Category string `json:"category"`
// Currency
Currency string `json:"currency"`
// Sales by queries
GMV float64 `json:"gmv"`
// Product name
Name string `json:"name"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Average product position. Available only with the Premium or Premium Plus subscription, otherwise the field returns empty
Position float64 `json:"position"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Number of customers who searched for your product on Ozon
UniqueSearchUsers int64 `json:"unique_search_users"`
// Number of customers who have seen your product on Ozon.
// Available only with the Premium or Premium Plus subscription,
// otherwise the field returns empty
UniqueViewUsers int64 `json:"unique_view_users"`
// Conversion from product views.
// Available only with the Premium or Premium Plus subscription,
// otherwise the field returns empty
ViewConversion float64 `json:"view_conversion"`
}
// Use the method to get data about your product queries.
// Full analytics is available with the Premium and Premium Plus subscription.
// Without subscription, you can see a part of the metrics.
// The method is similar to the Products in Search → Queries for my product tab in your personal account.
//
// You can view analytics by queries for certain dates.
// To do this, specify the interval in the date_from and date_to fields.
// Data for the last month are available in any interval except for
// three days from the current date because these days the calculation is performed.
// Analytics for dates later than a month ago is available only with
// the Premium and Premium Plus subscription, and only by weeks.
// Specify the date_from parameter in the request
func (c Analytics) GetProductQueries(ctx context.Context, params *GetProductQueriesParams) (*GetProductQueriesResponse, error) {
url := "/v1/analytics/product-queries"
resp := &GetProductQueriesResponse{}
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

@@ -277,3 +277,74 @@ func TestGetStock(t *testing.T) {
}
}
}
func TestGetProductQueries(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetProductQueriesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetProductQueriesParams{
Page: 1,
PageSize: 10,
SKUs: []string{"string"},
},
`{
"analytics_period": {
"date_from": "string",
"date_to": "string"
},
"items": [
{
"category": "string",
"currency": "string",
"gmv": 0,
"name": "string",
"offer_id": "string",
"position": 0,
"sku": 0,
"unique_search_users": 0,
"unique_view_users": 0,
"view_conversion": 0
}
],
"page_count": 0,
"total": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetProductQueriesParams{},
`{
"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().GetProductQueries(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductQueriesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

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

@@ -923,3 +923,13 @@ const (
// 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"
)

View File

@@ -338,6 +338,9 @@ type GetSupplyRequestInfoResponse struct {
}
type SupplyOrder struct {
// true if the supply request can be canceled
CanCancel bool `json:"can_cancel"`
// Date of supply request creation
CreationDate string `json:"creation_date"`
@@ -350,6 +353,18 @@ type SupplyOrder struct {
// Supply warehouse identifier
DropoffWarehouseId int64 `json:"dropoff_warehouse_id"`
// true if the supply request contains Super Economy products
IsEconom bool `json:"is_econom"`
// true if the seller has Super supplies enabled
IsSuperFBO bool `json:"is_super_fbo"`
// true if the supply request is virtual
IsVirtual bool `json:"is_virtual"`
// true if the supply request contains Super products
ProductSuperFBO bool `json:"product_super_fbo"`
// Filter by supply status
State string `json:"state"`
@@ -373,13 +388,33 @@ type Supply struct {
// Supply contents identifier. Used in the /v1/supply-order/bundle method
BundleId string `json:"bundle_id"`
// Filter by supply status
SupplyState string `json:"supply_state"`
// Storage warehouse identifier
StorageWarehouseId int64 `json:"storage_warehouse_id"`
// Product tags in the supply request
SupplyTags []SupplyTag `json:"supply_tags"`
// Supply identifier
Id int64 `json:"supply_id"`
}
type SupplyTag struct {
// true if the supply request contains products certified in the Mercury system
IsEVSDRequired bool `json:"is_evsd_required"`
// true if the supply request contains jewelry
IsJewelry bool `json:"is_jewelry"`
// true if the supply request contains products for which labeling is possible
IsMarkingPossible bool `json:"is_marking_possible"`
// true if the supply request contains products for which labeling is mandatory
IsMarkingRequired bool `json:"is_marking_required"`
}
type SupplyTimeslot struct {
// Reason why you can't select the supply time slot
Reasons []string `json:"can_not_set_reasons"`
@@ -472,64 +507,6 @@ func (c FBO) GetSupplyRequestInfo(ctx context.Context, params *GetSupplyRequestI
return resp, nil
}
type ListProductsInSupplyRequestParams struct {
// Number of the page returned in the query
Page int32 `json:"page"`
// Number of elements on the page
PageSize int32 `json:"page_size"`
// Supply request identifier
SupplyOrderId int64 `json:"supply_order_id"`
}
type ListProductsInSupplyRequestResponse struct {
core.CommonResponse
// Indicates that the response contains not the entire array of supply requests:
// - true — make a new request with a different page and page_size values to get the remaining products;
// - false — the entire array of product was returned in the response
HasNext bool `json:"has_next"`
// Products list
Items []ListProductsInSupplyRequestItem `json:"items"`
// Total number of products in the request
TotalItemsCount int32 `json:"total_items_count"`
}
type ListProductsInSupplyRequestItem struct {
// Link to product image
IconPath string `json:"icon_path"`
// Product name
Name string `json:"name"`
// Product ID
OfferId string `json:"offer_id"`
// Product quantity
Quantity int64 `json:"quantity"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
}
// List of products in the sullpy request
func (c FBO) ListProductsInSupplyRequest(ctx context.Context, params *ListProductsInSupplyRequestParams) (*ListProductsInSupplyRequestResponse, error) {
url := "/v1/supply-order/items"
resp := &ListProductsInSupplyRequestResponse{}
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 GetWarehouseWorkloadResponse struct {
core.CommonResponse
@@ -771,16 +748,16 @@ func (c FBO) GetPass(ctx context.Context, params *GetPassParams) (*GetPassRespon
}
type GetSupplyContentParams struct {
// Identifiers of supply contents. Minimum is 1, maximum is 1000. You can get them using the /v2/supply-order/get method
// Identifiers of supply contents. You can get them using the /v2/supply-order/get method.
BundleIds []string `json:"bundle_ids"`
// true, to sort in ascending order
IsAsc bool `json:"is_asc"`
// Identifier of the last value on the page
// Identifier of the last SKU value on the page.
LastId string `json:"last_id"`
// Number of values on the page. Minimum is 1, maximum is 1000
// Number of products on the page.
Limit int32 `json:"limit"`
// Search query, for example: by name, article code, or SKU
@@ -949,7 +926,7 @@ type SupplyDraftWarehouse struct {
// Warehouse name
Name string `json:"name"`
// Bundle of products that don't come in a shipment
// Bundle of products that don't come in a shipment. Use the parameter in the /v1/supply-order/bundle method to get details.
RestrictedBundleId string `json:"restricted_bundle_id"`
// Warehouse availability
@@ -974,7 +951,7 @@ type SupplyDraftWarehouse struct {
}
type SupplyDraftWarehouseBundle struct {
// Bundle identifier
// Bundle identifier. Use the parameter in the /v1/supply-order/bundle method to get details
Id string `json:"bundle_id"`
// Indicates that the UTD is to be passed
@@ -1138,3 +1115,82 @@ func (c FBO) GetDraftTimeslots(ctx context.Context, params *GetDraftTimeslotsPar
return resp, nil
}
type CancelSuppyOrderParams struct {
// Supply request identifier
OrderId int64 `json:"order_id"`
}
type CancelSuppyOrderResponse struct {
core.CommonResponse
// Operation identifier for canceling the request
OperationId string `json:"operation_id"`
}
// Cancel supply request
func (c FBO) CancelSuppyOrder(ctx context.Context, params *CancelSuppyOrderParams) (*CancelSuppyOrderResponse, error) {
url := "/v1/supply-order/cancel"
resp := &CancelSuppyOrderResponse{}
response, err := c.client.Request(ctx, http.MethodGet, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type StatusCancelledSupplyOrderParams struct {
// Operation identifier for canceling the supply request
OperationId string `json:"operation_id"`
}
type StatusCancelledSupplyOrderResponse struct {
core.CommonResponse
// Reason why the supply request can't be canceled
ErrorReasons []string `json:"error_reasons"`
// Details on supply request cancellation
Result StatusCancelledSupplyOrderResult `json:"result"`
// Cancellation status of the supply request
Status string `json:"status"`
}
type StatusCancelledSupplyOrderResult struct {
// true, if supply request is canceled
IsOrderCancelled bool `json:"is_order_cancelled"`
// List of canceled supplies
Supplies []StatusCancelledSupplyOrderSupply `json:"supplies"`
}
type StatusCancelledSupplyOrderSupply struct {
// Reason why supplies can't be canceled
ErrorReasons []string `json:"error_reasons"`
// true, if supply is canceled
IsSupplyCancelled bool `json:"is_supply_cancelled"`
// Supply identifier
SupplyId int64 `json:"supply_id"`
}
// Get status of canceled supply request
func (c FBO) StatusCancelledSupplyOrder(ctx context.Context, params *StatusCancelledSupplyOrderParams) (*StatusCancelledSupplyOrderResponse, error) {
url := "/v1/supply-order/cancel/status"
resp := &StatusCancelledSupplyOrderResponse{}
response, err := c.client.Request(ctx, http.MethodGet, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}

View File

@@ -361,16 +361,30 @@ func TestGetSupplyRequestInfo(t *testing.T) {
`{
"orders": [
{
"can_cancel": true,
"creation_date": "string",
"creation_flow": "string",
"data_filling_deadline_utc": "2019-08-24T14:15:22Z",
"dropoff_warehouse_id": 0,
"is_econom": true,
"is_super_fbo": true,
"is_virtual": true,
"product_super_fbo": true,
"state": "ORDER_STATE_UNSPECIFIED",
"supplies": [
{
"bundle_id": "string",
"storage_warehouse_id": 0,
"supply_id": 0
"supply_id": 0,
"supply_state": "SUPPLY_STATE_UNSPECIFIED",
"supply_tags": [
{
"is_evsd_required": true,
"is_jewelry": true,
"is_marking_possible": true,
"is_marking_required": true
}
]
}
],
"supply_order_id": 0,
@@ -456,68 +470,6 @@ func TestGetSupplyRequestInfo(t *testing.T) {
}
}
func TestListProductsInSupplyRequest(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListProductsInSupplyRequestParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListProductsInSupplyRequestParams{
Page: 0,
PageSize: 0,
SupplyOrderId: 0,
},
`{
"has_next": true,
"items": [
{
"icon_path": "string",
"name": "string",
"offer_id": "string",
"quantity": 0,
"sku": 0
}
],
"total_items_count": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListProductsInSupplyRequestParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBO().ListProductsInSupplyRequest(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListProductsInSupplyRequestResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetWarehouseWorkload(t *testing.T) {
t.Parallel()
@@ -1270,3 +1222,118 @@ func TestGetDraftTimeslots(t *testing.T) {
}
}
}
func TestCancelSuppyOrder(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *CancelSuppyOrderParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&CancelSuppyOrderParams{
OrderId: 11,
},
`{
"operation_id": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&CancelSuppyOrderParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBO().CancelSuppyOrder(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CancelSuppyOrderResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestStatusCancelledSupplyOrder(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *StatusCancelledSupplyOrderParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&StatusCancelledSupplyOrderParams{
OperationId: "123",
},
`{
"error_reasons": [
"INVALID_ORDER_STATE"
],
"result": {
"is_order_cancelled": true,
"supplies": [
{
"error_reasons": [
"INVALID_SUPPLY_STATE"
],
"is_supply_cancelled": true,
"supply_id": 0
}
]
},
"status": "SUCCESS"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&StatusCancelledSupplyOrderParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBO().StatusCancelledSupplyOrder(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &StatusCancelledSupplyOrderResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

@@ -154,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"`
@@ -766,7 +769,7 @@ type ValidateLabelingCodesExemplar struct {
GTD string `json:"gtd"`
// Mandatory “Chestny ZNAK” labeling
MandatoryMark string `json:"mandatory_mark"`
Marks []SetProductItemsDataProductMark `json:"marks"`
// Product batch registration number
RNPT string `json:"rnpt"`
@@ -775,11 +778,6 @@ type ValidateLabelingCodesExemplar struct {
type ValidateLabelingCodesResponse struct {
core.CommonResponse
// Method result
Result ValidateLabelingCodesResult `json:"result"`
}
type ValidateLabelingCodesResult struct {
// Products list
Products []ValidateLabelingCodesResultProduct `json:"products"`
}
@@ -789,7 +787,7 @@ type ValidateLabelingCodesResultProduct struct {
Error string `json:"error"`
// Product items data
Exemplars []FBSProductExemplar `json:"exemplars"`
Exemplars []ValidateLabelingCodesResultExemplar `json:"exemplars"`
// Product identifier
ProductId int64 `json:"product_id"`
@@ -798,11 +796,43 @@ type ValidateLabelingCodesResultProduct struct {
Valid bool `json:"valid"`
}
type ValidateLabelingCodesResultExemplar struct {
// Product item validation errors
Errors []string `json:"errors"`
// Сustoms cargo declaration (CCD) number
GTD string `json:"gtd"`
// List of Control Identification Marks in one copy
Marks []ValidateLabelingCodesMark `json:"marks"`
// Product batch registration number
RNPT string `json:"rnpt"`
// Check result. true if the labeling codes of all product items meet the requirements
Valid bool `json:"valid"`
}
type ValidateLabelingCodesMark struct {
// Errors that appeared during verification of Control Identification Marks
Errors []string `json:"errors"`
// Labeling code meaning
Mark string `json:"mark"`
// Labeling code type
MarkType string `json:"mark_type"`
// Check result. true if the labeling
// codes of all product items meet the requirements
Valid bool `json:"valid"`
}
// Method for checking whether labeling codes meet the "Chestny ZNAK" system requirements on length and symbols.
//
// If you don't have the customs cargo declaration (CCD) number, you don't have to specify it
func (c FBS) ValidateLabelingCodes(ctx context.Context, params *ValidateLabelingCodesParams) (*ValidateLabelingCodesResponse, error) {
url := "/v4/fbs/posting/product/exemplar/validate"
url := "/v5/fbs/posting/product/exemplar/validate"
resp := &ValidateLabelingCodesResponse{}
@@ -1016,6 +1046,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
@@ -1027,6 +1060,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"`
@@ -1185,39 +1221,23 @@ type ProductDimension struct {
}
type FBSProductExemplar struct {
// Product item validation errors
Errors []string `json:"errors"`
// Item identifier
ExemplarId int64 `json:"exemplar_id"`
// Mandatory “Chestny ZNAK” labeling
MandatoryMark string `json:"mandatory_mark"`
// "Chestny ZNAK" labeling check status
MandatoryMarkCheckStatus MandatoryMarkStatus `json:"mandatory_mark_check_status"`
// "Chestny ZNAK" labeling check error codes
MandatoryMarkErrorCodes []string `json:"mandatory_mark_error_codes"`
// Сustoms cargo declaration (CCD) number
GTD string `json:"gtd"`
// Сustoms cargo declaration (CCD) check status
GTDCheckStatus string `json:"gtd_check_status"`
// Indication that a сustoms cargo declaration (CCD) number hasn't been specified
IsGTDAbsest bool `json:"is_gtd_absent"`
// Сustoms cargo declaration (CCD) check error codes
GTDErrorCodes []string `json:"gtd_error_codes"`
// Product batch registration number
RNPT string `json:"rnpt"`
// Indication that a product batch registration number hasn't been specified
IsRNPTAbsent bool `json:"is_rnpt_absent"`
// Check result.
// `true` if the labeling code of product item meets the requirements
Valid bool `json:"valid"`
}
// Method for getting shipment details by identifier
@@ -1405,38 +1425,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"`
@@ -1521,7 +1509,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"`
}
@@ -1826,7 +1814,7 @@ func (c FBS) GetDropOffPointRestrictions(ctx context.Context, params *GetDropOff
return resp, nil
}
type CheckProductItemsDataParams struct {
type SetProductItemsDataParams struct {
// Quantity of boxes the product is packed in
MultiBoxQuantity int32 `json:"multi_box_qty"`
@@ -1834,20 +1822,26 @@ type CheckProductItemsDataParams struct {
PostingNumber string `json:"posting_number"`
// Product list
Products []CheckProductItemsDataProduct `json:"products"`
Products []SetProductItemsDataProduct `json:"products"`
}
type CheckProductItemsDataProduct struct {
type SetProductItemsDataProduct struct {
// Product items data
Exemplars []CheckProductItemsDataProductExemplar `json:"exemplars"`
Exemplars []SetProductItemsDataProductExemplar `json:"exemplars"`
// Indication that you need to pass the сustoms cargo declaration
// (CCD) number for the product and shipment
IsGTDNeeded bool `json:"is_gtd_needed"`
// Indication that you need to pass the unique identifier of charges of the jewelry
IsJwUINNeeded bool `json:"is_jw_uin_needed"`
// Indication that you need to pass the "Chestny ZNAK" labeling
IsMandatoryMarkNeeded bool `json:"is_mandatory_mark_needed"`
// Indication that you can pass the "Chestny ZNAK" labeling, but it's not mandatory
IsMandatoryMarkPossible bool `json:"is_mandatory_mark_possible"`
// Indication that you need to pass the product batch registration number
IsRNPTNeeded bool `json:"is_rnpt_needed"`
@@ -1858,48 +1852,35 @@ type CheckProductItemsDataProduct struct {
Quantity int32 `json:"quantity"`
}
type CheckProductItemsDataProductExemplar struct {
type SetProductItemsDataProductExemplar struct {
// Item identifier
ExemplarId int64 `json:"exemplar_id"`
// Customs cargo declaration (CCD) number
GTD string `json:"gtd"`
// Сustoms cargo declaration (CCD) check status
GTDCheckStatus string `json:"gtd_check_status"`
// Сustoms cargo declaration (CCD) check error codes
GTDErrorCodes []string `json:"gtd_error_codes"`
// Indication that the customs cargo declaration (CCD) number isn't specified
IsGTDAbsent bool `json:"is_gtd_absent"`
// "Chestny ZNAK" labeling check status
MandatoryMarkCheckStatus MandatoryMarkStatus `json:"mandatory_mark_check_status"`
// "Chestny ZNAK" labeling check error codes
MandatoryMarkErrorCodes []string `json:"mandatory_mark_error_codes"`
// Indication that the product batch registration number isn't specified
IsRNPTAbsent bool `json:"is_rnpt_absent"`
// Mandatory "Chestny ZNAK" labeling
MandatoryMark string `json:"mandatory_mark"`
// Product batch registration number
RNPT string `json:"rnpt"`
// Product batch registration number check status
RNPTCheckStatus string `json:"rnpt_check_status"`
// Product batch registration number check error codes
RNPTErrorCodes []string `json:"rnpt_error_codes"`
// Unique identifier of charges of the jewelry
JWUIN string `json:"jw_uin"`
// Errors that appeared during verification of Control Identification Marks
Marks []SetProductItemsDataProductMark `json:"marks"`
}
type CheckProductItemsDataResponse struct {
type SetProductItemsDataProductMark struct {
// Labeling code meaning
Mark string `json:"mark"`
// Labeling code type
MarkType string `json:"mark_type"`
}
type SetProductItemsDataResponse struct {
core.CommonResponse
// Method result. true if the request was processed successfully
@@ -1908,39 +1889,21 @@ type CheckProductItemsDataResponse struct {
// Asynchronous method:
//
// for checking the availability of product items in the “Chestny ZNAK” labeling system;
// for saving product items data.
// for checking the availability of product items in the “Chestny ZNAK” labeling system;
// for saving product items data.
// To get the checks results, use the /v5/fbs/posting/product/exemplar/status method. To get data about created items, use the /v6/fbs/posting/product/exemplar/create-or-get method.
//
// To get the checks results,
// use the /v4/fbs/posting/product/exemplar/status method.
// To get data about created items,
// use the /v5/fbs/fbs/posting/product/exemplar/create-or-get method.
//
// If necessary, specify the number of the cargo customs declaration
// in the gtd parameter. If it is missing,
// pass the value is_gtd_absent = true.
//
// If you have multiple identical products in a shipment,
// specify one product_id and exemplars array for each product in the shipment.
// If you have multiple identical products in a shipment, specify one product_id and exemplars array for each product in the shipment.
//
// Always pass a complete set of product items data.
//
// For example, you have 10 product items in your system.
// You've passed them for checking and saving.
// Then you added another 60 product items to your system.
// When you pass product items for checking and saving again,
// pass all of them: both old and newly added.
// For example, you have 10 product items in your system. You pass them for checking and saving. Then you add another 60 product items to your system. When you pass product items for checking and saving again, pass all of them: both old and newly added.
//
// Unlike /v4/fbs/posting/product/exemplar/set,
// you can pass more item information in the request.
//
// The 200 response code doesn't guarantee that instance data has been received.
// It indicates that a task for adding the information has been created.
// To check the task status, use the /v4/fbs/posting/product/exemplar/status method.
func (c FBS) CheckProductItemsData(ctx context.Context, params *CheckProductItemsDataParams) (*CheckProductItemsDataResponse, error) {
url := "/v5/fbs/posting/product/exemplar/set"
// The 200 response code doesn't guarantee that instance data has been received. It indicates that a task for adding the information has been created. To check the task status, use the /v5/fbs/posting/product/exemplar/status method.
func (c FBS) SetProductItemsData(ctx context.Context, params *SetProductItemsDataParams) (*SetProductItemsDataResponse, error) {
url := "/v6/fbs/posting/product/exemplar/set"
resp := &CheckProductItemsDataResponse{}
resp := &SetProductItemsDataResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
@@ -1963,19 +1926,71 @@ type GetProductItemsCheckStatusesResponse struct {
PostingNumber string `json:"posting_number"`
// Products list
Products []CheckProductItemsDataProduct `json:"products"`
Products []GetProductItemsCheckStatusProduct `json:"products"`
// Product items check statuses and order collection availability:
// - ship_available — order collection is available,
// - ship_not_available — order collection is unavailable,
// - validation_in_process — product items validation is in progress
// Product items check statuses and order collection availability
Status string `json:"status"`
}
// Method for getting check statuses of product items that were passed in the `/fbs/posting/product/exemplar/set` method.
type GetProductItemsCheckStatusProduct struct {
// Product identifier
ProductId int64 `json:"product_id"`
// Product items data
Exemplars []GetProductItemsCheckStatusExemplar `json:"exemplars"`
}
type GetProductItemsCheckStatusExemplar struct {
// Item identifier
ExemplarId int64 `json:"exemplar_id"`
// Customs cargo declaration (CCD) number
GTD string `json:"gtd"`
// Сustoms cargo declaration (CCD) check status
GTDCheckStatus string `json:"gtd_check_status"`
// Сustoms cargo declaration (CCD) check error codes
GTDErrorCodes []string `json:"gtd_error_codes"`
// Indication that the customs cargo declaration (CCD) number isn't specified
IsGTDAbsent bool `json:"is_gtd_absent"`
// Indication that the product batch registration number isn't specified
IsRNPTAbsent bool `json:"is_rnpt_absent"`
// List of Control Identification Marks in one copy
Marks []GetProductItemsCheckStatusMark `json:"marks"`
// Product batch registration number
RNPT string `json:"rnpt"`
// Product batch registration number check status
RNPTCheckStatus string `json:"rnpt_check_status"`
// Product batch registration number check error codes
RNPTErrorCodes []string `json:"rnpt_error_codes"`
}
type GetProductItemsCheckStatusMark struct {
// Check status
CheckStatus string `json:"check_status"`
// Errors that appeared during verification of Control Identification Marks
ErrorCodes []string `json:"error_codes"`
// Labeling code meaning
Mark string `json:"mark"`
// Labeling code type
MarkType string `json:"mark_type"`
}
// Method for getting product items addition statuses
// that were passed in the /v6/fbs/posting/product/exemplar/set method.
// Also returns data on these product items.
func (c FBS) GetProductItemsCheckStatuses(ctx context.Context, params *GetProductItemsCheckStatusesParams) (*GetProductItemsCheckStatusesResponse, error) {
url := "/v4/fbs/posting/product/exemplar/status"
url := "/v5/fbs/posting/product/exemplar/status"
resp := &GetProductItemsCheckStatusesResponse{}
@@ -2961,11 +2976,37 @@ type CreateOrGetProductExemplarResponse struct {
Products []CheckProductItemsDataProduct `json:"products"`
}
type CheckProductItemsDataProduct struct {
// Data about items
Exemplars []SetProductItemsDataProductExemplar `json:"exemplars"`
// Indication that you need to pass the сustoms cargo declaration (CCD) number for the product and shipment
IsGTDNeeded bool `json:"is_gtd_needed"`
// Indication that you need to pass the unique identifier of charges of the jewelry
IsJwUINNeeded bool `json:"is_jw_uin_needed"`
// Indication that you need to pass the "Chestny ZNAK" labeling
IsMandatoryMarkNeeded bool `json:"is_mandatory_mark_needed"`
// Indication that you can pass the "Chestny ZNAK" labeling, but it's not mandatory
IsMandatoryMarkPossible bool `json:"is_mandatory_mark_possible"`
// Indication that you need to pass the product batch registration number
IsRNPTNeeded bool `json:"is_rnpt_needed"`
// Product ID
ProductId int64 `json:"product_id"`
// Items quantity
Quantity int32 `json:"quantity"`
}
// Method returns the created items data passed in the `/v5/fbs/posting/product/exemplar/set` method.
//
// Use this method to get the `exemplar_id`
func (c FBS) CreateOrGetProductExemplar(ctx context.Context, params *CreateOrGetProductExemplarParams) (*CreateOrGetProductExemplarResponse, error) {
url := "/v5/fbs/posting/product/exemplar/create-or-get"
url := "/v6/fbs/posting/product/exemplar/create-or-get"
resp := &CreateOrGetProductExemplarResponse{}
@@ -3234,3 +3275,128 @@ func (c FBS) ListUnpaidProducts(ctx context.Context, params *ListUnpaidProductsP
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
}
type UpdateProductsDataParams struct {
// Shipment number
PostingNumber string `json:"posting_number"`
}
type UpdateProductsDataResponse struct {
core.CommonResponse
}
// Use the method after passing item data using the
// /v6/fbs/posting/product/exemplar/set method to
// save updated item data for shipments in the “Awaiting shipment” status
func (c FBS) UpdateProductsData(ctx context.Context, params *UpdateProductsDataParams) (*UpdateProductsDataResponse, error) {
url := "/v1/fbs/posting/product/exemplar/update"
resp := &UpdateProductsDataResponse{}
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

@@ -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,
@@ -238,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": {
@@ -301,7 +303,7 @@ func TestGetFBSShipmentsList(t *testing.T) {
],
"has_next": true
}
}`,
}`,
},
// Test No Client-Id or Api-Key
{
@@ -421,8 +423,7 @@ func TestValidateLabelingCodes(t *testing.T) {
{
Exemplars: []ValidateLabelingCodesExemplar{
{
GTD: "",
MandatoryMark: "010290000151642731tVMohkbfFgunB",
GTD: "",
},
},
ProductId: 476925391,
@@ -430,23 +431,33 @@ func TestValidateLabelingCodes(t *testing.T) {
},
},
`{
"result": {
"products": [
{
"product_id": 476925391,
"exemplars": [
{
"mandatory_mark": "010290000151642731tVMohkbfFgunB",
"gtd": "",
"valid": true,
"errors": []
}
],
"valid": true,
"error": ""
}
]
}
"products": [
{
"error": "string",
"exemplars": [
{
"errors": [
"string"
],
"gtd": "string",
"marks": [
{
"errors": [
"string"
],
"mark": "string",
"mark_type": "string",
"valid": true
}
],
"rnpt": "string",
"valid": true
}
],
"product_id": 476925391,
"valid": true
}
]
}`,
},
// Test No Client-Id or Api-Key
@@ -478,11 +489,11 @@ func TestValidateLabelingCodes(t *testing.T) {
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result.Products) != len(test.params.Products) {
if len(resp.Products) != len(test.params.Products) {
t.Errorf("Length of products in request and response are not equal")
}
if len(resp.Result.Products) > 0 {
if resp.Result.Products[0].ProductId != test.params.Products[0].ProductId {
if len(resp.Products) > 0 {
if resp.Products[0].ProductId != test.params.Products[0].ProductId {
t.Errorf("Product ids in request and response are not equal")
}
}
@@ -591,8 +602,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 Логистика самостоятельно, Москва",
@@ -878,62 +891,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()
@@ -1432,52 +1389,52 @@ func TestGetDropOffPointRestrictions(t *testing.T) {
}
}
func TestCheckProductItemsData(t *testing.T) {
func TestSetProductItemsData(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *CheckProductItemsDataParams
params *SetProductItemsDataParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&CheckProductItemsDataParams{
&SetProductItemsDataParams{
MultiBoxQuantity: 0,
PostingNumber: "1234",
Products: []CheckProductItemsDataProduct{
Products: []SetProductItemsDataProduct{
{
Exemplars: []CheckProductItemsDataProductExemplar{
Exemplars: []SetProductItemsDataProductExemplar{
{
ExemplarId: 1,
GTD: "string",
IsGTDAbsent: true,
IsRNPTAbsent: true,
MandatoryMark: "string",
RNPT: "string",
JWUIN: "string",
ExemplarId: 1,
GTD: "string",
IsGTDAbsent: true,
RNPT: "string",
},
},
IsGTDNeeded: true,
IsMandatoryMarkNeeded: true,
IsRNPTNeeded: true,
ProductId: 22,
Quantity: 11,
ProductId: 22,
},
},
},
`{
"result": true
"code": 0,
"details": [
{
"typeUrl": "string",
"value": "string"
}
],
"message": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&CheckProductItemsDataParams{},
&SetProductItemsDataParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
@@ -1489,13 +1446,13 @@ func TestCheckProductItemsData(t *testing.T) {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().CheckProductItemsData(ctx, test.params)
resp, err := c.FBS().SetProductItemsData(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CheckProductItemsDataResponse{})
compareJsonResponse(t, test.response, &SetProductItemsDataResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
@@ -1523,21 +1480,37 @@ func TestGetProductItemsCheckStatuses(t *testing.T) {
"posting_number": "23281294-0063-2",
"products": [
{
"product_id": 476925391,
"exemplars": [
{
"mandatory_mark": "010290000151642731tVMohkbfFgunB",
"gtd": "",
"exemplar_id": 0,
"gtd": "string",
"gtd_check_status": "string",
"gtd_error_codes": [
"string"
],
"is_gtd_absent": true,
"mandatory_mark_check_status": "passed",
"mandatory_mark_error_codes": [],
"gtd_check_status": "passed",
"gtd_error_codes": []
"is_rnpt_absent": true,
"marks": [
{
"check_status": "string",
"error_codes": [
"string"
],
"mark": "string",
"mark_type": "string"
}
],
"rnpt": "string",
"rnpt_check_status": "string",
"rnpt_error_codes": [
"string"
]
}
]
],
"product_id": 123
}
],
"status": "ship_available"
"status": "string"
}`,
},
// Test No Client-Id or Api-Key
@@ -1578,8 +1551,8 @@ func TestGetProductItemsCheckStatuses(t *testing.T) {
if resp.Products[0].ProductId == 0 {
t.Errorf("Product id cannot be 0")
}
if len(resp.Products[0].Exemplars) > 0 {
if resp.Products[0].Exemplars[0].MandatoryMark == "" {
if len(resp.Products[0].Exemplars) > 0 && len(resp.Products[0].Exemplars[0].Marks) > 0 {
if resp.Products[0].Exemplars[0].Marks[0].Mark == "" {
t.Errorf("Mandatory mark cannot be empty")
}
}
@@ -2929,13 +2902,19 @@ func TestCreateOrGetProductExemplar(t *testing.T) {
"gtd": "string",
"is_gtd_absent": true,
"is_rnpt_absent": true,
"mandatory_mark": "string",
"rnpt": "string",
"jw_uin": "string"
"marks": [
{
"mark": "string",
"mark_type": "string"
}
],
"rnpt": "string"
}
],
"is_gtd_needed": true,
"is_jw_uin_needed": true,
"is_mandatory_mark_needed": true,
"is_mandatory_mark_possible": true,
"is_rnpt_needed": true,
"product_id": 0,
"quantity": 0
@@ -3312,3 +3291,226 @@ func TestListUnpaidProducts(t *testing.T) {
}
}
}
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)
}
}
}
func TestUpdateProductsData(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *UpdateProductsDataParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&UpdateProductsDataParams{
PostingNumber: "string",
},
`{
"code": 0,
"details": [
{
"typeUrl": "string",
"value": "string"
}
],
"message": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&UpdateProductsDataParams{},
`{
"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().UpdateProductsData(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UpdateProductsDataResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

@@ -152,7 +152,7 @@ type ReturnCommission struct {
//
// 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{}
@@ -305,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"`
}
@@ -362,11 +362,7 @@ type ListTransactionsResultOperationItem struct {
}
type ListTransactionsResultOperationPosting struct {
// Delivery scheme:
// - FBO — delivery to Ozon warehouse
// - FBS — delivery from seller's warehouse
// - RFBS — delivery service of seller's choice
// - Crossborder — delivery from abroad
// Delivery scheme
DeliverySchema string `json:"delivery_schema"`
// Date the product was accepted for processing

View File

@@ -44,6 +44,7 @@ type Client struct {
passes *Passes
clusters *Clusters
quants *Quants
reviews *Reviews
}
func (c Client) Analytics() *Analytics {
@@ -134,6 +135,10 @@ 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 {
@@ -200,6 +205,7 @@ func NewClient(opts ...ClientOption) *Client {
passes: &Passes{client: coreClient},
clusters: &Clusters{client: coreClient},
quants: &Quants{client: coreClient},
reviews: &Reviews{client: coreClient},
}
}
@@ -230,5 +236,6 @@ func NewMockClient(handler http.HandlerFunc) *Client {
passes: &Passes{client: coreClient},
clusters: &Clusters{client: coreClient},
quants: &Quants{client: coreClient},
reviews: &Reviews{client: coreClient},
}
}

View File

@@ -13,13 +13,11 @@ type Products struct {
}
type GetStocksInfoParams struct {
// Identifier of the last value on the page. Leave this field blank in the first request.
//
// To get the next values, specify last_id from the response of the previous request.
LastId string `json:"last_id"`
// Cursor for the next data sample
Cursor string `json:"cursor"`
// Number of values per page. Minimum is 1, maximum is 1000
Limit int64 `json:"limit"`
// Limit on number of entries in a reply. Default value is 1000. Maximum value is 1000
Limit int32 `json:"limit"`
// Filter by product
Filter GetStocksInfoFilter `json:"filter"`
@@ -34,20 +32,24 @@ type GetStocksInfoFilter struct {
// Filter by product visibility
Visibility string `json:"visibility,omitempty"`
// Products at the “Economy” tariff
WithQuant GetStocksInfoFilterWithQuant `json:"with_quant"`
}
type GetStocksInfoFilterWithQuant struct {
// Active economy products
Created bool `json:"created"`
// Economy products in all statuses
Exists bool `json:"exists"`
}
type GetStocksInfoResponse struct {
core.CommonResponse
// Method Result
Result GetStocksInfoResult `json:"result"`
}
type GetStocksInfoResult struct {
// Identifier of the last value on the page
//
// To get the next values, specify the recieved value in the next request in the last_id parameter
LastId string `json:"last_id"`
// Cursor for the next data sample
Cursor string `json:"cursor"`
// The number of unique products for which information about stocks is displayed
Total int32 `json:"total"`
@@ -76,6 +78,12 @@ type GetStocksInfoResultItemStock struct {
// Warehouse type
Type string `json:"type" default:"ALL"`
// Packaging type
ShipmentType string `json:"shipment_type"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
}
// Returns information about the quantity of products in stock:
@@ -84,7 +92,7 @@ type GetStocksInfoResultItemStock struct {
//
// * how many are reserved by customers.
func (c Products) GetStocksInfo(ctx context.Context, params *GetStocksInfoParams) (*GetStocksInfoResponse, error) {
url := "/v3/product/info/stocks"
url := "/v4/product/info/stocks"
resp := &GetStocksInfoResponse{}
@@ -97,28 +105,7 @@ func (c Products) GetStocksInfo(ctx context.Context, params *GetStocksInfoParams
return resp, nil
}
type GetProductDetailsParams struct {
// Product identifier in the seller's system
OfferId string `json:"offer_id,omitempty"`
// Product identifier
ProductId int64 `json:"product_id,omitempty"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku,omitempty"`
}
type GetProductDetailsResponse struct {
core.CommonResponse
// Request results
Result ProductDetails `json:"result"`
}
type ProductDetails struct {
// Barcode
Barcode string `json:"barcode"`
// All product barcodes
Barcodes []string `json:"barcodes"`
@@ -130,11 +117,20 @@ type ProductDetails struct {
// Category identifier
DescriptionCategoryId int64 `json:"description_category_id"`
// Markdown product stocks at the Ozon warehouse
DiscountedFBOStocks int32 `json:"discounted_fbo_stocks"`
// Details on errors when creating or validating a product
Errors []ProductDetailsError `json:"errors"`
// Indication that the product has similar markdown products at the Ozon warehouse
HasDiscountedFBOItem bool `json:"has_discounted_fbo_item"`
// Product type identifier
TypeId int64 `json:"type_id"`
// Marketing color
ColorImage string `json:"color_image"`
ColorImage []string `json:"color_image"`
// Commission fees details
Commissions []ProductDetailCommission `json:"commissions"`
@@ -158,7 +154,7 @@ type ProductDetails struct {
Images []string `json:"images"`
// Main product image
PrimaryImage string `json:"primary_image"`
PrimaryImage []string `json:"primary_image"`
// Array of 360 images
Images360 []string `json:"images360"`
@@ -252,20 +248,112 @@ type ProductDetails struct {
// 'true' if the item is archived automatically.
IsArchivedAuto bool `json:"is_autoarchived"`
// Product status details
Statuses ProductDetailsStatus `json:"statuses"`
// Product model details
ModelInfo ProductDetailsModelInfo `json:"model_info"`
// Indication of a super product
IsSuper bool `json:"is_super"`
}
type ProductDetailsError struct {
// Characteristic identifier
AttributeId int64 `json:"attribute_id"`
// Error code
Code string `json:"code"`
// Field in which the error occurred
Field string `json:"field"`
// Error level description
Level string `json:"level"`
// Status of the product with the error
State string `json:"state"`
// Error description
Texts ProductDetailsErrorText `json:"texts"`
}
type ProductDetailsErrorText struct {
// Attribute name
AttributeName string `json:"attribute_name"`
// Error description
Description string `json:"description"`
// Error code in the Ozon system
HintCode string `json:"hint_code"`
// Error message
Message string `json:"message"`
// Short description of the error
ShortDescription string `json:"short_description"`
// Parameters in which the error occurred
Params []NameValue `json:"params"`
}
type NameValue struct {
Name string `json:"name"`
Value string `json:"value"`
}
type ProductDetailsStatus struct {
// true, if the product is created correctly
IsCreated bool `json:"is_created"`
// Moderation status
ModerateStatus string `json:"moderate_status"`
// Product status
Status string `json:"status"`
// Product status description
Description string `json:"status_description"`
// Status of the product where the error occurred
Failed string `json:"status_failed"`
// Product status name
Name string `json:"status_name"`
// Status description
Tooltip string `json:"status_tooltip"`
// Time of the last status change
UpdatedAt time.Time `json:"status_updated_at"`
// Validation status
ValidationStatus string `json:"validation_status"`
}
type ProductDetailsModelInfo struct {
// Number of products in the response
Count int64 `json:"count"`
// Identifier of the product model
ModelId int64 `json:"model_id"`
}
type ProductDetailCommission struct {
// Delivery cost
DeliveryAmount float64 `json:"deliveryAmount"`
DeliveryAmount float64 `json:"delivery_amount"`
// Commission percentage
Percent float64 `json:"percent"`
// Return cost
ReturnAmount float64 `json:"returnAmount"`
ReturnAmount float64 `json:"return_amount"`
// Sale scheme
SaleSchema string `json:"saleSchema"`
SaleSchema string `json:"sale_schema"`
// Commission fee amount
Value float64 `json:"value"`
@@ -278,8 +366,8 @@ type ProductDetailPriceIndex struct {
// Competitors' product price on Ozon
OzonIndexData ProductDetailPriceIndexOzon `json:"ozon_index_data"`
// Resulting price index of the product
PriceIndex string `json:"price_index"`
// Types of price index
ColorIndex string `json:"color_index"`
// Price of your product on other marketplaces
SelfMarketplaceIndexData ProductDetailPriceIndexSelfMarketplace `json:"self_marketplaces_index_data"`
@@ -357,25 +445,42 @@ type ProductDetailStatus struct {
}
type ProductDetailSource struct {
// Indication that the source is taken into account when calculating the market value
IsEnabled bool `json:"is_enabled"`
// Product creation date
CreatedAt time.Time `json:"created_at"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Link to the source
Source string `json:"source"`
// Package type
ShipmentType string `json:"shipment_type"`
// List of MOQs with products
QuantCode string `json:"quant_code"`
}
type ProductDetailStock struct {
// Supply expected
Coming int32 `json:"coming"`
// true, if there are stocks at the warehouses
HasStock bool `json:"has_stock"`
// Status of product stocks
Stocks []ProductDetailStockStock `json:"stocks"`
}
type ProductDetailStockStock struct {
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Currently at the warehouse
Present int32 `json:"present"`
// Reserved
Reserved int32 `json:"reserved"`
// Sales scheme
Source string `json:"source"`
}
type ProductDetailVisibilityDetails struct {
@@ -430,24 +535,6 @@ type GetProductDetailsResponseItemError struct {
OptionalDescriptionElements map[string]string `json:"optional_description_elements"`
}
// Get product details
//
// Check a minimum product price with all promotions applied in your personal account.
// The min_price parameter from the method response is in development and returns 0
func (c Products) GetProductDetails(ctx context.Context, params *GetProductDetailsParams) (*GetProductDetailsResponse, error) {
url := "/v2/product/info"
resp := &GetProductDetailsResponse{}
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 UpdateStocksParams struct {
// Stock details
Stocks []UpdateStocksStock `json:"stocks"`
@@ -662,6 +749,12 @@ type UpdatePricesPrice struct {
// By default, the passed value is RUB, Russian ruble
CurrencyCode string `json:"currency_code"`
// true, if Ozon takes into account
// the minimum price when creating promotions.
// If you don't pass anything,
// the status of the price accounting remains the same
MinPriceForAutoActionsEnabled bool `json:"min_price_for_auto_actions_enabled"`
// Minimum product price with all promotions applied
MinPrice string `json:"min_price"`
@@ -708,6 +801,9 @@ type UpdatePricesPrice struct {
//
// If the regular and economy products have different article codes, don't specify the parameter.
QuantSize int64 `json:"quant_size"`
// VAT rate for the product
VAT VAT `json:"vat"`
}
type UpdatePricesResponse struct {
@@ -850,11 +946,8 @@ type CreateOrUpdateProductItem struct {
// you can leave out the attributes attribute if it has the `id:8229` parameter
TypeId int64 `json:"type_id"`
// VAT rate for the product:
// - 0 — not subject to VAT,
// - 0.1 — 10%,
// - 0.2 — 20%
VAT string `json:"vat"`
// VAT rate for the product
VAT VAT `json:"vat"`
// Product weight with the package. The limit value is 1000 kilograms or a corresponding converted value in other measurement units
Weight int32 `json:"weight"`
@@ -934,12 +1027,14 @@ func (c Products) CreateOrUpdateProduct(ctx context.Context, params *CreateOrUpd
return resp, nil
}
// GetListOfProductsParams reflects the new /v3/product/list request body.
// Filter, LastId, and Limit are used the same way as in the v2 version, plus we keep
// offer_id and product_id for backward compatibility if needed.
type GetListOfProductsParams struct {
// Filter by product
Filter GetListOfProductsFilter `json:"filter"`
// Identifier of the last value on the page. Leave this field blank in the first request.
//
// To get the next values, specify last_id from the response of the previous request
LastId string `json:"last_id"`
@@ -947,51 +1042,82 @@ type GetListOfProductsParams struct {
Limit int64 `json:"limit"`
}
// GetListOfProductsFilter holds filtering options for /v3/product/list.
type GetListOfProductsFilter struct {
// Filter by the offer_id parameter. You can pass a list of values in this parameter
OfferId []string `json:"offer_id"`
OfferId []string `json:"offer_id,omitempty"`
// Filter by the product_id parameter. You can pass a list of values in this parameter
ProductId []int64 `json:"product_id"`
ProductId []int64 `json:"product_id,omitempty"`
// Filter by product visibility
Visibility string `json:"visibility"`
Visibility string `json:"visibility,omitempty"`
}
// GetListOfProductsResponse describes the /v3/product/list response body.
type GetListOfProductsResponse struct {
core.CommonResponse
// Result
// Result object containing list of products and pagination info
Result GetListOfProductsResult `json:"result"`
}
// GetListOfProductsResult contains the products, total count, and last_id.
type GetListOfProductsResult struct {
// Products list
Items []GetListOfProductsResultItem `json:"items"`
// Identifier of the last value on the page.
//
// To get the next values, specify the recieved value in the next request in the last_id parameter
LastId string `json:"last_id"`
// Total number of products
Total int32 `json:"total"`
// Identifier of the last value on the page.
// To get the next values, specify the received value in the next request in the last_id parameter
LastId string `json:"last_id"`
}
// GetListOfProductsResultItem describes a single product item in the /v3/product/list response.
type GetListOfProductsResultItem struct {
// Product ID
ProductId int64 `json:"product_id"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product ID
ProductId int64 `json:"product_id"`
// Flag indicating presence of FBO stocks
HasFboStocks bool `json:"has_fbo_stocks"`
// Flag indicating presence of FBS stocks
HasFbsStocks bool `json:"has_fbs_stocks"`
// Product archive status
Archived bool `json:"archived"`
// Whether the product has an active discount
IsDiscounted bool `json:"is_discounted"`
// List of quants with detailed stock information
Quants []ProductQuant `json:"quants"`
}
// ProductQuant describes a single quant entry with warehouse, available quantity, and reserved.
type ProductQuant struct {
// Warehouse ID where the stock is located
WarehouseId int64 `json:"warehouse_id"`
// Quantity available in the warehouse
Quantity int64 `json:"quantity"`
// Quantity reserved in the warehouse
Reserved int64 `json:"reserved"`
}
// GetListOfProducts calls the new /v3/product/list endpoint.
// When using the filter by offer_id or product_id identifier, other parameters are not required.
// Only one identifiers group can be used at a time, not more than 1000 products.
//
// If you do not use identifiers for display, specify limit and last_id in subsequent requests.
func (c Products) GetListOfProducts(ctx context.Context, params *GetListOfProductsParams) (*GetListOfProductsResponse, error) {
url := "/v2/product/list"
url := "/v3/product/list"
resp := &GetListOfProductsResponse{}
@@ -1179,11 +1305,8 @@ type CreateProductsByOzonIDItem struct {
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// VAT rate for the product:
// - 0 — not subject to VAT,
// - 0.1 — 10%,
// - 0.2 — 20%
VAT string `json:"vat"`
// VAT rate for the product
VAT VAT `json:"vat"`
}
type CreateProductByOzonIDResponse struct {
@@ -1264,9 +1387,9 @@ type ProductInfoResultPicture struct {
// Image uploading status.
//
// If the `/v1/product/pictures/import` method was called, the response will always be imported—image not processed.
// To see the final status, call the `/v1/product/pictures/info` method after about 10 seconds.
// To see the final status, call the `/v2/product/pictures/info` method after about 10 seconds.
//
// If you called the `/v1/product/pictures/info` method, one of the statuses will appear:
// If you called the `/v2/product/pictures/info` method, one of the statuses will appear:
// - uploaded — image uploaded;
// - pending — image was not uploaded
State string `json:"state"`
@@ -1313,11 +1436,35 @@ type CheckImageUploadingStatusParams struct {
ProductId []int64 `json:"product_id"`
}
// Check products images uploading status
func (c Products) CheckImageUploadingStatus(ctx context.Context, params *CheckImageUploadingStatusParams) (*ProductInfoResponse, error) {
url := "/v1/product/pictures/info"
type CheckImageUploadingStatusResponse struct {
core.CommonResponse
resp := &ProductInfoResponse{}
// Product images
Items []CheckImageUploadingStatusItem `json:"items"`
}
type CheckImageUploadingStatusItem struct {
// Product identifier
ProductId int64 `json:"product_id"`
// Main image link
PrimaryPhoto []string `json:"primary_photo"`
// Links to product photos
Photo []string `json:"photo"`
// Links to uploaded color samples
ColorPhoto []string `json:"color_photo"`
// 360 images links
Photo360 []string `json:"photo_360"`
}
// Check products images uploading status
func (c Products) CheckImageUploadingStatus(ctx context.Context, params *CheckImageUploadingStatusParams) (*CheckImageUploadingStatusResponse, error) {
url := "/v2/product/pictures/info"
resp := &CheckImageUploadingStatusResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
@@ -1342,11 +1489,6 @@ type ListProductsByIDsParams struct {
type ListProductsByIDsResponse struct {
core.CommonResponse
// Request results
Result ListProductsByIDsResult `json:"result"`
}
type ListProductsByIDsResult struct {
// Data array
Items []ProductDetails `json:"items"`
}
@@ -1357,7 +1499,7 @@ type ListProductsByIDsResult struct {
//
// For each shipment in the items array the fields match the ones recieved in the /v2/product/info method
func (c Products) ListProductsByIDs(ctx context.Context, params *ListProductsByIDsParams) (*ListProductsByIDsResponse, error) {
url := "/v2/product/info/list"
url := "/v3/product/info/list"
resp := &ListProductsByIDsResponse{}
@@ -1422,6 +1564,9 @@ type GetDescriptionOfProductResult struct {
// Barcode
Barcode string `json:"barcode"`
// All product's barcodes
Barcodes []string `json:"barcodes"`
// Category identifier
DescriptionCategoryId int64 `json:"description_category_id"`
@@ -1461,6 +1606,12 @@ type GetDescriptionOfProductResult struct {
// Array of PDF files
PDFList []GetDescriptionOfProductResultPDF `json:"pdf_list"`
// Link to the main product image
PrimaryImage string `json:"primary_image"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Product type identifier
TypeId int64 `json:"type_id"`
@@ -1545,7 +1696,7 @@ type GetDescriptionOfProductResultPDF struct {
// Returns a product characteristics description by product identifier. You can search for the product by `offer_id` or `product_id`
func (c Products) GetDescriptionOfProduct(ctx context.Context, params *GetDescriptionOfProductParams) (*GetDescriptionOfProductResponse, error) {
url := "/v3/products/info/attributes"
url := "/v4/product/info/attributes"
resp := &GetDescriptionOfProductResponse{}
@@ -1950,10 +2101,8 @@ type GetProductPriceInfoParams struct {
// Filter by product
Filter GetProductPriceInfoFilter `json:"filter"`
// Identifier of the last value on page.
//
// To get the next values, specify the recieved value in the next request in the `last_id` parameter
LastId string `json:"last_id"`
// Cursor for the next data sample
Cursor string `json:"cursor"`
// Number of values per page. Minimum is 1, maximum is 1000
Limit int32 `json:"limit"`
@@ -1973,7 +2122,6 @@ type GetProductPriceInfoFilter struct {
type GetProductPriceInfoResponse struct {
core.CommonResponse
// Result
Result GetProductPriceInfoResult `json:"result"`
}
@@ -1981,10 +2129,8 @@ type GetProductPriceInfoResult struct {
// Products list
Items []GetProductPriceInfoResultItem `json:"items"`
// Identifier of the last value on page. Leave this field blank in the first request.
//
// To get the next values, specify last_id from the response of the previous request
LastId string `json:"last_id"`
// Cursor for the next data sample
Cursor string `json:"cursor"`
// Products number in the list
Total int32 `json:"total"`
@@ -2006,11 +2152,6 @@ type GetProductPriceInfoResultItem struct {
// Product price
Price GetProductPriceInfoResultItemPrice `json:"price"`
// Deprected: price index
//
// Use PriceIndexes instead
PriceIndex string `json:"price_index"`
// Product price indexes
PriceIndexes GetProductPriceInfoResultItemPriceIndexes `json:"price_indexes"`
@@ -2098,11 +2239,11 @@ type GetProductPriceInfoResultItemMarketingActionsAction struct {
// Date and time when the seller's promotion ends
DateTo time.Time `json:"date_to"`
// Discount on the seller's promotion
DiscountValue string `json:"discount_value"`
// Promotion name
Title string `json:"title"`
// Discount on the seller's promotion
Value float64 `json:"value"`
}
type GetProductPriceInfoResultItemPrice struct {
@@ -2113,72 +2254,50 @@ type GetProductPriceInfoResultItemPrice struct {
CurrencyCode string `json:"currency_code"`
// Product price including all promotion discounts. This value will be indicated on the Ozon storefront
MarketingPrice string `json:"marketing_price"`
MarketingPrice float64 `json:"marketing_price"`
// Product price with seller's promotions applied
MarketingSellerPrice string `json:"marketing_seller_price"`
MarketingSellerPrice float64 `json:"marketing_seller_price"`
// Minimum price for similar products on Ozon
MinOzonPrice string `json:"min_ozon_price"`
MinOzonPrice float64 `json:"min_ozon_price"`
// Minimum product price with all promotions applied
MinPrice string `json:"min_price"`
MinPrice float64 `json:"min_price"`
// Price before discounts. Displayed strikethrough on the product description page
OldPrice string `json:"old_price"`
OldPrice float64 `json:"old_price"`
// Product price including discounts. This value is shown on the product description page
Price string `json:"price"`
Price float64 `json:"price"`
// Retailer price
RetailPrice string `json:"retail_price"`
RetailPrice float64 `json:"retail_price"`
// Product VAT rate
VAT string `json:"vat"`
VAT float64 `json:"vat"`
}
type GetProductPriceInfoResultItemPriceIndexes struct {
// Resulting price index of the product
ColorIndex string `json:"color_index"`
// Competitors' product price on other marketplaces
ExternalIndexData GetProductPriceInfoResultItemPriceIndexesExternal `json:"external_index_data"`
ExternalIndexData GetProductPriceInfoResultItemPriceIndexesValue `json:"external_index_data"`
// Competitors' product price on Ozon
OzonIndexData GetProductPriceInfoResultItemPriceIndexesOzon `json:"ozon_index_data"`
// Resulting price index of the product
PriceIndex string `json:"price_index"`
OzonIndexData GetProductPriceInfoResultItemPriceIndexesValue `json:"ozon_index_data"`
// Price of your product on other marketplaces
SelfMarketplaceIndexData GetProductPriceInfoResultItemPriceIndexesSelfMarketplace `json:"self_marketplaces_index_data"`
SelfMarketplaceIndexData GetProductPriceInfoResultItemPriceIndexesValue `json:"self_marketplaces_index_data"`
}
type GetProductPriceInfoResultItemPriceIndexesExternal struct {
// Minimum competitors' product price on other marketplaces
MinimalPrice string `json:"minimal_price"`
// Price currency
MinimalPriceCurrency string `json:"minimal_price_currency"`
// Price index value
PriceIndexValue float64 `json:"price_index_value"`
}
type GetProductPriceInfoResultItemPriceIndexesOzon struct {
// Minimum competitors' product price on Ozon
MinimalPrice string `json:"minimal_price"`
// Price currency
MinimalPriceCurrency string `json:"minimal_price_currency"`
// Price index value
PriceIndexValue float64 `json:"price_index_value"`
}
type GetProductPriceInfoResultItemPriceIndexesSelfMarketplace struct {
type GetProductPriceInfoResultItemPriceIndexesValue struct {
// Minimum price of your product on other marketplaces
MinimalPrice string `json:"minimal_price"`
MinimalPrice float64 `json:"min_price"`
// Price currency
MinimalPriceCurrency string `json:"minimal_price_currency"`
MinimalPriceCurrency string `json:"min_price_currency"`
// Price index value
PriceIndexValue float64 `json:"price_index_value"`
@@ -2190,7 +2309,7 @@ type GetProductPriceInfoResultItemPriceIndexesSelfMarketplace struct {
// The `fbo_direct_flow_trans_max_amount` and `fbo_direct_flow_trans_min_amount` parameters
// from the method response are in development and return 0
func (c Products) GetProductPriceInfo(ctx context.Context, params *GetProductPriceInfoParams) (*GetProductPriceInfoResponse, error) {
url := "/v4/product/info/prices"
url := "/v5/product/info/prices"
resp := &GetProductPriceInfoResponse{}
@@ -2642,3 +2761,63 @@ func (c Products) ListEconomy(ctx context.Context, params *ListEconomyProductsPa
return resp, nil
}
type UpdatePriceRelevanceTimerParams struct {
// List of product identifiers
ProductIds []string `json:"product_ids"`
}
type UpdatePriceRelevanceTimerResponse struct {
core.CommonResponse
}
func (c Products) UpdatePriceRelevanceTimer(ctx context.Context, params *UpdatePriceRelevanceTimerParams) (*UpdatePriceRelevanceTimerResponse, error) {
url := "/v1/product/action/timer/update"
resp := &UpdatePriceRelevanceTimerResponse{}
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 StatusPriceRelevanceTimerParams struct {
// List of product identifiers
ProductIds []string `json:"product_ids"`
}
type StatusPriceRelevanceTimerResponse struct {
core.CommonResponse
Statuses []PriceRelevanceTimerStatus `json:"statuses"`
}
type PriceRelevanceTimerStatus struct {
// Timer end time
ExpiredAt time.Time `json:"expired_at"`
// true, if Ozon takes into account the minimum price when creating promotions
MinPriceForAutoActionsEnabled bool `json:"min_price_for_auto_actions_enabled"`
// Product identifier
ProductId int64 `json:"product_id"`
}
// Get status of timer you've set
func (c Products) StatusPriceRelevanceTimer(ctx context.Context, params *StatusPriceRelevanceTimerParams) (*StatusPriceRelevanceTimerResponse, error) {
url := "/v1/product/action/timer/update"
resp := &StatusPriceRelevanceTimerResponse{}
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
}

File diff suppressed because it is too large Load Diff

View File

@@ -667,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
@@ -697,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 {
@@ -722,7 +725,7 @@ func (c Returns) FBSQuantity(ctx context.Context, params *GetFBSQuantityReturnsP
}
type ListReturnsParams struct {
// Filter
// 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
@@ -745,7 +748,7 @@ type ListReturnsFilter struct {
// Filter by order identifier
OrderId int64 `json:"order_id,omitempty"`
// Filter by shipment number
// Filter by shipment number. Pass no more than 50 postings
PostingNumbers []string `json:"posting_numbers,omitempty"`
// Filter by product name
@@ -904,6 +907,9 @@ type ReturnProduct struct {
// Commission details
Commission ReturnSum `json:"commission"`
// Product quantity
Quantity int32 `json:"quantity"`
}
type ReturnLogistic struct {

View File

@@ -823,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": {
@@ -835,6 +835,7 @@ func TestFBSQuantity(t *testing.T) {
},
"place_id": 0,
"returns_count": 0,
"utc_offset": "string",
"warehouses_ids": [
"string"
]
@@ -963,7 +964,8 @@ func TestListReturns(t *testing.T) {
"commission": {
"currency_code": "RUB",
"price": 2312
}
},
"quantity": 1
},
"logistic": {
"technical_return_moment": "2024-07-29T06:15:48.998146Z",

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

@@ -88,7 +88,8 @@ type GetListOfWarehousesResultFirstMile struct {
FirstMileType string `json:"first_mile_type"`
}
// You do not need to specify any parameters in the request. Your company will be identified by the Warehouses ID
// Method returns the list of FBS and rFBS warehouses.
// To get the list of FBO warehouses, use the /v1/cluster/list method.
func (c Warehouses) GetListOfWarehouses(ctx context.Context) (*GetListOfWarehousesResponse, error) {
url := "/v1/warehouse/list"
@@ -161,6 +162,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,
@@ -233,7 +237,9 @@ type Coordinates struct {
Longitude float64 `json:"longitude"`
}
// Get a list of warehouses, sorting centers and pick-up points available for cross-docking, and direct supplies.
// 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"

View File

@@ -127,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