84 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
Kirill
7f71ed6545 Update November 29, 2024 (#124) 2024-12-27 22:59:45 +03:00
Kirill
2f4d207726 Update November 22, 2024 (#123) 2024-12-27 22:46:08 +03:00
Kirill
7b5f44ee44 Update November 20, 2024 (#122) 2024-12-27 22:01:51 +03:00
Kirill
bc5f0e52a5 Update November 18, 2024 (#121) 2024-12-27 21:50:21 +03:00
benice2me11
8b8b3bc974 Get description of products (#120) 2024-12-26 15:35:13 +03:00
Kirill
45b0dffe39 Update November 19, 2024 (#118) 2024-12-15 21:09:09 +03:00
Kirill
c7697863db Update November 14, 2024 (#117) 2024-12-15 21:05:21 +03:00
Kirill
8a585d086a Update November 13, 2024 (#116) 2024-12-15 21:03:19 +03:00
diPhantxm
d1fd698368 update 2024-12-15 20:52:23 +03:00
Kirill
6b8b22180a Update November 6, 2024 (#115) 2024-12-15 20:47:07 +03:00
Kirill
f4a09903c7 Update October 31, 2024 (#114) 2024-12-12 00:53:56 +03:00
Kirill
23ca98fedd Update October 24, 2024 (#111) 2024-10-31 16:14:01 +03:00
Kirill
eae6f54e71 Update October 23, 2024 (#110) 2024-10-31 15:59:47 +03:00
Kirill
8a6cd20b95 Update October 22, 2024 (#109)
New method for getting product turnovers
2024-10-31 15:56:04 +03:00
Kirill
1706575a34 Update October 17, 2024 (#108) 2024-10-31 15:47:35 +03:00
diPhantxm
3430ead143 method to list cancellation reasons for fbo scheme 2024-10-31 15:16:11 +03:00
Kirill
2164eff0a6 Update October 16, 2024 (#107) 2024-10-31 15:10:45 +03:00
Kirill
b6af642636 Update October 1, 2024 (#105) 2024-10-02 00:39:04 +03:00
Kirill
67898a4738 Update September 18, 2024 (#106) 2024-10-02 00:38:52 +03:00
Kirill
8c07540d28 Update September 6, 2024 (#104) 2024-10-02 00:38:01 +03:00
Kirill
7c0e18681b Updates August 26, 2024 and August 28, 2024 (#103) 2024-09-10 00:57:30 +03:00
Pireirik
895ef8be52 Change the data structure of chat response according to the updates from Ozon (#102) 2024-09-09 22:09:31 +03:00
Pireirik
ccd3610c76 Change variable type for list of chats (#101) 2024-09-08 19:39:20 +03:00
s1berc0de
090b2afb63 added /v1/description-category/attribute/values/search support (#100) 2024-08-29 17:53:31 +03:00
diPhantxm
823386edf2 remove states 2024-08-09 19:57:56 +03:00
Kirill
5ecf131061 Update August 2, 2024 (#99) 2024-08-09 19:55:36 +03:00
Kirill
dfbb93f438 Update June 27, 2024 (#98) 2024-07-29 04:26:59 +03:00
Zloy_Leshiy
b0c133ba40 replace method from GET to POST for /v1/actions/products/activate request (#97)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-07-22 14:31:28 +03:00
Kirill
680a155294 Update June 18, 2024 (#96) 2024-07-05 01:52:13 +03:00
Kirill
26e2c8b9a7 Update June 10, 2024 (#95) 2024-06-16 02:14:27 +03:00
Zloy_Leshiy
b26dd5bbee Archived fields for product details and fix filter for GetStockInfo (#94)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-06-03 13:40:55 +03:00
Kirill
9ea138003c Update May 24, 2024 (#93) 2024-05-24 15:59:51 +03:00
Kirill
5d62c8ec14 Update May 23, 2024 (#92) 2024-05-24 15:50:40 +03:00
Kirill
2e284d9667 Time format fixes and optional fields (#91) 2024-05-24 15:42:48 +03:00
Kirill
7ffcf3f235 Update 16, 2024 (#90) 2024-05-18 12:07:52 +03:00
Kirill
ad2eb19325 Update May 2, 2024 (#89) 2024-05-06 19:57:44 +03:00
Kirill
99b0a24d48 Tests for Time Format (#87) 2024-04-30 14:15:03 +03:00
Zloy_Leshiy
e5867813ab Custom data type for time representation (#86)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-04-30 13:03:08 +03:00
Kirill
b0e1210f34 Fix type in GetStocksInfoParams.GetStocksInfoFilter #84 (#85) 2024-04-27 13:22:18 +05:00
Kirill
049b02835b Fix response types in GetListOfWarehouses (#83) 2024-04-21 18:41:18 +03:00
Kirill
6cf4ae89e3 Fix empty body (#81) 2024-04-20 18:55:49 +03:00
Kirill
8a6ca3b2eb Update April 16, 2024 (#78) 2024-04-18 16:39:33 +03:00
Kirill
25051ee802 Updates April 8, 2024 and April 9, 2024 (#77) 2024-04-10 17:53:31 +03:00
Kirill
61a78b1c4c Methods to manage Passes (Update April 1, 2024) (#76) 2024-04-02 22:50:52 +03:00
Kirill
97a9d2aba5 Update March 29, 2024 (#74) 2024-03-31 16:51:47 +03:00
Kirill
f02e71d17e Update March 12, 2024 (#73)
Add `hybryd` value to TPL Integration Type enum
2024-03-13 21:54:54 +03:00
Kirill
e6bfa30545 Update March 1, 2024 (#72)
Remove `accepted_from_customer_moment` from request and response in `/v3/returns/company/fbs`
2024-03-06 14:42:57 +03:00
Kirill
f6311fe59e Update February 16, 2024 (#71) 2024-02-17 03:47:20 +03:00
Kirill
8e73d136f2 Update February 13, 2024 (#70) 2024-02-17 03:24:14 +03:00
Kirill
7d8f43540f Update February 8, 2024 (#69) 2024-02-11 13:55:36 +03:00
diPhantxm
114a5b90b9 fix typo 2024-02-06 16:41:46 +03:00
Kirill
a0995a79e1 Update January 26, 2024 (#66)
add new status to fbo returns
2024-01-29 23:31:46 +03:00
Kirill
2f94b8c774 Reimplement default values (#65) 2024-01-29 23:22:57 +03:00
Kirill
35832e6269 remove default values temporarily (#64) 2024-01-29 18:51:12 +03:00
Kirill
965c83ba85 Make fields optional in method params (#62) 2024-01-25 15:41:30 +03:00
Zloy_Leshiy
a3c9d93adc Golang version to 19 and not required fields #60 (#61)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-01-23 03:14:18 +03:00
Kirill
549a2b9b41 Update readme example (#59) 2024-01-08 15:55:49 +03:00
Kirill
c36446bb59 Update December 25, 2023 (#57) 2023-12-27 00:04:03 +03:00
Kirill
ebafb17c9d Update December 15, 2023 (#55) 2023-12-16 15:52:35 +03:00
Kirill
178fd7086a Update December 11, 2023 (#54) 2023-12-12 23:29:03 +03:00
Kirill
de08ee28c0 Client options (#53) 2023-12-11 21:30:36 +03:00
49 changed files with 9437 additions and 2359 deletions

View File

@@ -25,7 +25,7 @@ jobs:
- name: Setup go - name: Setup go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: '1.19' go-version: '1.20'
- name: Setup - name: Setup
run: | run: |
go install github.com/mattn/goveralls@latest go install github.com/mattn/goveralls@latest

View File

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

View File

@@ -8,8 +8,6 @@ A Ozon Seller API client written in Golang
Read full [documentation](https://docs.ozon.ru/api/seller/en/#tag/Introduction) Read full [documentation](https://docs.ozon.ru/api/seller/en/#tag/Introduction)
You can check [list of supported endpoints](ENDPOINTS.md)
## How to start ## How to start
### API ### API
Get Client-Id and Api-Key in your seller profile [here](https://seller.ozon.ru/app/settings/api-keys?locale=en) Get Client-Id and Api-Key in your seller profile [here](https://seller.ozon.ru/app/settings/api-keys?locale=en)
@@ -23,6 +21,7 @@ A simple example on how to use this library:
package main package main
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
@@ -33,11 +32,14 @@ import (
func main() { func main() {
// Create a client with your Client-Id and Api-Key // Create a client with your Client-Id and Api-Key
// [Documentation]: https://docs.ozon.ru/api/seller/en/#tag/Auth // [Documentation]: https://docs.ozon.ru/api/seller/en/#tag/Auth
client := ozon.NewClient("my-client-id", "my-api-key") opts := []ozon.ClientOption{
ozon.WithAPIKey("api-key"),
ozon.WithClientId("client-id"),
}
c := ozon.NewClient(opts...)
// Send request with parameters // Send request with parameters
ctx, _ := context.WithTimeout(context.Background(), testTimeout) resp, err := client.Products().GetProductDetails(context.Background(), &ozon.GetProductDetailsParams{
resp, err := client.Products().GetProductDetails(&ozon.GetProductDetailsParams{
ProductId: 123456789, ProductId: 123456789,
}) })
if err != nil || resp.StatusCode != http.StatusOK { if err != nil || resp.StatusCode != http.StatusOK {
@@ -87,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

@@ -7,6 +7,8 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"reflect"
) )
type HttpClient interface { type HttpClient interface {
@@ -34,14 +36,28 @@ func NewMockClient(handler http.HandlerFunc) *Client {
} }
} }
func (c Client) newRequest(ctx context.Context, method string, url string, body interface{}) (*http.Request, error) { func (c Client) newRequest(ctx context.Context, method string, uri string, body interface{}) (*http.Request, error) {
bodyJson, err := json.Marshal(body) var err error
var bodyJson []byte
// Set default values for empty fields if `default` tag is present
// And body is not nil
if body != nil {
if err := getDefaultValues(reflect.ValueOf(body)); err != nil {
return nil, err
}
bodyJson, err = json.Marshal(body)
if err != nil {
return nil, err
}
}
uri, err = url.JoinPath(c.baseUrl, uri)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequestWithContext(ctx, method, uri, bytes.NewBuffer(bodyJson))
url = c.baseUrl + url
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(bodyJson))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -58,11 +74,6 @@ func (c Client) Request(ctx context.Context, method string, path string, req, re
if err != nil { if err != nil {
return nil, err return nil, err
} }
rawQuery, err := buildRawQuery(httpReq, req)
if err != nil {
return nil, err
}
httpReq.URL.RawQuery = rawQuery
httpResp, err := c.client.Do(httpReq) httpResp, err := c.client.Do(httpReq)
if err != nil { if err != nil {

172
core.go
View File

@@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
"strconv"
"strings"
"testing" "testing"
"time" "time"
) )
@@ -32,51 +34,103 @@ func (r Response) CopyCommonResponse(rhs *CommonResponse) {
rhs.Message = r.Message rhs.Message = r.Message
} }
func getDefaultValues(v interface{}) (map[string]string, error) { func getDefaultValues(v reflect.Value) error {
isNil, err := isZero(v) vValue := v.Elem()
if err != nil { vType := vValue.Type()
return make(map[string]string), err
}
if isNil {
return make(map[string]string), nil
}
out := make(map[string]string)
vType := reflect.TypeOf(v).Elem()
vValue := reflect.ValueOf(v).Elem()
for i := 0; i < vType.NumField(); i++ { for i := 0; i < vType.NumField(); i++ {
field := vType.Field(i) field := vType.Field(i)
tag := field.Tag.Get("json")
defaultValue := field.Tag.Get("default")
if field.Type.Kind() == reflect.Slice { switch field.Type.Kind() {
// Attach any slices as query params case reflect.Slice:
fieldVal := vValue.Field(i) for j := 0; j < vValue.Field(i).Len(); j++ {
for j := 0; j < fieldVal.Len(); j++ { // skip if slice type is primitive
out[tag] = fmt.Sprintf("%v", fieldVal.Index(j)) if vValue.Field(i).Index(j).Kind() != reflect.Struct {
}
} else {
// Add any scalar values as query params
fieldVal := fmt.Sprintf("%v", vValue.Field(i))
// If no value was set by the user, use the default
// value specified in the struct tag.
if fieldVal == "" || fieldVal == "0" {
if defaultValue == "" {
continue continue
} }
fieldVal = defaultValue // Attach any slices as query params
err := getDefaultValues(vValue.Field(i).Index(j).Addr())
if err != nil {
return err
}
}
case reflect.String:
isNil, err := isZero(vValue.Field(i).Addr())
if err != nil {
return err
}
if !isNil {
continue
} }
out[tag] = fmt.Sprintf("%v", fieldVal) defaultValue, ok := field.Tag.Lookup("default")
if !ok {
continue
}
vValue.Field(i).SetString(defaultValue)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
isNil, err := isZero(vValue.Field(i).Addr())
if err != nil {
return err
}
if !isNil {
continue
}
defaultValue, ok := field.Tag.Lookup("default")
if !ok {
continue
}
defaultValueInt, err := strconv.Atoi(defaultValue)
if err != nil {
return err
}
vValue.Field(i).SetInt(int64(defaultValueInt))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
isNil, err := isZero(vValue.Field(i).Addr())
if err != nil {
return err
}
if !isNil {
continue
}
defaultValue, ok := field.Tag.Lookup("default")
if !ok {
continue
}
defaultValueUint, err := strconv.ParseUint(defaultValue, 10, 64)
if err != nil {
return err
}
vValue.Field(i).SetUint(uint64(defaultValueUint))
case reflect.Struct:
err := getDefaultValues(vValue.Field(i).Addr())
if err != nil {
return err
}
case reflect.Ptr:
isNil, err := isZero(vValue.Field(i).Addr())
if err != nil {
return err
}
if isNil {
continue
}
if err := getDefaultValues(vValue.Field(i)); err != nil {
return err
}
default:
continue
} }
} }
return out, nil return nil
} }
func buildRawQuery(req *http.Request, v interface{}) (string, error) { func buildRawQuery(req *http.Request, v interface{}) (string, error) {
@@ -86,23 +140,20 @@ func buildRawQuery(req *http.Request, v interface{}) (string, error) {
return query.Encode(), nil return query.Encode(), nil
} }
values, err := getDefaultValues(v) err := getDefaultValues(reflect.ValueOf(v))
if err != nil { if err != nil {
return "", err return "", err
} }
for k, v := range values {
query.Add(k, v)
}
return query.Encode(), nil return query.Encode(), nil
} }
func isZero(v interface{}) (bool, error) { func isZero(v reflect.Value) (bool, error) {
t := reflect.TypeOf(v) t := v.Elem().Type()
if !t.Comparable() { if !t.Comparable() {
return false, fmt.Errorf("type is not comparable: %v", t) return false, fmt.Errorf("type is not comparable: %v", t)
} }
return v == reflect.Zero(t).Interface(), nil return reflect.Zero(t).Equal(v.Elem()), nil
} }
func TimeFromString(t *testing.T, format, datetime string) time.Time { func TimeFromString(t *testing.T, format, datetime string) time.Time {
@@ -112,3 +163,44 @@ func TimeFromString(t *testing.T, format, datetime string) time.Time {
} }
return dt return dt
} }
const ShortDateLayout = "2006-01-02"
// Do not use this structure for responses
// as there are no ways to unmarshal to any layout
// and leave nil if json field is null
type TimeFormat struct {
time.Time
layout string
}
func NewTimeFormat(t time.Time, layout string) *TimeFormat {
return &TimeFormat{
Time: t,
layout: layout,
}
}
func newTimeLayout(layout string) *TimeFormat {
return &TimeFormat{
layout: layout,
}
}
func (rd *TimeFormat) UnmarshalJSON(b []byte) error {
var err error
s := strings.Trim(string(b), `"`) // remove quotes
// Added for extra accuracy
// encoding/json won't invoke this method if field is null
if s == "null" {
return nil
}
rd.Time, err = time.Parse(rd.layout, s)
return err
}
func (rd *TimeFormat) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`%q`, rd.Time.Format(rd.layout))), nil
}

View File

@@ -1,34 +1,152 @@
package core package core
import ( import (
"log" "encoding/json"
"reflect"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert"
) )
type TestTagDefaultValueStruct struct { type DefaultStructure struct {
TestString string `json:"test_string" default:"something"` EmptyField string `json:"empty_field" default:"empty_string"`
TestNumber int `json:"test_number" default:"12"` Field string `json:"field" default:"string"`
} }
func TestTagDefaultValue(t *testing.T) { type DefaultRequest struct {
testStruct := &TestTagDefaultValueStruct{} Field int `json:"field" default:"100"`
EmptyField int `json:"empty_field" default:"14"`
Structure DefaultStructure `json:"structure"`
Slice []DefaultStructure `json:"slice"`
OptionalStructure *DefaultStructure `json:"optional_structure"`
EmptyOptionalStructure *DefaultStructure `json:"empty_optional_structure"`
}
values, err := getDefaultValues(testStruct) func TestDefaultValues(t *testing.T) {
if err != nil { req := &DefaultRequest{
log.Fatalf("error when getting default values from tags: %s", err) Field: 50,
Structure: DefaultStructure{
Field: "something",
},
Slice: []DefaultStructure{
{
Field: "something",
},
{
Field: "something",
},
},
OptionalStructure: &DefaultStructure{
Field: "something",
},
} }
err := getDefaultValues(reflect.ValueOf(req))
assert.Nil(t, err)
expected := map[string]string{ assert.Equal(t, 50, req.Field)
"test_string": "something", assert.Equal(t, 14, req.EmptyField)
"test_number": "12", assert.Equal(t, "something", req.Structure.Field)
} assert.Equal(t, "empty_string", req.Structure.EmptyField)
assert.Equal(t, "something", req.Slice[0].Field)
assert.Equal(t, "something", req.Slice[1].Field)
assert.Equal(t, "empty_string", req.Slice[1].EmptyField)
assert.Equal(t, "empty_string", req.Slice[1].EmptyField)
assert.Equal(t, "something", req.OptionalStructure.Field)
assert.Equal(t, "empty_string", req.OptionalStructure.EmptyField)
assert.Equal(t, (*DefaultStructure)(nil), req.EmptyOptionalStructure)
}
if len(values) != len(expected) { func TestTimeFormat(t *testing.T) {
log.Fatalf("expected equal length of values and expected: expected: %d, got: %d", len(expected), len(values)) t.Run("Time Format Marshalling", func(t *testing.T) {
} tests := []struct {
for expKey, expValue := range expected { ft *TimeFormat
if expValue != values[expKey] { layout string
log.Fatalf("not equal values for key %s", expKey) expectedJSON string
diff time.Duration
}{
{
ft: NewTimeFormat(time.Date(2024, 4, 30, 15, 42, 12, 55, time.FixedZone("Test Zone", 0)), ShortDateLayout),
layout: ShortDateLayout,
expectedJSON: `"2024-04-30"`,
diff: time.Hour * 24,
},
{
ft: NewTimeFormat(time.Date(2024, 4, 30, 0, 0, 0, 0, time.FixedZone("Test Zone", 0)), ShortDateLayout),
layout: ShortDateLayout,
expectedJSON: `"2024-04-30"`,
diff: time.Hour * 24,
},
{
ft: NewTimeFormat(time.Time{}, ShortDateLayout),
layout: ShortDateLayout,
expectedJSON: `"0001-01-01"`,
diff: time.Hour * 24,
},
{
ft: nil,
layout: ShortDateLayout,
expectedJSON: `null`,
diff: time.Hour * 24,
},
} }
}
for _, tc := range tests {
marshaled, err := json.Marshal(tc.ft)
assert.Equal(t, nil, err)
assert.Equal(t, tc.expectedJSON, string(marshaled))
unmarshaled := newTimeLayout(tc.layout)
err = json.Unmarshal(marshaled, unmarshaled)
assert.Equal(t, nil, err)
if tc.ft != nil {
diffedTime := tc.ft.Add(-tc.diff)
assert.Equal(t, true, diffedTime.Before(unmarshaled.Time) || diffedTime.Equal(unmarshaled.Time))
assert.Equal(t, true, tc.ft.After(unmarshaled.Time) || tc.ft.Equal(unmarshaled.Time))
}
}
})
t.Run("Time Format in structure Marshalling", func(t *testing.T) {
type test struct {
Date *TimeFormat `json:"date"`
}
tests := []struct {
structure *test
layout string
expectedJSON string
diff time.Duration
}{
{
structure: &test{Date: NewTimeFormat(time.Date(2024, 4, 30, 5, 4, 7, 20, time.FixedZone("Test Zone", 0)), ShortDateLayout)},
layout: ShortDateLayout,
expectedJSON: `{"date":"2024-04-30"}`,
diff: time.Hour * 24,
},
{
structure: &test{Date: nil},
layout: ShortDateLayout,
expectedJSON: `{"date":null}`,
diff: time.Hour * 24,
},
}
for _, tc := range tests {
marshaled, err := json.Marshal(tc.structure)
assert.Equal(t, nil, err)
assert.Equal(t, tc.expectedJSON, string(marshaled))
unmarshaled := &test{Date: newTimeLayout(tc.layout)}
err = json.Unmarshal(marshaled, unmarshaled)
assert.Equal(t, nil, err)
if tc.structure != nil && tc.structure.Date != nil {
diffedTime := tc.structure.Date.Add(-tc.diff)
assert.Equal(t, true, diffedTime.Before(unmarshaled.Date.Time) || diffedTime.Equal(unmarshaled.Date.Time))
assert.Equal(t, true, tc.structure.Date.After(unmarshaled.Date.Time) || tc.structure.Date.Equal(unmarshaled.Date.Time))
}
}
})
} }

10
go.mod
View File

@@ -1,3 +1,11 @@
module github.com/diphantxm/ozon-api-client module github.com/diphantxm/ozon-api-client
go 1.18 go 1.20
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

17
go.sum Normal file
View File

@@ -0,0 +1,17 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -3,7 +3,6 @@ package ozon
import ( import (
"context" "context"
"net/http" "net/http"
"time"
core "github.com/diphantxm/ozon-api-client" core "github.com/diphantxm/ozon-api-client"
) )
@@ -14,10 +13,10 @@ type Analytics struct {
type GetAnalyticsDataParams struct { type GetAnalyticsDataParams struct {
// Date from which the data will be in the report // Date from which the data will be in the report
DateFrom time.Time `json:"date_from"` DateFrom *core.TimeFormat `json:"date_from"`
// Date up to which the data will be in the report // Date up to which the data will be in the report
DateTo time.Time `json:"date_to"` DateTo *core.TimeFormat `json:"date_to"`
// Items Enum: "unknownDimension" "sku" "spu" "day" "week" "month" "year" "category1" "category2" "category3" "category4" "brand" "modelID" // Items Enum: "unknownDimension" "sku" "spu" "day" "week" "month" "year" "category1" "category2" "category3" "category4" "brand" "modelID"
// Data grouping available to all sellers: // Data grouping available to all sellers:
@@ -126,7 +125,7 @@ type GetAnalyticsDataResultData struct {
} }
type GetAnalyticsDataResultDimension struct { type GetAnalyticsDataResultDimension struct {
// Identifier // Product SKU
Id string `json:"id"` Id string `json:"id"`
// Name // Name
@@ -164,8 +163,8 @@ type GetStocksOnWarehousesParams struct {
// Number of elements that will be skipped in the response. For example, if `offset=10`, the response will start with the 11th element found // Number of elements that will be skipped in the response. For example, if `offset=10`, the response will start with the 11th element found
Offset int64 `json:"offset"` Offset int64 `json:"offset"`
// Warehouse type filter: // Warehouse type filter
WarehouseType WarehouseType `json:"warehouse_type"` WarehouseType WarehouseType `json:"warehouse_type" default:"ALL"`
} }
type GetStocksOnWarehousesResponse struct { type GetStocksOnWarehousesResponse struct {
@@ -203,7 +202,7 @@ type GetStocksOnWarehousesResultRow struct {
WarehouseName string `json:"warehouse_name"` WarehouseName string `json:"warehouse_name"`
} }
// 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) { func (c Analytics) GetStocksOnWarehouses(ctx context.Context, params *GetStocksOnWarehousesParams) (*GetStocksOnWarehousesResponse, error) {
url := "/v2/analytics/stock_on_warehouses" url := "/v2/analytics/stock_on_warehouses"
@@ -217,3 +216,235 @@ func (c Analytics) GetStocksOnWarehouses(ctx context.Context, params *GetStocksO
return resp, nil return resp, nil
} }
type GetProductTurnoverParams struct {
// Number of values in the response
Limit int64 `json:"limit"`
// Number of elements to skip in the response.
//
// For example, if offset = 10, the response starts with the 11th element found
Offset int32 `json:"offset"`
// Product identifiers in the Ozon system, SKU
SKU []string `json:"sku"`
}
type GetProductTurnoverResponse struct {
core.CommonResponse
// Products
Items []ProductTurnoverItem `json:"items"`
}
type ProductTurnoverItem struct {
// Average daily number of product items sold over the last 60 days
Ads float64 `json:"ads"`
// Product stock, pcs
CurrentStock int64 `json:"current_stock"`
// Number of days the stock will last based on your average daily sales
IDC float64 `json:"idc"`
// Product stock level
IDCGrade string `json:"idc_grade"`
}
// Use the method to get the product turnover rate and the number of days the current stock will last.
//
// If you request a list of products by sku, the limit and offset parameters are optional.
func (c Analytics) GetProductTurnover(ctx context.Context, params *GetProductTurnoverParams) (*GetProductTurnoverResponse, error) {
url := "/v1/analytics/turnover/stocks"
resp := &GetProductTurnoverResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type GetStockManagementParams struct {
// GetStockManagementFilter
Filter GetStockManagementFilter `json:"filter"`
// Number of values in the response
Limit int32 `json:"limit,omitempty"`
// Number of elements to skip in the response
Offset int32 `json:"offset,omitempty"`
}
type GetStockManagementFilter struct {
// Product identifiers in the Ozon system, SKU
SKUs []string `json:"skus"`
// The type of item in stock
StockTypes string `json:"stock_types"`
// Warehouse identifiers
WarehouseIds []string `json:"warehouse_ids"`
}
type GetStockManagementResponse struct {
core.CommonResponse
// Products
Items []StockItem `json:"items"`
}
type StockItem struct {
// Stock of defective products, pcs
DefectCount int64 `json:"defect_stock_count"`
// Stock of near-expired products, pcs
ExpiringCount int64 `json:"expiring_stock_count"`
// Product name
ProductName string `json:"name"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Stock of valid products
ValidCount int64 `json:"valid_stock_count"`
// Stock of products that waiting for documents
WaitingDocsCount int64 `json:"waitingdocs_stock_count"`
// Warehouse name
WarehouseName string `json:"warehouse_name"`
}
// Use the method to find out how many product items are left in stock
func (c Analytics) Stock(ctx context.Context, params *GetStockManagementParams) (*GetStockManagementResponse, error) {
url := "/v1/analytics/manage/stocks"
resp := &GetStockManagementResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
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

@@ -4,6 +4,7 @@ import (
"context" "context"
"net/http" "net/http"
"testing" "testing"
"time"
core "github.com/diphantxm/ozon-api-client" core "github.com/diphantxm/ozon-api-client"
) )
@@ -22,8 +23,8 @@ func TestGetAnalyticsData(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetAnalyticsDataParams{ &GetAnalyticsDataParams{
DateFrom: core.TimeFromString(t, "2006-01-02", "2020-09-01"), DateFrom: core.NewTimeFormat(time.Now().Add(time.Duration(30)*24*time.Hour), core.ShortDateLayout),
DateTo: core.TimeFromString(t, "2006-01-02", "2021-10-15"), DateTo: core.NewTimeFormat(time.Now(), core.ShortDateLayout),
Dimension: []GetAnalyticsDataDimension{SKUDimension, DayDimension}, Dimension: []GetAnalyticsDataDimension{SKUDimension, DayDimension},
Metrics: []GetAnalyticsDataFilterMetric{HistViewPDP}, Metrics: []GetAnalyticsDataFilterMetric{HistViewPDP},
Sort: []GetAnalyticsDataSort{ Sort: []GetAnalyticsDataSort{
@@ -144,3 +145,206 @@ func TestGetStocksOnWarehouses(t *testing.T) {
} }
} }
} }
func TestGetProductTurnover(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetProductTurnoverParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetProductTurnoverParams{
Limit: 1,
SKU: []string{"string"},
},
`{
"items": [
{
"ads": 0,
"current_stock": 0,
"idc": 0,
"idc_grade": "GRADES_NONE"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetProductTurnoverParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Analytics().GetProductTurnover(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductTurnoverResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Items) > int(test.params.Limit) {
t.Errorf("Length of items is bigger than limit")
}
}
}
}
func TestGetStock(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetStockManagementParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetStockManagementParams{
Limit: 1,
Offset: 0,
Filter: GetStockManagementFilter{
StockTypes: "STOCK_TYPE_VALID",
SKUs: []string{
"string",
},
},
},
`{
"items": [
{
"defect_stock_count": 0,
"expiring_stock_count": 0,
"name": "string",
"offer_id": "string",
"sku": 0,
"valid_stock_count": 0,
"waitingdocs_stock_count": 0,
"warehouse_name": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetStockManagementParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Analytics().Stock(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetStockManagementResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
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

@@ -41,7 +41,7 @@ type CancellationInfo struct {
CancellationReasonMessage string `json:"cancellation_reason_message"` CancellationReasonMessage string `json:"cancellation_reason_message"`
// Delivery service integration type // Delivery service integration type
TPLIntegrationType string `json:"tpl_integration_type"` TPLIntegrationType TPLIntegrationType `json:"tpl_integration_type"`
// Cancellation request status // Cancellation request status
State CancellationInfoState `json:"state"` State CancellationInfoState `json:"state"`
@@ -83,7 +83,7 @@ type CancellationInfoState struct {
// Method for getting information about a rFBS cancellation request // Method for getting information about a rFBS cancellation request
func (c Cancellations) GetInfo(ctx context.Context, params *GetCancellationInfoParams) (*GetCancellationInfoResponse, error) { func (c Cancellations) GetInfo(ctx context.Context, params *GetCancellationInfoParams) (*GetCancellationInfoResponse, error) {
url := "/v1/delivery-method/list" url := "/v1/conditional-cancellation/get"
resp := &GetCancellationInfoResponse{} resp := &GetCancellationInfoResponse{}
@@ -98,30 +98,30 @@ func (c Cancellations) GetInfo(ctx context.Context, params *GetCancellationInfoP
type ListCancellationsParams struct { type ListCancellationsParams struct {
// Filters // Filters
Filter ListCancellationsFilter `json:"filter"` Filter *ListCancellationsFilter `json:"filter,omitempty"`
// Number of cancellation requests in the response // Number of cancellation requests in the response
Limit int32 `json:"limit"` Limit int32 `json:"limit,omitempty"`
// Number of elements that will be skipped in the response. // Number of elements that will be skipped in the response.
// For example, if offset=10, the response will start with the 11th element found // For example, if offset=10, the response will start with the 11th element found
Offset int32 `json:"offset"` Offset int32 `json:"offset,omitempty"`
// Additional information // Additional information
With ListCancellationWith `json:"with"` With *ListCancellationWith `json:"with,omitempty"`
} }
type ListCancellationsFilter struct { type ListCancellationsFilter struct {
// Filter by cancellation initiator // Filter by cancellation initiator
CancellationInitiator []string `json:"cancellation_initiator"` CancellationInitiator []string `json:"cancellation_initiator,omitempty"`
// Filter by shipment number. // Filter by shipment number.
// //
// Optional parameter. You can pass several values here // Optional parameter. You can pass several values here
PostingNumber string `json:"posting_number"` PostingNumber string `json:"posting_number,omitempty"`
// Filter by cancellation request status // Filter by cancellation request status
State string `json:"state"` State string `json:"state,omitempty"`
} }
type ListCancellationWith struct { type ListCancellationWith struct {
@@ -173,7 +173,7 @@ type ApproveRejectCancellationsParams struct {
CancellationId int64 `json:"cancellation_id"` CancellationId int64 `json:"cancellation_id"`
// Comment // Comment
Comment string `json:"comment"` Comment string `json:"comment,omitempty"`
} }
type ApproveRejectCancellationsResponse struct { type ApproveRejectCancellationsResponse struct {

View File

@@ -98,13 +98,13 @@ func TestListCancellations(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListCancellationsParams{ &ListCancellationsParams{
Filter: ListCancellationsFilter{ Filter: &ListCancellationsFilter{
CancellationInitiator: []string{"CLIENT"}, CancellationInitiator: []string{"CLIENT"},
State: "ALL", State: "ALL",
}, },
Limit: 2, Limit: 2,
Offset: 0, Offset: 0,
With: ListCancellationWith{ With: &ListCancellationWith{
Counters: true, Counters: true,
}, },
}, },

View File

@@ -13,7 +13,7 @@ type Categories struct {
type GetProductTreeParams struct { type GetProductTreeParams struct {
// Response language // Response language
Language Language `json:"language"` Language Language `json:"language,omitempty"`
} }
type GetProductTreeResponse struct { type GetProductTreeResponse struct {
@@ -25,7 +25,7 @@ type GetProductTreeResponse struct {
type GetProductTreeResult struct { type GetProductTreeResult struct {
// Category identifier // Category identifier
CategoryId int64 `json:"category_id"` DescriptionCategoryId int64 `json:"description_category_id"`
// Category name // Category name
CategoryName string `json:"category_name"` CategoryName string `json:"category_name"`
@@ -64,10 +64,10 @@ func (c *Categories) Tree(ctx context.Context, params *GetProductTreeParams) (*G
type GetCategoryAttributesParams struct { type GetCategoryAttributesParams struct {
// Category identifier // Category identifier
CategoryId int64 `json:"category_id"` DescriptionCategoryId int64 `json:"description_category_id"`
// Response language // Response language
Language Language `json:"language"` Language Language `json:"language,omitempty"`
// Product type identifier // Product type identifier
TypeId int64 `json:"type_id"` TypeId int64 `json:"type_id"`
@@ -81,6 +81,12 @@ type GetCategoryAttributesResponse struct {
} }
type GetCategoryAttributesResult struct { type GetCategoryAttributesResult struct {
// Indication that the dictionary attribute values depend on the category:
//
// true — the attribute has its own set of values for each category.
// false — the attribute has the same set of values for all categories
CategoryDependent bool `json:"category_dependent"`
// Characteristic description // Characteristic description
Description string `json:"description"` Description string `json:"description"`
@@ -123,6 +129,12 @@ type GetCategoryAttributesResult struct {
// Characteristic type // Characteristic type
Type string `json:"type"` Type string `json:"type"`
// Complex attribute identifier
AttributeComplexId int64 `json:"attribute_complex_id"`
// Maximum number of values for attribute
MaxValueCount int64 `json:"max_value_count"`
} }
// Getting characteristics for specified product category and type. // Getting characteristics for specified product category and type.
@@ -149,10 +161,10 @@ type GetAttributeDictionaryParams struct {
AttributeId int64 `json:"attribute_id"` AttributeId int64 `json:"attribute_id"`
// Category identifier // Category identifier
CategoryId int64 `json:"category_id"` DescriptionCategoryId int64 `json:"description_category_id"`
// Response language // Response language
Language Language `json:"language"` Language Language `json:"language,omitempty"`
// Identifier of the directory to start the response with. // Identifier of the directory to start the response with.
// If `last_value_id` is 10, the response will contain directories starting from the 11th // If `last_value_id` is 10, the response will contain directories starting from the 11th
@@ -162,7 +174,7 @@ type GetAttributeDictionaryParams struct {
// //
// - maximum—5000, // - maximum—5000,
// - minimum—1. // - minimum—1.
Limit int64 `json:"limit"` Limit int64 `json:"limit,omitempty"`
// Product type identifier // Product type identifier
TypeId int64 `json:"type_id"` TypeId int64 `json:"type_id"`
@@ -200,7 +212,7 @@ type GetAttributeDictionaryResult struct {
// To check if an attribute has a nested directory, // To check if an attribute has a nested directory,
// use the `/v1/description-category/attribute` method. // use the `/v1/description-category/attribute` method.
func (c *Categories) AttributesDictionary(ctx context.Context, params *GetAttributeDictionaryParams) (*GetAttributeDictionaryResponse, error) { func (c *Categories) AttributesDictionary(ctx context.Context, params *GetAttributeDictionaryParams) (*GetAttributeDictionaryResponse, error) {
url := "/v1/description-category/attribute" url := "/v1/description-category/attribute/values"
resp := &GetAttributeDictionaryResponse{} resp := &GetAttributeDictionaryResponse{}
@@ -212,3 +224,63 @@ func (c *Categories) AttributesDictionary(ctx context.Context, params *GetAttrib
return resp, nil return resp, nil
} }
type SearchAttributeDictionaryParams struct {
// Characteristics identifier
AttributeId int64 `json:"attribute_id"`
// Category identifier
DescriptionCategoryId int64 `json:"description_category_id"`
// The value to be searched for
// - minimum—2 characters
Value string `json:"value"`
// Number of values in the response:
//
// - maximum—100,
// - minimum—1.
Limit int64 `json:"limit,omitempty"`
// Product type identifier
TypeId int64 `json:"type_id"`
}
type SearchAttributeDictionaryResponse struct {
core.CommonResponse
// Characteristic values
Result []SearchAttributeDictionaryResult `json:"result"`
}
type SearchAttributeDictionaryResult struct {
// Characteristic value identifier
Id int64 `json:"id"`
// Additional description
Info string `json:"info"`
// Image link
Picture string `json:"picture"`
// Product characteristic value
Value string `json:"value"`
}
// Returns found characteristics value directory.
//
// To check if an attribute has a nested directory,
// use the `/v1/description-category/attribute` method.
func (c *Categories) SearchAttributesDictionary(ctx context.Context, params *SearchAttributeDictionaryParams) (*SearchAttributeDictionaryResponse, error) {
url := "/v1/description-category/attribute/values/search"
resp := &SearchAttributeDictionaryResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}

View File

@@ -27,7 +27,7 @@ func TestGetProductTree(t *testing.T) {
`{ `{
"result": [ "result": [
{ {
"category_id": 0, "description_category_id": 0,
"category_name": "string", "category_name": "string",
"children": [], "children": [],
"disabled": true, "disabled": true,
@@ -81,13 +81,14 @@ func TestGetCategoryAttributes(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetCategoryAttributesParams{ &GetCategoryAttributesParams{
CategoryId: 12345, DescriptionCategoryId: 12345,
Language: English, Language: English,
TypeId: 2, TypeId: 2,
}, },
`{ `{
"result": [ "result": [
{ {
"category_dependent": true,
"description": "string", "description": "string",
"dictionary_id": 0, "dictionary_id": 0,
"group_id": 0, "group_id": 0,
@@ -97,7 +98,9 @@ func TestGetCategoryAttributes(t *testing.T) {
"is_collection": true, "is_collection": true,
"is_required": true, "is_required": true,
"name": "string", "name": "string",
"type": "string" "type": "string",
"attribute_complex_id": 0,
"max_value_count": 0
} }
] ]
}`, }`,
@@ -146,12 +149,12 @@ func TestGetAttributeDictionary(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetAttributeDictionaryParams{ &GetAttributeDictionaryParams{
AttributeId: 123456, AttributeId: 123456,
CategoryId: 12, DescriptionCategoryId: 12,
Language: English, Language: English,
LastValueId: 1, LastValueId: 1,
Limit: 5, Limit: 5,
TypeId: 6, TypeId: 6,
}, },
`{ `{
"has_next": true, "has_next": true,
@@ -200,3 +203,71 @@ func TestGetAttributeDictionary(t *testing.T) {
} }
} }
} }
func TestSearchAttributeDictionary(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SearchAttributeDictionaryParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SearchAttributeDictionaryParams{
AttributeId: 123456,
DescriptionCategoryId: 12,
Value: "34",
Limit: 5,
TypeId: 6,
},
`{
"has_next": true,
"result": [
{
"id": 0,
"info": "string",
"picture": "string",
"value": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SearchAttributeDictionaryParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Categories().SearchAttributesDictionary(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetAttributeDictionaryResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) > int(test.params.Limit) {
t.Errorf("Length of response result is bigger than limit")
}
}
}
}

View File

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

View File

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

View File

@@ -14,14 +14,14 @@ type Chats struct {
type ListChatsParams struct { type ListChatsParams struct {
// Chats filter // Chats filter
Filter ListChatsFilter `json:"filter"` Filter *ListChatsFilter `json:"filter,omitempty"`
// Number of values in the response. Default value is 1 // Number of values in the response. The default value is 30. The maximum value is 1000
Limit int64 `json:"limit" default:"1"` Limit int64 `json:"limit" default:"30"`
// Number of elements that will be skipped in the response. // Number of elements that will be skipped in the response.
// For example, if offset=10, the response will start with the 11th element found // For example, if offset=10, the response will start with the 11th element found
Offset int64 `json:"offset"` Offset int64 `json:"offset,omitempty"`
} }
type ListChatsFilter struct { type ListChatsFilter struct {
@@ -39,7 +39,7 @@ type ListChatsResponse struct {
core.CommonResponse core.CommonResponse
// Chats data // Chats data
Chats []ListChatsChat `json:"chats"` Chats []ListChatsChatData `json:"chats"`
// Total number of chats // Total number of chats
TotalChatsCount int64 `json:"total_chats_count"` TotalChatsCount int64 `json:"total_chats_count"`
@@ -48,20 +48,6 @@ type ListChatsResponse struct {
TotalUnreadCount int64 `json:"total_unread_count"` TotalUnreadCount int64 `json:"total_unread_count"`
} }
type ListChatsChat struct {
// Chat data
Chat ListChatsChatData `json:"chat"`
// Identifier of the first unread chat message
FirstUnreadMessageId string `json:"first_unread_message_id"`
// Identifier of the last message in the chat
LastMessageId string `json:"last_message_id"`
// Number of unread messages in the chat
UnreadCount int64 `json:"unread_count"`
}
type ListChatsChatData struct { type ListChatsChatData struct {
// Chat identifier // Chat identifier
ChatId string `json:"chat_id"` ChatId string `json:"chat_id"`
@@ -79,6 +65,15 @@ type ListChatsChatData struct {
// Chat creation date // Chat creation date
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
// Identifier of the first unread chat message
FirstUnreadMessageId uint64 `json:"first_unread_message_id"`
// Identifier of the last message in the chat
LastMessageId uint64 `json:"last_message_id"`
// Number of unread messages in the chat
UnreadCount int64 `json:"unread_count"`
} }
// Returns information about chats by specified filters // Returns information about chats by specified filters
@@ -173,7 +168,7 @@ type ChatHistoryParams struct {
// Default value is the last visible message // Default value is the last visible message
FromMessageId string `json:"from_message_id"` FromMessageId string `json:"from_message_id"`
// Number of messages in the response. The default value is 50 // Number of messages in the response. The default value is 50. The maximum value is 1000
Limit int64 `json:"limit" default:"50"` Limit int64 `json:"limit" default:"50"`
} }
@@ -217,7 +212,7 @@ type ChatHistoryMessageUser struct {
Type string `json:"type"` Type string `json:"type"`
} }
// Chat history // Returns the history of chat messages. By default messages are shown from newest to oldest.
func (c Chats) History(ctx context.Context, params *ChatHistoryParams) (*ChatHistoryResponse, error) { func (c Chats) History(ctx context.Context, params *ChatHistoryParams) (*ChatHistoryResponse, error) {
url := "/v2/chat/history" url := "/v2/chat/history"
@@ -240,7 +235,7 @@ type UpdateChatParams struct {
FromMessageId uint64 `json:"from_message_id"` FromMessageId uint64 `json:"from_message_id"`
// Number of messages in the response // Number of messages in the response
Limit int64 `json:"limit"` Limit int64 `json:"limit,omitempty"`
} }
type UpdateChatResponse struct { type UpdateChatResponse struct {
@@ -386,7 +381,7 @@ func (c Chats) Create(ctx context.Context, params *CreateNewChatParams) (*Create
type MarkAsReadParams struct { type MarkAsReadParams struct {
// Chat identifier // Chat identifier
Chatid string `json:"chat_id"` ChatId string `json:"chat_id"`
// Message identifier // Message identifier
FromMessageId uint64 `json:"from_message_id"` FromMessageId uint64 `json:"from_message_id"`

View File

@@ -22,7 +22,7 @@ func TestListChats(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListChatsParams{ &ListChatsParams{
Filter: ListChatsFilter{ Filter: &ListChatsFilter{
ChatStatus: "Opened", ChatStatus: "Opened",
UnreadOnly: true, UnreadOnly: true,
}, },
@@ -32,15 +32,13 @@ func TestListChats(t *testing.T) {
`{ `{
"chats": [ "chats": [
{ {
"chat": { "chat_id": "5e767w03-b400-4y1b-a841-75319ca8a5c8",
"created_at": "2022-07-22T08:07:19.581Z", "chat_status": "Opened",
"chat_id": "5e767w03-b400-4y1b-a841-75319ca8a5c8", "chat_type": "Seller_Support",
"chat_status": "Opened", "created_at": "2022-07-22T08:07:19.581Z",
"chat_type": "Seller_Support" "unread_count": 1,
}, "last_message_id": 3000000000128004274,
"first_unread_message_id": "3000000000118021931", "first_unread_message_id": 3000000000118021931
"last_message_id": "30000000001280042740",
"unread_count": 1
} }
], ],
"total_chats_count": 25, "total_chats_count": 25,
@@ -77,10 +75,10 @@ func TestListChats(t *testing.T) {
if resp.StatusCode == http.StatusOK { if resp.StatusCode == http.StatusOK {
if len(resp.Chats) > 0 { if len(resp.Chats) > 0 {
if resp.Chats[0].Chat.ChatStatus == "" { if resp.Chats[0].ChatStatus == "" {
t.Errorf("Chat status cannot be empty") t.Errorf("Chat status cannot be empty")
} }
if resp.Chats[0].Chat.ChatType == "" { if resp.Chats[0].ChatType == "" {
t.Errorf("Chat type cannot be empty") t.Errorf("Chat type cannot be empty")
} }
} }
@@ -400,7 +398,7 @@ func TestMarkAsRead(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&MarkAsReadParams{ &MarkAsReadParams{
Chatid: "99feb3fc-a474-469f-95d5-268b470cc607", ChatId: "99feb3fc-a474-469f-95d5-268b470cc607",
FromMessageId: 3000000000118032000, FromMessageId: 3000000000118032000,
}, },
`{ `{

74
ozon/clusters.go Normal file
View File

@@ -0,0 +1,74 @@
package ozon
import (
"context"
"net/http"
core "github.com/diphantxm/ozon-api-client"
)
type Clusters struct {
client *core.Client
}
type ListClustersParams struct {
// Clusters identifiers
ClusterIds []string `json:"cluster_ids"`
// Cluster type
ClusterType string `json:"cluster_type"`
}
type ListClustersResponse struct {
core.CommonResponse
// Cluster details
Clusters []Cluster `json:"clusters"`
}
type Cluster struct {
// Cluster identifier
Id int64 `json:"id"`
// Cluster warehouse details
LogisticClusters []LogisticCluster `json:"logistic_clusters"`
// Cluster name
Name string `json:"name"`
// Cluster type
Type string `json:"type"`
}
type LogisticCluster struct {
// Warehouse status
IsArchived bool `json:"is_archived"`
// Warehouses
Warehouses []LogisticClusterWarehouse `json:"warehouses"`
}
type LogisticClusterWarehouse struct {
// Warehouse name
Name string `json:"name"`
// Warehouse type
Type string `json:"type"`
// Warehouse identifier
Id int64 `json:"warehouse_id"`
}
func (c Clusters) List(ctx context.Context, params *ListClustersParams) (*ListClustersResponse, error) {
url := "/v1/cluster/list"
resp := &ListClustersResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}

78
ozon/clusters_test.go Normal file
View File

@@ -0,0 +1,78 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestListClusters(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListClustersParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListClustersParams{
ClusterIds: []string{"string"},
ClusterType: "CLUSTER_TYPE_UNKNOWN",
},
`{
"clusters": [
{
"id": 0,
"logistic_clusters": [
{
"is_archived": true,
"warehouses": [
{
"name": "string",
"type": "FULL_FILLMENT",
"warehouse_id": 0
}
]
}
],
"name": "string",
"type": "CLUSTER_TYPE_UNKNOWN"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListClustersParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Clusters().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListClustersResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

@@ -1,6 +1,8 @@
package ozon package ozon
import "time" import (
"time"
)
const ( const (
testTimeout = 5 * time.Second testTimeout = 5 * time.Second
@@ -91,16 +93,16 @@ const (
Purchased ListDiscountRequestsStatus = "PURCHASED" Purchased ListDiscountRequestsStatus = "PURCHASED"
) )
type WorkingDay string type WorkingDay int
const ( const (
Mon WorkingDay = "1" Mon WorkingDay = 1
Tue WorkingDay = "2" Tue WorkingDay = 2
Wed WorkingDay = "3" Wed WorkingDay = 3
Thu WorkingDay = "4" Thu WorkingDay = 4
Fri WorkingDay = "5" Fri WorkingDay = 5
Sat WorkingDay = "6" Sat WorkingDay = 6
Sun WorkingDay = "7" Sun WorkingDay = 7
) )
type GetAnalyticsDataDimension string type GetAnalyticsDataDimension string
@@ -124,24 +126,6 @@ const (
type SupplyRequestState string type SupplyRequestState string
const ( const (
// request draft. Only for supplies via vDC
Draft SupplyRequestState = "DRAFT"
// selecting supply options. Only for supplies via vDC
SupplyVariantsArranging SupplyRequestState = "SUPPLY_VARIANTS_ARRANGING"
// no supply options, the request is archived. Only for supplies via vDC
HasNoSupplyVariantsArchive SupplyRequestState = "HAS_NO_SUPPLY_VARIANTS_ARCHIVE"
// no supply options. Only for supplies via vDC
HasNoSupplyVariantsNew SupplyRequestState = "HAS_NO_SUPPLY_VARIANTS_NEW"
// supply being approved. Only for supplies via vDC
SupplyVariantsConfirmation SupplyRequestState = "SUPPLY_VARIANTS_CONFIRMATION"
// time reservation
TimeslotBooking SupplyRequestState = "TIMESLOT_BOOKING"
// filling in the data // filling in the data
DATA_FILLING SupplyRequestState = "DATA_FILLING" DATA_FILLING SupplyRequestState = "DATA_FILLING"
@@ -312,6 +296,9 @@ const (
// delivery by the seller // delivery by the seller
NonIntegratedTPLType TPLIntegrationType = "non_integrated" NonIntegratedTPLType TPLIntegrationType = "non_integrated"
// Russian Post delivery scheme
HybrydTPLType TPLIntegrationType = "hybryd"
) )
type DetailsDeliveryItemName string type DetailsDeliveryItemName string
@@ -346,7 +333,7 @@ const (
ServiceProcessingIdentifiedSurplus DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedSurplus" ServiceProcessingIdentifiedSurplus DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedSurplus"
ServiceProcessingIdentifiedDiscrepancies DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedDiscrepancies" ServiceProcessingIdentifiedDiscrepancies DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedDiscrepancies"
ServiceItemInternetSiteAdvertising DetailsServiceItemName = "MarketplaceServiceItemInternetSiteAdvertising" ServiceItemInternetSiteAdvertising DetailsServiceItemName = "MarketplaceServiceItemInternetSiteAdvertising"
ServiceItemPremiumSubscribtion DetailsServiceItemName = "MarketplaceServiceItemPremiumSubscribtion" ServiceItemPremiumSubscribtion DetailsServiceItemName = "MarketplaceServiceItemSubscribtionPremium"
AgencyFeeAggregator3PLGlobalItem DetailsServiceItemName = "MarketplaceAgencyFeeAggregator3PLGlobalItem" AgencyFeeAggregator3PLGlobalItem DetailsServiceItemName = "MarketplaceAgencyFeeAggregator3PLGlobalItem"
) )
@@ -458,18 +445,25 @@ const (
type GetFBSReturnsFilterStatus string type GetFBSReturnsFilterStatus string
const ( const (
Moving GetFBSReturnsFilterStatus = "moving"
ReturnedToSeller GetFBSReturnsFilterStatus = "returned_to_seller" ReturnedToSeller GetFBSReturnsFilterStatus = "returned_to_seller"
WaitingForSeller GetFBSReturnsFilterStatus = "waiting_for_seller" WaitingForSeller GetFBSReturnsFilterStatus = "waiting_for_seller"
AcceptedFromCustomer GetFBSReturnsFilterStatus = "accepted_from_customer" AcceptedFromCustomer GetFBSReturnsFilterStatus = "accepted_from_customer"
CancelledWithCompensation GetFBSReturnsFilterStatus = "cancelled_with_compensation" CancelledWithCompensation GetFBSReturnsFilterStatus = "cancelled_with_compensation"
ReadyForShipment GetFBSReturnsFilterStatus = "ready_for_shipment" ReadyForShipment GetFBSReturnsFilterStatus = "ready_for_shipment"
Disposing GetFBSReturnsFilterStatus = "disposing"
Disposed GetFBSReturnsFilterStatus = "disposed"
ArrivedForResale GetFBSReturnsFilterStatus = "arrived_for_resale"
MovingToResale GetFBSReturnsFilterStatus = "moving_to_resale"
) )
type GetFBOReturnsFilterStatus string type GetFBOReturnsFilterStatus string
const ( const (
GetFBOReturnsFilterStatusReturnedToOzon GetFBOReturnsFilterStatus = "ReturnedToOzon" GetFBOReturnsFilterStatusCreated GetFBOReturnsFilterStatus = "Created"
GetFBOReturnsFilterStatusCancelled GetFBOReturnsFilterStatus = "Cancelled" GetFBOReturnsFilterStatusReturnedToOzon GetFBOReturnsFilterStatus = "ReturnedToOzon"
GetFBOReturnsFilterStatusCancelled GetFBOReturnsFilterStatus = "Cancelled"
GetFBOReturnsFilterStatusCancelledWithCompensation GetFBOReturnsFilterStatus = "CancelledWithCompensation"
) )
type GetFBOReturnsReturnStatus string type GetFBOReturnsReturnStatus string
@@ -567,6 +561,12 @@ const (
// financial report // financial report
ReportTypeSellerFinance ReportType = "SELLER_FINANCE" ReportTypeSellerFinance ReportType = "SELLER_FINANCE"
// report on sales to legal entities
ReportTypeDocB2BSales ReportType = "DOCUMENT_B2B_SALES"
// settlement report
ReportTypeMutualSettlement ReportType = "MUTUAL_SETTLEMENT"
) )
type ReportInfoStatus string type ReportInfoStatus string
@@ -679,3 +679,257 @@ const (
// Check is failed // Check is failed
MandatoryMarkStatusFailed MandatoryMarkStatus = "failed" MandatoryMarkStatusFailed MandatoryMarkStatus = "failed"
) )
type GetCarriageStatus string
const (
// acceptance in progress
GetCarriageStatusReceived GetCarriageStatus = "received"
// closed after acceptance
GetCarriageStatusClosed GetCarriageStatus = "closed"
GetCarriageStatusSended GetCarriageStatus = "sended"
GetCarriageStatusCancelled GetCarriageStatus = "cancelled"
)
type TransactionOperationService string
const (
// return of unclaimed products from the customer to the warehouse
TransactionNotDelivered TransactionOperationService = "MarketplaceNotDeliveredCostItem"
// return from the customer to the warehouse after delivery
TransactionReturnAfterDelivery TransactionOperationService = "TransactionOperationServiceNotDelivered"
// product delivery to the customer
TransactionDelivery TransactionOperationService = "MarketplaceDeliveryCostItem"
// purchasing reviews on the platform
TransactionSaleReviews TransactionOperationService = "MarketplaceSaleReviewsItem"
// products delivery to the Ozon warehouse (cross docking)
TransactionItemAdForSupplierLogistic TransactionOperationService = "ItemAdvertisementForSupplierLogistic"
// product placement service
TransactionServiceStorageItem TransactionOperationService = "OperationMarketplaceServiceStorage"
// products promotion
TransactionMarketingActionCost TransactionOperationService = "MarketplaceMarketingActionCostItem"
// promotion and selling on an instalment plan
TransactionServiceItemInstallment TransactionOperationService = "MarketplaceServiceItemInstallment"
// mandatory products labeling
TransactionServiceMarkingItems TransactionOperationService = "MarketplaceServiceItemMarkingItems"
// flexible payment schedule
TransactionServiceFlexiblePaymentSchedule TransactionOperationService = "MarketplaceServiceItemFlexiblePaymentSchedule"
// picking up products for removal by the seller
TransactionServiceReturnFromStock TransactionOperationService = "MarketplaceServiceItemReturnFromStock"
TransactionServiceStarsMembership TransactionOperationService = "ItemAgentServiceStarsMembership"
// forwarding trade
TransactionItemAdForSupplierLogisticSeller TransactionOperationService = "ItemAdvertisementForSupplierLogisticSeller"
// last mile
TransactionServiceDeliveryToCustomer TransactionOperationService = "MarketplaceServiceItemDelivToCustomer"
// pipeline
TransactionServiceDirectFlowTrans TransactionOperationService = "MarketplaceServiceItemDirectFlowTrans"
// shipment processing
TransactionServiceDropoffFF TransactionOperationService = "MarketplaceServiceItemDropoffFF"
// shipment processing
TransactionServiceDropoffPVZ TransactionOperationService = "MarketplaceServiceItemDropoffPVZ"
// shipment processing
TransactionServiceDropoffSC TransactionOperationService = "MarketplaceServiceItemDropoffSC"
// order packaging
TransactionServiceFulfillment TransactionOperationService = "MarketplaceServiceItemFulfillment"
// picking products up by car from the seller's address (Pick-up)
TransactionServicePickup TransactionOperationService = "MarketplaceServiceItemPickup"
// return processing
TransactionServiceReturnAfterDelivToCustomer TransactionOperationService = "MarketplaceServiceItemReturnAfterDelivToCustomer"
// reverse pipeline
TransactionServiceReturnFlowTrans TransactionOperationService = "MarketplaceServiceItemReturnFlowTrans"
// cancellation processing
TransactionServiceReturnNotDelivToCustomer TransactionOperationService = "MarketplaceServiceItemReturnNotDelivToCustomer"
// unredeemed order processing
TransactionServiceReturnPartGoodsCustomer TransactionOperationService = "MarketplaceServiceItemReturnPartGoodsCustomer"
// acquiring payment
TransactionRedistributionOfAcquiringOperation TransactionOperationService = "MarketplaceRedistributionOfAcquiringOperation"
// FBS return short-term placement
TransactionServiceAtPickupPointFBS TransactionOperationService = "MarketplaceReturnStorageServiceAtThePickupPointFbsItem"
// FBS return long-term placement
TransactionServiceInWarehouseFBS TransactionOperationService = "MarketplaceReturnStorageServiceInTheWarehouseFbsItem"
// bulky products delivery
TransactionServiceDeliveryKGT TransactionOperationService = "MarketplaceServiceItemDeliveryKGT"
// logistics
TransactionServiceDirectFlowLogistic TransactionOperationService = "MarketplaceServiceItemDirectFlowLogistic"
// reverse logistics
TransactionServiceReturnFlowLogistic TransactionOperationService = "MarketplaceServiceItemReturnFlowLogistic"
// "Seller's Bonus" promotion service
TransactionServicePremiumCashbackIndPoints TransactionOperationService = "MarketplaceServicePremiumCashbackIndividualPoints"
// Premium promotion service, fixed commission
TransactionServicePremiumPromotion TransactionOperationService = "MarketplaceServicePremiumPromotion"
// withholding for product shortage
TransactionServiceWithHoldingForUndeliverableGoods TransactionOperationService = "OperationMarketplaceWithHoldingForUndeliverableGoods"
// drop-off service at the pick-up point
TransactionServiceDropoffPPZ TransactionOperationService = "MarketplaceServiceItemDropoffPPZ"
// reissue of returns at the pick-up point
TransactionServiceRedistributionReturnsPVZ TransactionOperationService = "MarketplaceServiceItemRedistributionReturnsPVZ"
// Agregator 3PL Globalagency service tariffication
TransactionServiceAgencyFeeAggregator3PLGlobal TransactionOperationService = "OperationMarketplaceAgencyFeeAggregator3PLGlobal"
)
type PaymentTypeGroupName string
const (
PaymentTypeGroupByCardOnline PaymentTypeGroupName = "by card online"
PaymentTypeGroupOzonCard PaymentTypeGroupName = "Ozon Card"
PaymentTypeGroupOzonCardAtCheckout PaymentTypeGroupName = "Ozon Card at checkout"
PaymentTypeGroupBySavedBankCardUponPickup PaymentTypeGroupName = "by saved bank card upon pick-up"
PaymentTypeGroupFasterPaymentSystem PaymentTypeGroupName = "Faster payment system"
PaymentTypeGroupOzonInstallment PaymentTypeGroupName = "Ozon Installment"
PaymentTypeGroupPaymentToCurrentAccount PaymentTypeGroupName = "payment to current account"
PaymentTypeGroupSberpay PaymentTypeGroupName = "Sberpay"
)
type VisualStatus string
const (
// dispute with the customer has been opened
VisualStatusDisputeOpened VisualStatus = "DisputeOpened"
// pending with the seller
VisualStatusOnSellerApproval VisualStatus = "OnSellerApproval"
// at the pick-up point
VisualStatusArrivedAtReturnPlace VisualStatus = "ArrivedAtReturnPlace"
// pending clarification by the seller
VisualStatusOnSellerClarification VisualStatus = "OnSellerClarification"
// pending clarification by the seller after partial compensation
VisualStatusOnSellerClarificationPartial VisualStatus = "OnSellerClarificationAfterPartialCompensation"
// partial compensation offered
VisualStatusOfferedPartial VisualStatus = "OfferedPartialCompensation"
// refund approved
VisualStatusReturnMoneyApproved VisualStatus = "ReturnMoneyApproved"
// partial compensation provided
VisualStatusPartialReturned VisualStatus = "PartialCompensationReturned"
// refund rejected, dispute isn't opened
VisualStatusCancelledDisputeNotOpen VisualStatus = "CancelledDisputeNotOpen"
// request rejected
VisualStatusRejected VisualStatus = "Rejected"
// request rejected by Ozon
VisualStatusCrmRejected VisualStatus = "CrmRejected"
// request canceled
VisualStatusCancelled VisualStatus = "Cancelled"
// request approved by the seller
VisualStatusApproved VisualStatus = "Approved"
// request approved by Ozon
VisualStatusApprovedByOzon VisualStatus = "ApprovedByOzon"
// seller received the return
VisualStatusReceivedBySeller VisualStatus = "ReceivedBySeller"
// return is on its way to the seller
VisualStatusMovingToSeller VisualStatus = "MovingToSeller"
// seller received the refund
VisualStatusReturnCompensated VisualStatus = "ReturnCompensated"
// courier is taking the return to the seller
VisualStatusReturningByCourier VisualStatus = "ReturningByCourier"
// on disposal
VisualStatusUtilizing VisualStatus = "Utilizing"
// disposed of
VisualStatusUtilized VisualStatus = "Utilized"
// customer received full refund
VisualStatusMoneyReturned VisualStatus = "MoneyReturned"
// partial refund has been approved
VisualStatusPartialInProcess VisualStatus = "PartialCompensationInProcess"
// seller opened a dispute
VisualStatusDisputeYouOpened VisualStatus = "DisputeYouOpened"
// compensation rejected
VisualStatusCompensationRejected VisualStatus = "CompensationRejected"
// support request sent
VisualStatusDisputeOpening VisualStatus = "DisputeOpening"
// awaiting your decision on compensation
VisualStatusCompensationOffered VisualStatus = "CompensationOffered"
// awaiting compensation
VisualStatusWaitingCompensation VisualStatus = "WaitingCompensation"
// an error occurred when sending the support request
VisualStatusSendingError VisualStatus = "SendingError"
// decision period has expired
VisualStatusCompensationRejectedBySla VisualStatus = "CompensationRejectedBySla"
// seller has refused compensation
VisualStatusCompensationRejectedBySeller VisualStatus = "CompensationRejectedBySeller"
// on the way to the Ozon warehouse
VisualStatusMovingToOzon VisualStatus = "MovingToOzon"
// arrived at the Ozon warehouse
VisualStatusReturnedToOzon VisualStatus = "ReturnedToOzon"
// quick refund
VisualStatusMoneyReturnedBySystem VisualStatus = "MoneyReturnedBySystem"
// awaiting shipping
VisualStatusWaitingShipment VisualStatus = "WaitingShipment"
)
type VAT string
const (
VAT0 VAT = "0"
VAT005 VAT = "0.05"
VAT007 VAT = "0.07"
VAT01 VAT = "0.1"
VAT02 VAT = "0.2"
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -13,8 +13,11 @@ type Finance struct {
} }
type ReportOnSoldProductsParams struct { type ReportOnSoldProductsParams struct {
// Time period in the `YYYY-MM` format // Month
Date string `json:"date"` Month int32 `json:"month"`
// Year
Year int32 `json:"year"`
} }
type ReportOnSoldProductsResponse struct { type ReportOnSoldProductsResponse struct {
@@ -34,7 +37,7 @@ type ReportonSoldProductsResult struct {
type ReportOnSoldProductsResultHeader struct { type ReportOnSoldProductsResultHeader struct {
// Report ID // Report ID
Id string `json:"num"` Id string `json:"number"`
// Report generation date // Report generation date
DocDate string `json:"doc_date"` DocDate string `json:"doc_date"`
@@ -43,10 +46,10 @@ type ReportOnSoldProductsResultHeader struct {
ContractDate string `json:"contract_date"` ContractDate string `json:"contract_date"`
// Offer agreement number // Offer agreement number
ContractNum string `json:"contract_num"` ContractNum string `json:"contract_number"`
// Currency of your prices // Currency of your prices
CurrencyCode string `json:"currency_code"` CurrencySysName string `json:"currency_sys_name"`
// Amount to accrue // Amount to accrue
DocAmount float64 `json:"doc_amount"` DocAmount float64 `json:"doc_amount"`
@@ -64,13 +67,13 @@ type ReportOnSoldProductsResultHeader struct {
PayerName string `json:"payer_name"` PayerName string `json:"payer_name"`
// Recipient's TIN // Recipient's TIN
RecipientINN string `json:"rcv_inn"` RecipientINN string `json:"receiver_inn"`
// Recipient's Tax Registration Reason Code (KPP) // Recipient's Tax Registration Reason Code (KPP)
RecipientKPP string `json:"rcv_kpp"` RecipientKPP string `json:"receiver_kpp"`
// Recipient's name // Recipient's name
RecipientName string `json:"rcv_name"` RecipientName string `json:"receiver_name"`
// Period start in the report // Period start in the report
StartDate string `json:"start_date"` StartDate string `json:"start_date"`
@@ -81,13 +84,28 @@ type ReportOnSoldProductsResultHeader struct {
type ReportOnSoldProductsResultRow struct { type ReportOnSoldProductsResultRow struct {
// Row number // Row number
RowNumber int32 `json:"row_number"` RowNumber int32 `json:"rowNumber"`
// Product ID // Product Information
ProductId int64 `json:"product_id"` Item ReturnOnSoldProduct `json:"item"`
// Commission including the quantity of products, discounts and extra charges.
// Ozon compensates it for the returned products
ReturnCommission ReturnCommission `json:"return_commission"`
// Percentage of sales commission by category
CommissionRatio float64 `json:"commission_ratio"`
// Delivery fee
DeliveryCommission ReturnCommission `json:"delivery_commission"`
// Seller's discounted price
SellerPricePerInstance float64 `json:"seller_price_per_instance"`
}
type ReturnOnSoldProduct struct {
// Product name // Product name
ProductName string `json:"product_name"` ProductName string `json:"name"`
// Product barcode // Product barcode
Barcode string `json:"barcode"` Barcode string `json:"barcode"`
@@ -95,65 +113,46 @@ type ReportOnSoldProductsResultRow struct {
// Product identifier in the seller's system // Product identifier in the seller's system
OfferId string `json:"offer_id"` OfferId string `json:"offer_id"`
// Sales commission by category SKU int64 `json:"sku"`
CommissionPercent float64 `json:"commission_percent"` }
// Seller's price with their discount type ReturnCommission struct {
Price float64 `json:"price"` // Amount
Amount float64 `json:"amount"`
// Selling price: the price at which the customer purchased the product. For sold products // Points for discounts
PriceSale float64 `json:"price_sale"` Bonus float64 `json:"bonus"`
// Sold for amount.
//
// Sold products cost considering the quantity and regional coefficients. Calculation is made by the sale_amount price
SaleAmount float64 `json:"sale_amount"`
// Commission for sold products, including discounts and extra charges // Commission for sold products, including discounts and extra charges
SaleCommission float64 `json:"sale_commission"` Commission float64 `json:"commission"`
// Extra charge at the expense of Ozon. // Additional payment at the expense of Ozon
// Compensation float64 `json:"compensation"`
// Amount that Ozon will compensate the seller if the Ozon discount is greater than or equal to the sales commission
SaleDiscount float64 `json:"sale_discount"`
// Total accrual for the products sold. // Price per item
// PricePerInstance float64 `json:"price_per_instance"`
// Amount after deduction of sales commission, application of discounts and extra charges
SalePriceSeller float64 `json:"sale_price_seller"`
// Quantity of products sold at the price_sale price // Product quantity
SaleQuantity int32 `json:"sale_qty"` Quantity int32 `json:"quantity"`
// Price at which the customer purchased the product. For returned products // Ozon referral fee
ReturnSale float64 `json:"return_sale"` StandardFee float64 `json:"standard_fee"`
// Cost of returned products, taking into account the quantity and regional coefficients. // Payouts on partner loyalty mechanics: green prices
// Calculation is carried out at the return_sale price BankCoinvestment float64 `json:"bank_coinvestment"`
ReturnAmount float64 `json:"return_amount"`
// Commission including the quantity of products, discounts and extra charges. // Payouts on partner loyalty mechanics: stars
// Ozon compensates it for the returned products Stars float64 `json:"stars"`
ReturnCommission float64 `json:"return_commission"`
// Extra charge at the expense of Ozon. // Total accrual
// Total float64 `json:"total"`
// Amount of the discount at the expense of Ozon on returned products.
// Ozon will compensate it to the seller if the Ozon discount is greater than or equal to the sales commission
ReturnDiscount float64 `json:"return_discount"`
// Amount charged to the seller for returned products after deducing sales commissions, applying discounts and extra charges
ReturnPriceSeller float64 `json:"return_price_seller"`
// Quantity of returned products
ReturnQuantity int32 `json:"return_qty"`
} }
// Returns information on products sold and returned within a month. Canceled or non-purchased products are not included. // Returns information on products sold and returned within a month. Canceled or non-purchased products are not included.
// //
// Report is returned no later than the 5th day of the next month // Report is returned no later than the 5th day of the next month
func (c Finance) ReportOnSoldProducts(ctx context.Context, params *ReportOnSoldProductsParams) (*ReportOnSoldProductsResponse, error) { func (c Finance) ReportOnSoldProducts(ctx context.Context, params *ReportOnSoldProductsParams) (*ReportOnSoldProductsResponse, error) {
url := "/v1/finance/realization" url := "/v2/finance/realization"
resp := &ReportOnSoldProductsResponse{} resp := &ReportOnSoldProductsResponse{}
@@ -306,10 +305,10 @@ type ListTransactionsResult struct {
// Transactions infromation // Transactions infromation
Operations []ListTransactionsResultOperation `json:"operations"` Operations []ListTransactionsResultOperation `json:"operations"`
// Number of pages // Number of pages. If 0, there are no more pages
PageCount int64 `json:"page_count"` 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"` RowCount int64 `json:"row_count"`
} }
@@ -363,11 +362,7 @@ type ListTransactionsResultOperationItem struct {
} }
type ListTransactionsResultOperationPosting struct { type ListTransactionsResultOperationPosting struct {
// Delivery scheme: // Delivery scheme
// - FBO — delivery to Ozon warehouse
// - FBS — delivery from seller's warehouse
// - RFBS — delivery service of seller's choice
// - Crossborder — delivery from abroad
DeliverySchema string `json:"delivery_schema"` DeliverySchema string `json:"delivery_schema"`
// Date the product was accepted for processing // Date the product was accepted for processing
@@ -382,7 +377,7 @@ type ListTransactionsResultOperationPosting struct {
type ListTransactionsResultOperationService struct { type ListTransactionsResultOperationService struct {
// Service name // Service name
Name string `json:"name"` Name TransactionOperationService `json:"name"`
// Price // Price
Price float64 `json:"price"` Price float64 `json:"price"`
@@ -404,3 +399,57 @@ func (c Finance) ListTransactions(ctx context.Context, params *ListTransactionsP
return resp, nil return resp, nil
} }
type GetReportParams struct {
// Time period in the YYYY-MM format
Date string `json:"date"`
// Response language
Language string `json:"language"`
}
type ReportResponse struct {
core.CommonResponse
// Method result
Result ReportResult `json:"result"`
}
type ReportResult struct {
// Unique report identifier
Code string `json:"code"`
}
// Use the method to get mutual settlements report.
// Matches the Finance → Documents → Analytical reports → Mutual
// settlements report section in your personal account.
func (c Finance) MutualSettlements(ctx context.Context, params *GetReportParams) (*ReportResponse, error) {
url := "/v1/finance/mutual-settlement"
resp := &ReportResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
// Use the method to get sales to legal entities report.
// Matches the Finance → Documents → Legal
// entities sales register section in your personal account.
func (c Finance) SalesToLegalEntities(ctx context.Context, params *GetReportParams) (*ReportResponse, error) {
url := "/v1/finance/mutual-settlement"
resp := &ReportResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}

View File

@@ -23,52 +23,67 @@ func TestReportOnSoldProducts(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ReportOnSoldProductsParams{ &ReportOnSoldProductsParams{
Date: "2022-09", Month: 9,
Year: 2022,
}, },
`{ `{
"result": { "result": {
"header": { "header": {
"doc_date": "2022-09-22", "contract_date": "string",
"num": "string", "contract_number": "string",
"start_date": "2022-09-02", "currency_sys_name": "string",
"stop_date": "2022-09-22", "doc_amount": 0,
"contract_date": "2022-09-02", "doc_date": "string",
"contract_num": "string", "number": "string",
"payer_name": "string", "payer_inn": "string",
"payer_inn": "string", "payer_kpp": "string",
"payer_kpp": "string", "payer_name": "string",
"rcv_name": "string", "receiver_inn": "string",
"rcv_inn": "string", "receiver_kpp": "string",
"rcv_kpp": "string", "receiver_name": "string",
"doc_amount": 1, "start_date": "string",
"vat_amount": 1, "stop_date": "string",
"currency_code": "string" "vat_amount": 0
}, },
"rows": [ "rows": [
{ {
"row_number": 0, "commission_ratio": 0,
"product_id": 0, "delivery_commission": {
"product_name": "string", "amount": 0,
"offer_id": "string", "bonus": 0,
"barcode": "string", "commission": 0,
"price": 0, "compensation": 0,
"commission_percent": 0, "price_per_instance": 0,
"price_sale": 0, "quantity": 0,
"sale_qty": 0, "standard_fee": 0,
"sale_amount": 0, "bank_coinvestment": 0,
"sale_discount": 0, "stars": 0,
"sale_commission": 0, "total": 0
"sale_price_seller": 0, },
"return_sale": 0, "item": {
"return_qty": 0, "barcode": "string",
"return_amount": 0, "name": "string",
"return_discount": 0, "offer_id": "string",
"return_commission": 0, "sku": 0
"return_price_seller": 0 },
} "return_commission": {
] "amount": 0,
} "bonus": 0,
}`, "commission": 0,
"compensation": 0,
"price_per_instance": 0,
"quantity": 0,
"standard_fee": 0,
"bank_coinvestment": 0,
"stars": 0,
"total": 0
},
"rowNumber": 0,
"seller_price_per_instance": 0
}
]
}
}`,
"", "",
}, },
// Test No Client-Id or Api-Key // Test No Client-Id or Api-Key
@@ -271,3 +286,127 @@ func TestListTransactions(t *testing.T) {
} }
} }
} }
func TestMutualSettlements(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetReportParams
response string
errorMessage string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetReportParams{
Date: "2024-08",
Language: "DEFAULT",
},
`{
"result": {
"code": "string"
}
}`,
"",
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetReportParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
"Client-Id and Api-Key headers are required",
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Finance().MutualSettlements(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ReportResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode != http.StatusOK {
if resp.Message != test.errorMessage {
t.Errorf("got wrong error message: got: %s, expected: %s", resp.Message, test.errorMessage)
}
}
}
}
func TestSalesToLegalEntities(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetReportParams
response string
errorMessage string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetReportParams{
Date: "2024-08",
Language: "DEFAULT",
},
`{
"result": {
"code": "string"
}
}`,
"",
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetReportParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
"Client-Id and Api-Key headers are required",
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Finance().SalesToLegalEntities(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ReportResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode != http.StatusOK {
if resp.Message != test.errorMessage {
t.Errorf("got wrong error message: got: %s, expected: %s", resp.Message, test.errorMessage)
}
}
}
}

View File

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

View File

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

View File

@@ -10,6 +10,15 @@ const (
DefaultAPIBaseUrl = "https://api-seller.ozon.ru" DefaultAPIBaseUrl = "https://api-seller.ozon.ru"
) )
type ClientOptions struct {
client core.HttpClient
baseUri string
apiKey string
clientId string
}
type Client struct { type Client struct {
client *core.Client client *core.Client
@@ -32,6 +41,10 @@ type Client struct {
certificates *Certificates certificates *Certificates
strategies *Strategies strategies *Strategies
barcodes *Barcodes barcodes *Barcodes
passes *Passes
clusters *Clusters
quants *Quants
reviews *Reviews
} }
func (c Client) Analytics() *Analytics { func (c Client) Analytics() *Analytics {
@@ -110,10 +123,62 @@ func (c Client) Barcodes() *Barcodes {
return c.barcodes return c.barcodes
} }
func NewClient(httpClient core.HttpClient, clientId, apiKey string) *Client { func (c Client) Passes() *Passes {
coreClient := core.NewClient(httpClient, DefaultAPIBaseUrl, map[string]string{ return c.passes
"Client-Id": clientId, }
"Api-Key": apiKey,
func (c Client) Clusters() *Clusters {
return c.clusters
}
func (c Client) Quants() *Quants {
return c.quants
}
func (c Client) Reviews() *Reviews {
return c.reviews
}
type ClientOption func(c *ClientOptions)
func WithHttpClient(httpClient core.HttpClient) ClientOption {
return func(c *ClientOptions) {
c.client = httpClient
}
}
func WithURI(uri string) ClientOption {
return func(c *ClientOptions) {
c.baseUri = uri
}
}
func WithClientId(clientId string) ClientOption {
return func(c *ClientOptions) {
c.clientId = clientId
}
}
func WithAPIKey(apiKey string) ClientOption {
return func(c *ClientOptions) {
c.apiKey = apiKey
}
}
func NewClient(opts ...ClientOption) *Client {
// default values
options := &ClientOptions{
client: http.DefaultClient,
baseUri: DefaultAPIBaseUrl,
}
for _, opt := range opts {
opt(options)
}
coreClient := core.NewClient(options.client, options.baseUri, map[string]string{
"Client-Id": options.clientId,
"Api-Key": options.apiKey,
}) })
return &Client{ return &Client{
@@ -137,6 +202,10 @@ func NewClient(httpClient core.HttpClient, clientId, apiKey string) *Client {
certificates: &Certificates{client: coreClient}, certificates: &Certificates{client: coreClient},
strategies: &Strategies{client: coreClient}, strategies: &Strategies{client: coreClient},
barcodes: &Barcodes{client: coreClient}, barcodes: &Barcodes{client: coreClient},
passes: &Passes{client: coreClient},
clusters: &Clusters{client: coreClient},
quants: &Quants{client: coreClient},
reviews: &Reviews{client: coreClient},
} }
} }
@@ -164,5 +233,9 @@ func NewMockClient(handler http.HandlerFunc) *Client {
certificates: &Certificates{client: coreClient}, certificates: &Certificates{client: coreClient},
strategies: &Strategies{client: coreClient}, strategies: &Strategies{client: coreClient},
barcodes: &Barcodes{client: coreClient}, barcodes: &Barcodes{client: coreClient},
passes: &Passes{client: coreClient},
clusters: &Clusters{client: coreClient},
quants: &Quants{client: coreClient},
reviews: &Reviews{client: coreClient},
} }
} }

27
ozon/ozon_test.go Normal file
View File

@@ -0,0 +1,27 @@
package ozon
import (
"net/http"
"testing"
)
const (
apiKey = "some_key"
clientId = "some_client_id"
)
func TestNewClient(t *testing.T) {
client := NewClient(
WithAPIKey(apiKey),
WithClientId(clientId),
WithURI(DefaultAPIBaseUrl),
WithHttpClient(http.DefaultClient),
)
if client.client.Options["Api-Key"] != apiKey {
t.Errorf("expected api key: %s, but got: %s", apiKey, client.client.Options["Api-Key"])
}
if client.client.Options["Client-Id"] != clientId {
t.Errorf("expected client id: %s, but got: %s", clientId, client.client.Options["Client-Id"])
}
}

302
ozon/pass.go Normal file
View File

@@ -0,0 +1,302 @@
package ozon
import (
"context"
"net/http"
"time"
core "github.com/diphantxm/ozon-api-client"
)
type Passes struct {
client *core.Client
}
type ListPassesParams struct {
// Cursor for the next data sample
Cursor string `json:"curson"`
// Filters
Filter ListPassesFilter `json:"filter"`
// Limit on number of entries in a reply. Default value is 1000. Maximum value is 1000
Limit int32 `json:"limit"`
}
type ListPassesFilter struct {
// Filter by pass identifier
ArrivalPassIds []string `json:"arrival_pass_ids"`
// Filter by purpose of arrival:
//
// FBS_DELIVERY—delivery.
// FBS_RETURN—take out returns.
// If the parameter isn't specified, both purposes are considered.
//
// The specified purpose must be in the list of reasons for passes
ArrivalReason string `json:"arrival_reason"`
// Filter by drop-off points identifier
DropoffPointIds []int64 `json:"dropoff_point_ids"`
// true to get only active pass requests
OnlyActivePasses bool `json:"only_active_passes"`
// Filter by seller's warehouses identifier
WarehouseIds []int64 `json:"warehouse_ids"`
}
type ListPassesResponse struct {
core.CommonResponse
// List of passes
ArrivalPasses []ListPassesArrivalPass `json:"arrival_passes"`
// Cursor for the next data sample. If the parameter is empty, there is no more data
Cursor string `json:"cursor"`
}
type ListPassesArrivalPass struct {
// Pass identifier
ArrivalPassId int64 `json:"arrival_pass_id"`
// Arrival purpose
ArrivalReasons []string `json:"arrival_reasons"`
// Date and time of arrival in UTC format
ArrivalTime time.Time `json:"arrival_time"`
// Driver full name
DriverName string `json:"driver_name"`
// Driver phone number
DriverPhone string `json:"driver_phone"`
// Drop-off point identifier
DropoffPointId int64 `json:"dropoff_point_id"`
// true if the request is active
IsActive bool `json:"is_active"`
// Car license plate
VehicleLicensePlate string `json:"vehicle_license_plate"`
// Car model
VehicleModel string `json:"vehicle_model"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
func (c Passes) List(ctx context.Context, params *ListPassesParams) (*ListPassesResponse, error) {
url := "/v1/pass/list"
resp := &ListPassesResponse{}
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 CreateCarriageParams struct {
// List of passes
ArrivalPasses []CarriageArrivalPass `json:"arrival_passes"`
// Freight identifier
CarriageId int64 `json:"carriage_id"`
}
type CarriageArrivalPass struct {
// Driver full name
DriverName string `json:"driver_name"`
// Driver phone number
DriverPhone string `json:"driver_phone"`
// Car license plate
VehicleLicensePlate string `json:"vehicle_license_plate"`
// Car model
VehicleModel string `json:"vehicle_model"`
// true if you will export returns. Default is false
WithReturns bool `json:"with_returns"`
}
type CreateCarriageResponse struct {
core.CommonResponse
// Pass identifiers
ArrivalPassIds []string `json:"arrival_pass_ids"`
}
func (c Passes) CreateCarriage(ctx context.Context, params *CreateCarriageParams) (*CreateCarriageResponse, error) {
url := "/v1/carriage/pass/create"
resp := &CreateCarriageResponse{}
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 UpdateCarriageParams struct {
ArrivalPasses []UpdateCarriageArrivalPass `json:"arrival_passes"`
CarriageId int64 `json:"carriage_id"`
}
type UpdateCarriageArrivalPass struct {
CarriageArrivalPass
Id int64 `json:"id"`
}
type UpdateCarriageResponse struct {
core.CommonResponse
}
func (c Passes) UpdateCarriage(ctx context.Context, params *UpdateCarriageParams) (*UpdateCarriageResponse, error) {
url := "/v1/carriage/pass/update"
resp := &UpdateCarriageResponse{}
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 DeleteCarriageParams struct {
// Pass identifiers
ArrivalPassIds []int64 `json:"arrival_pass_ids"`
// Freight identifier
CarriageId int64 `json:"carriage_id"`
}
type DeleteCarriageResponse struct {
core.CommonResponse
}
func (c Passes) DeleteCarriage(ctx context.Context, params *DeleteCarriageParams) (*DeleteCarriageResponse, error) {
url := "/v1/carriage/pass/delete"
resp := &DeleteCarriageResponse{}
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 CreateReturnParams struct {
// Array of passes
ArrivalPasses []ReturnArrivalPass `json:"arrival_passes"`
}
type ReturnArrivalPass struct {
// Date and time of arrival in UTC format. At that time, the pass will become valid
ArrivalTime time.Time `json:"arrival_time"`
// Driver full name
DriverName string `json:"driver_name"`
// Driver phone number
DriverPhone string `json:"driver_phone"`
// Car license plate
VehicleLicensePlate string `json:"vehicle_license_plate"`
// Car model
VehicleModel string `json:"vehicle_model"`
// Drop-off point identifier for which the pass is issued
DropoffPointId int64 `json:"dropoff_point_id"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
type CreateReturnResponse struct {
core.CommonResponse
// Pass identifiers
ArrivalPassIds []string `json:"arrival_pass_ids"`
}
func (c Passes) CreateReturn(ctx context.Context, params *CreateReturnParams) (*CreateReturnResponse, error) {
url := "/v1/return/pass/create"
resp := &CreateReturnResponse{}
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 UpdateReturnParams struct {
ArrivalPasses []ReturnArrivalPass `json:"arrival_passes"`
}
type UpdateReturnResponse struct {
core.CommonResponse
// Pass identifiers
ArrivalPassIds []string `json:"arrival_pass_ids"`
}
func (c Passes) UpdateReturn(ctx context.Context, params *UpdateReturnParams) (*UpdateReturnResponse, error) {
url := "/v1/return/pass/update"
resp := &UpdateReturnResponse{}
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 DeleteReturnParams struct {
// Pass identifiers
ArrivalPassIds []int64 `json:"arrival_pass_ids"`
}
type DeleteReturnResponse struct {
core.CommonResponse
}
func (c Passes) DeleteReturn(ctx context.Context, params *DeleteReturnParams) (*DeleteReturnResponse, error) {
url := "/v1/return/pass/delete"
resp := &DeleteReturnResponse{}
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
}

431
ozon/pass_test.go Normal file
View File

@@ -0,0 +1,431 @@
package ozon
import (
"context"
"net/http"
"testing"
"time"
core "github.com/diphantxm/ozon-api-client"
)
func TestListPasses(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListPassesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListPassesParams{
Cursor: "",
Filter: ListPassesFilter{
ArrivalPassIds: []string{"string"},
ArrivalReason: "string",
DropoffPointIds: []int64{123},
OnlyActivePasses: true,
WarehouseIds: []int64{456},
},
},
`{
"arrival_passes": [
{
"arrival_pass_id": 0,
"arrival_reasons": [
"string"
],
"arrival_time": "2019-08-24T14:15:22Z",
"driver_name": "string",
"driver_phone": "string",
"dropoff_point_id": 123,
"is_active": true,
"vehicle_license_plate": "string",
"vehicle_model": "string",
"warehouse_id": 456
}
],
"cursor": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListPassesParams{},
`{
"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.Passes().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListPassesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if len(resp.ArrivalPasses) != 0 {
if resp.ArrivalPasses[0].WarehouseId != test.params.Filter.WarehouseIds[0] {
t.Errorf("warehouse id in request and response should be equal")
}
if resp.ArrivalPasses[0].DropoffPointId != test.params.Filter.DropoffPointIds[0] {
t.Errorf("dropoff point id in request and response should be equal")
}
}
}
}
func TestCreateArrivalPass(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *CreateCarriageParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&CreateCarriageParams{
ArrivalPasses: []CarriageArrivalPass{
{
DriverName: "string",
DriverPhone: "string",
VehicleLicensePlate: "string",
VehicleModel: "string",
WithReturns: true,
},
},
CarriageId: 14,
},
`{
"arrival_pass_ids": [
"154"
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&CreateCarriageParams{},
`{
"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.Passes().CreateCarriage(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CreateCarriageResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestUpdateArrivalPass(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *UpdateCarriageParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&UpdateCarriageParams{
ArrivalPasses: []UpdateCarriageArrivalPass{
{
Id: 11,
CarriageArrivalPass: CarriageArrivalPass{
DriverName: "string",
DriverPhone: "string",
VehicleLicensePlate: "string",
VehicleModel: "string",
WithReturns: true,
},
},
},
CarriageId: 14,
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&UpdateCarriageParams{},
`{
"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.Passes().UpdateCarriage(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UpdateCarriageResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestDeleteArrivalPass(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *DeleteCarriageParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&DeleteCarriageParams{
ArrivalPassIds: []int64{123},
CarriageId: 14,
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&DeleteCarriageParams{},
`{
"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.Passes().DeleteCarriage(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DeleteCarriageResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestCreateReturn(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *CreateReturnParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&CreateReturnParams{
ArrivalPasses: []ReturnArrivalPass{
{
ArrivalTime: time.Now(),
DriverName: "string",
DriverPhone: "string",
VehicleLicensePlate: "string",
VehicleModel: "string",
DropoffPointId: 11,
WarehouseId: 5,
},
},
},
`{
"arrival_pass_ids": [
"1111"
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&CreateReturnParams{},
`{
"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.Passes().CreateReturn(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CreateReturnResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestUpdateReturn(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *UpdateReturnParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&UpdateReturnParams{
ArrivalPasses: []ReturnArrivalPass{
{
ArrivalTime: time.Now(),
DriverName: "string",
DriverPhone: "string",
VehicleLicensePlate: "string",
VehicleModel: "string",
DropoffPointId: 11,
WarehouseId: 5,
},
},
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&UpdateReturnParams{},
`{
"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.Passes().UpdateReturn(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UpdateReturnResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestDeleteReturn(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *DeleteReturnParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&DeleteReturnParams{
ArrivalPassIds: []int64{456},
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&DeleteReturnParams{},
`{
"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.Passes().DeleteReturn(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DeleteReturnResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -98,7 +98,7 @@ type AddProductToPromotionParams struct {
type AddProductToPromotionProduct struct { type AddProductToPromotionProduct struct {
// Product identifier // Product identifier
ProductId float64 `json:"produt_id"` ProductId float64 `json:"product_id"`
// Promotional product price // Promotional product price
ActionPrice float64 `json:"action_price"` ActionPrice float64 `json:"action_price"`
@@ -136,7 +136,7 @@ func (c Promotions) AddToPromotion(ctx context.Context, params *AddProductToProm
resp := &AddProductToPromotionResponse{} resp := &AddProductToPromotionResponse{}
response, err := c.client.Request(ctx, http.MethodGet, url, params, resp, nil) response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -154,7 +154,7 @@ type ProductsAvailableForPromotionParams struct {
// Number of elements that will be skipped in the response. // Number of elements that will be skipped in the response.
// For example, if offset=10, the response will start with the 11th element found // For example, if offset=10, the response will start with the 11th element found
Offset float64 `json:"offset"` Offset float64 `json:"offset,omitempty"`
} }
type ProductsAvailableForPromotionResponse struct { type ProductsAvailableForPromotionResponse struct {
@@ -218,7 +218,7 @@ type ProductsInPromotionParams struct {
Limit float64 `json:"limit"` Limit float64 `json:"limit"`
// Number of elements that will be skipped in the response. For example, if offset=10, the response will start with the 11th element found // Number of elements that will be skipped in the response. For example, if offset=10, the response will start with the 11th element found
Offset float64 `json:"offset"` Offset float64 `json:"offset,omitempty"`
} }
type ProductsInPromotionResponse struct { type ProductsInPromotionResponse struct {
@@ -355,7 +355,7 @@ type ProductsAvailableForHotSalePromotionParams struct {
Limit float64 `json:"limit"` Limit float64 `json:"limit"`
// Number of elements that will be skipped in the response. For example, if offset=10, the response will start with the 11th element found // Number of elements that will be skipped in the response. For example, if offset=10, the response will start with the 11th element found
Offset float64 `json:"offset"` Offset float64 `json:"offset,omitempty"`
} }
type ProductsAvailableForHotSalePromotionResponse struct { type ProductsAvailableForHotSalePromotionResponse struct {

356
ozon/quants.go Normal file
View File

@@ -0,0 +1,356 @@
package ozon
import (
"context"
"net/http"
"time"
core "github.com/diphantxm/ozon-api-client"
)
type Quants struct {
client *core.Client
}
type ListQuantsParams struct {
// Cursor for the next data sample
Cursor string `json:"cursor"`
// Filter
Filter ListQuantsFilter `json:"filter"`
// Maximum number of values in the response
Limit int32 `json:"limit"`
// Parameter by which products will be sorted
Sort string `json:"sort"`
// Sorting direction
SortDir string `json:"sort_dir"`
}
type ListQuantsFilter struct {
// MOQ creation period
CreatedAt *ListQuantsFilterTime `json:"created_at"`
// Time for MOQ assembly
Cutoff *ListQuantsFilterTime `json:"cutoff"`
// Destination point identifier
DestinationPlaceId int64 `json:"destination_place_id"`
// MOQ inventory identifiers
InvQuantIds []string `json:"inv_quants_ids"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product name
SKUName string `json:"sku_name"`
// MOQ statuses
Statuses []string `json:"statuses"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
type ListQuantsFilterTime struct {
// Start date
From string `json:"from"`
// End date
To string `json:"to"`
}
type ListQuantsResponse struct {
core.CommonResponse
Result ListQuantsResult `json:"result"`
}
type ListQuantsResult struct {
// Cursor for the next data sample
Cursor string `json:"cursor"`
// Indication that the response returned only a part of characteristic values
HasNext bool `json:"has_next"`
// MOQs list
Quants []Quant `json:"quants"`
}
type Quant struct {
// List of available actions with MOQ
AvailableActions []string `json:"available_actions"`
// Date until which the leftover stock amount must be specified
AwaitingStockDueDate string `json:"awaiting_stock_due_date"`
// MOQ cancellation reason
CancelReason `json:"cancel_reason"`
// Seller company identifier
CompanyId int64 `json:"company_id"`
// MOQ creation date
CreatedAt string `json:"created_at"`
// Current number of shipments in the MOQ
CurrentPostingsCount int64 `json:"current_postings_count"`
// Time until which the MOQ must be assembled
Cutoff string `json:"cutoff"`
// Delivery method name
DeliveryMethod string `json:"delivery_method_name"`
// Destination point identifier
DestinationPlaceId int64 `json:"destination_place_id"`
// Destination point name
DestinationPlaceName string `json:"destination_place_name"`
// MOQ filling percentage
FillingPercent float32 `json:"filling_percent"`
// Date when the shipments start to get canceled if the MOQ is not reserved
FirstPostingCancellationDate string `json:"first_posting_cancellation_date"`
// MOQ identifier in Ozon system
Id int64 `json:"id"`
// MOQ inventory identifier
InvQuantId int64 `json:"inv_quant_id"`
// Date of the last MOQ status change
LastStatusChangeAt string `json:"last_status_change_at"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Total cost of products in the MOQ
ProductsPrice float32 `json:"products_price"`
// Start date of MOQ filling
QuantumStartDate string `json:"quantum_start_date"`
// Product SKU
SKU int64 `json:"sku"`
// Product name
SKUName string `json:"sku_name"`
// MOQ statuses
Status string `json:"status"`
// Required number of products in the MOQ
TargetPostingsCount int64 `json:"target_postings_count"`
// Delivery service name
TPLProviderName string `json:"tpl_provider_name"`
// MOQ type: box or pallet
Type string `json:"type"`
// Seller warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
// Seller warehouse name
WarehouseName string `json:"warehouse_name"`
}
type CancelReason struct {
// Identifier of MOQ cancellation reason
Id int64 `json:"cancel_reason_id"`
// Cancellation reason name
Name string `json:"cancel_reason_name"`
// Cancellation initiator
Responsible string `json:"responsible"`
}
// You can leave feedback on this method in the comments section to the discussion in the Ozon for dev community.
func (q Quants) List(ctx context.Context, params *ListQuantsParams) (*ListQuantsResponse, error) {
url := "/v1/quant/list"
resp := &ListQuantsResponse{}
response, err := q.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type GetQuantParams struct {
// MOQ inventory identifier
QuantId int64 `json:"inv_quant_id"`
}
type GetQuantResponse struct {
core.CommonResponse
// MOQ information
Result []GetQuantResult `json:"result"`
}
type GetQuantResult struct {
// Available actions
AvailableActions []string `json:"available_actions"`
// Date until which the leftover stock amount must be specified
AwaitingStockDueDate time.Time `json:"awaiting_stock_due_date"`
// Shipment cancellation reason
CancelReason CancelReason `json:"cancel_reason"`
// Current number of shipments in the MOQ
CurrentPostingsCount int64 `json:"current_postings_count"`
// Time until which the MOQ must be assembled
Cutoff time.Time `json:"cutoff"`
// Delivery method name
DeliveryMethodName string `json:"delivery_method_name"`
// Destination point identifier
DestinationPlaceId int64 `json:"destination_place_id"`
// Destination point name
DestinationPlaceName string `json:"destination_place_name"`
// MOQ filling percentage
FillingPercent float32 `json:"filling_percent"`
// Date when the shipments start to get canceled if the MOQ isn't reserved
FirstPostingCancellationDate time.Time `json:"first_posting_cancellation_date"`
// MOQ identifier
Id int64 `json:"id"`
// MOQ inventory identifier
QuantId int64 `json:"inv_quant_id"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Shipments
Postings []GetQuantResultPosting `json:"postings"`
// Link to product photo
ProductPictureURL string `json:"product_picture_url"`
// Total price of products in the MOQ
ProductsPrice float32 `json:"products_price"`
// Start date of MOQ filling
QuantumStartDate time.Time `json:"quantum_start_date"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Product name
SKUName string `json:"sku_name"`
// MOQ statuses
Status string `json:"status"`
// Required number of products in the MOQ
TargetPostingsCount int64 `json:"target_postings_count"`
// Delivery service name
TPLProviderName string `json:"tpl_provider_name"`
// MOQ type: box or pallet
Type string `json:"type"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
// Warehouse name
WarehouseName string `json:"warehouse_name"`
}
type GetQuantResultPosting struct {
// Shipment cancellation reason
CancelReason CancelReason `json:"cancel_reason"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Total price of products in the MOQ
ProductsPrice float32 `json:"products_price"`
// Status text
StatusAlias string `json:"status_alias"`
// Status identifier
StatusId int64 `json:"status_id"`
}
func (q Quants) Get(ctx context.Context, params *GetQuantParams) (*GetQuantResponse, error) {
url := "/v1/quant/get"
resp := &GetQuantResponse{}
response, err := q.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type ShipQuantParams struct {
// MOQ inventory identifier
QuantId int64 `json:"quant_id"`
}
type ShipQuantResponse struct {
core.CommonResponse
}
func (q Quants) Ship(ctx context.Context, params *ShipQuantParams) (*ShipQuantResponse, error) {
url := "/v1/quant/ship"
resp := &ShipQuantResponse{}
response, err := q.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type StatusQuantParams struct {
// MOQ inventory identifier
QuantId int64 `json:"inv_quant_id"`
}
type StatusQuantResponse struct {
core.CommonResponse
// MOQ statuses
Status string `json:"status"`
}
func (q Quants) Status(ctx context.Context, params *StatusQuantParams) (*StatusQuantResponse, error) {
url := "/v1/quant/ship"
resp := &StatusQuantResponse{}
response, err := q.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}

303
ozon/quants_test.go Normal file
View File

@@ -0,0 +1,303 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestListQuants(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListQuantsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListQuantsParams{
Cursor: "string",
Filter: ListQuantsFilter{
InvQuantIds: []string{"string"},
DestinationPlaceId: 123,
OfferId: "string",
SKUName: "string",
Statuses: []string{"unknown"},
WarehouseId: 456,
},
Limit: 10,
Sort: "string",
SortDir: "string",
},
`{
"result": {
"cursor": "string",
"has_next": true,
"quants": [
{
"available_actions": [
"string"
],
"awaiting_stock_due_date": "2019-08-24T14:15:22Z",
"cancel_reason": {
"cancel_reason_id": 0,
"cancel_reason_name": "string",
"responsible": "string"
},
"company_id": 0,
"created_at": "2019-08-24T14:15:22Z",
"current_postings_count": 0,
"cutoff": "2019-08-24T14:15:22Z",
"delivery_method_name": "string",
"destination_place_id": 0,
"destination_place_name": "string",
"filling_percent": 0,
"first_posting_cancellation_date": "2019-08-24T14:15:22Z",
"id": 0,
"inv_quant_id": 0,
"last_status_change_at": "2019-08-24T14:15:22Z",
"offer_id": "string",
"products_price": 0,
"quantum_start_date": "2019-08-24T14:15:22Z",
"sku": 0,
"sku_name": "string",
"status": "unknown",
"target_postings_count": 0,
"tpl_provider_name": "string",
"type": "string",
"warehouse_id": 0,
"warehouse_name": "string"
}
]
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListQuantsParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Quants().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListQuantsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetQuant(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetQuantParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetQuantParams{
QuantId: 456,
},
`{
"result": [
{
"available_actions": [
"string"
],
"awaiting_stock_due_date": "2019-08-24T14:15:22Z",
"cancel_reason": {
"cancel_reason_id": 0,
"cancel_reason_name": "string",
"responsible": "string"
},
"current_postings_count": 0,
"cutoff": "2019-08-24T14:15:22Z",
"delivery_method_name": "string",
"destination_place_id": 0,
"destination_place_name": "string",
"filling_percent": 0,
"first_posting_cancellation_date": "2019-08-24T14:15:22Z",
"id": 0,
"inv_quant_id": 0,
"offer_id": "string",
"postings": [
{
"cancel_reason": {
"cancel_reason_id": 0,
"cancel_reason_name": "string",
"responsible": "string"
},
"posting_number": "string",
"products_price": 0,
"status_alias": "string",
"status_id": 0
}
],
"product_picture_url": "string",
"products_price": 0,
"quantum_start_date": "2019-08-24T14:15:22Z",
"sku": 0,
"sku_name": "string",
"status": "unknown",
"target_postings_count": 0,
"tpl_provider_name": "string",
"type": "string",
"warehouse_id": 0,
"warehouse_name": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetQuantParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Quants().Get(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetQuantResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestShipQuant(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ShipQuantParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ShipQuantParams{
QuantId: 456,
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ShipQuantParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Quants().Ship(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ShipQuantResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestStatusQuant(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *StatusQuantParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&StatusQuantParams{
QuantId: 456,
},
`{
"status": "unknown"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&StatusQuantParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Quants().Status(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &StatusQuantResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

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

View File

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

View File

@@ -450,7 +450,7 @@ func (c Reports) GetProducts(ctx context.Context, params *GetProductsReportParam
type GetReturnsReportParams struct { type GetReturnsReportParams struct {
// Filter // Filter
Filter GetReturnsReportsFilter `json:"filter"` Filter *GetReturnsReportsFilter `json:"filter,omitempty"`
// Default: "DEFAULT" // Default: "DEFAULT"
// Response language: // Response language:
@@ -463,8 +463,15 @@ type GetReturnsReportsFilter struct {
// Order delivery scheme: fbs — delivery from seller's warehouse // Order delivery scheme: fbs — delivery from seller's warehouse
DeliverySchema string `json:"delivery_schema"` DeliverySchema string `json:"delivery_schema"`
// Order identifier // Date from which the data is displayed in the report.
OrderId int64 `json:"order_id"` //
// Available for the last three months only
DateFrom time.Time `json:"date_from"`
// Date up to which the data is displayed in the report.
//
// Available for the last three months only
DateTo time.Time `json:"date_to"`
// Order status // Order status
Status string `json:"status"` Status string `json:"status"`
@@ -473,20 +480,13 @@ type GetReturnsReportsFilter struct {
type GetReturnsReportResponse struct { type GetReturnsReportResponse struct {
core.CommonResponse core.CommonResponse
// Method result
Result GetReturnReportResult `json:"result"`
}
type GetReturnReportResult struct {
// Unique report identifier. The report is available for downloading within 3 days after making a request. // Unique report identifier. The report is available for downloading within 3 days after making a request.
Code string `json:"code"` Code string `json:"code"`
} }
// The report contains information about returned products that were accepted from the customer, ready for pickup, or delivered to the seller. // Method for getting a report on FBO and FBS returns
//
// The method is only suitable for orders shipped from the seller's warehouse
func (c Reports) GetReturns(ctx context.Context, params *GetReturnsReportParams) (*GetReturnsReportResponse, error) { func (c Reports) GetReturns(ctx context.Context, params *GetReturnsReportParams) (*GetReturnsReportResponse, error) {
url := "/v1/report/returns/create" url := "/v2/report/returns/create"
resp := &GetReturnsReportResponse{} resp := &GetReturnsReportResponse{}
@@ -501,7 +501,7 @@ func (c Reports) GetReturns(ctx context.Context, params *GetReturnsReportParams)
type GetShipmentReportParams struct { type GetShipmentReportParams struct {
// Filter // Filter
Filter GetShipmentReportFilter `json:"filter"` Filter *GetShipmentReportFilter `json:"filter,omitempty"`
// Default: "DEFAULT" // Default: "DEFAULT"
// Response language: // Response language:
@@ -526,10 +526,10 @@ type GetShipmentReportFilter struct {
OfferId string `json:"offer_id"` OfferId string `json:"offer_id"`
// Order processing start date and time // Order processing start date and time
ProcessedAtFrom time.Time `json:"processed_at_from"` ProcessedAtFrom *core.TimeFormat `json:"processed_at_from,omitempty"`
// Time when the order appeared in your personal account // Time when the order appeared in your personal account
ProcessedAtTo time.Time `json:"processed_at_to"` ProcessedAtTo *core.TimeFormat `json:"processed_at_to,omitempty"`
// Product identifier in the Ozon system, SKU // Product identifier in the Ozon system, SKU
SKU []int64 `json:"sku"` SKU []int64 `json:"sku"`

View File

@@ -375,14 +375,12 @@ func TestGetReturnsReport(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetReturnsReportParams{ &GetReturnsReportParams{
Filter: GetReturnsReportsFilter{ Filter: &GetReturnsReportsFilter{
DeliverySchema: "fbs", DeliverySchema: "fbs",
}, },
}, },
`{ `{
"result": { "code": "REPORT_seller_products_924336_1720170405_a9ea2f27-a473-4b13-99f9-d0cfcb5b1a69"
"code": "d55f4517-8347-4e24-9d93-d6e736c1c07c"
}
}`, }`,
}, },
// Test No Client-Id or Api-Key // Test No Client-Id or Api-Key
@@ -407,16 +405,12 @@ func TestGetReturnsReport(t *testing.T) {
continue continue
} }
compareJsonResponse(t, test.response, &GetReturnsReportResponse{})
if resp.StatusCode != test.statusCode { if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", 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.StatusCode == http.StatusOK {
if resp.Result.Code == "" { compareJsonResponse(t, test.response, &GetReturnsReportResponse{})
t.Errorf("Code cannot be empty")
}
} }
} }
} }
@@ -435,10 +429,10 @@ func TestGetShipmentReport(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetShipmentReportParams{ &GetShipmentReportParams{
Filter: GetShipmentReportFilter{ Filter: &GetShipmentReportFilter{
DeliverySchema: []string{"fbs", "fbo", "crossborder"}, DeliverySchema: []string{"fbs", "fbo", "crossborder"},
ProcessedAtFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-09-02T17:10:54.861Z"), ProcessedAtFrom: core.NewTimeFormat(core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-09-02T17:10:54.861Z"), "2006-01-02T15:04:05Z"),
ProcessedAtTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-02T17:10:54.861Z"), ProcessedAtTo: core.NewTimeFormat(core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-02T17:10:54.861Z"), "2006-01-02T15:04:05Z"),
}, },
}, },
`{ `{

View File

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

View File

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

334
ozon/reviews.go Normal file
View File

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

416
ozon/reviews_test.go Normal file
View File

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

View File

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

View File

@@ -48,6 +48,9 @@ type GetListOfWarehousesResult struct {
// Indication that the warehouse accepts bulky products // Indication that the warehouse accepts bulky products
IsKGT bool `json:"is_kgt"` IsKGT bool `json:"is_kgt"`
// true if the warehouse handles economy products
IsEconomy bool `json:"is_economy"`
// Indication that warehouse schedule can be changed // Indication that warehouse schedule can be changed
IsTimetableEditable bool `json:"is_timetable_editable"` IsTimetableEditable bool `json:"is_timetable_editable"`
@@ -85,7 +88,8 @@ type GetListOfWarehousesResultFirstMile struct {
FirstMileType string `json:"first_mile_type"` 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) { func (c Warehouses) GetListOfWarehouses(ctx context.Context) (*GetListOfWarehousesResponse, error) {
url := "/v1/warehouse/list" url := "/v1/warehouse/list"
@@ -102,7 +106,7 @@ func (c Warehouses) GetListOfWarehouses(ctx context.Context) (*GetListOfWarehous
type GetListOfDeliveryMethodsParams struct { type GetListOfDeliveryMethodsParams struct {
// Search filter for delivery methods // Search filter for delivery methods
Filter GetListOfDeliveryMethodsFilter `json:"filter"` Filter *GetListOfDeliveryMethodsFilter `json:"filter,omitempty"`
// Number of items in a response. Maximum is 50, minimum is 1 // Number of items in a response. Maximum is 50, minimum is 1
Limit int64 `json:"limit"` Limit int64 `json:"limit"`
@@ -158,6 +162,9 @@ type GetListOfDeliveryMethodsResult struct {
// Delivery service identifier // Delivery service identifier
ProviderId int64 `json:"provider_id"` 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: // Delivery method status:
// - NEW—created, // - NEW—created,
// - EDITED—being edited, // - EDITED—being edited,
@@ -181,7 +188,64 @@ func (c Warehouses) GetListOfDeliveryMethods(ctx context.Context, params *GetLis
resp := &GetListOfDeliveryMethodsResponse{} resp := &GetListOfDeliveryMethodsResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, nil) response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type ListForShippingParams struct {
// Supply type
FilterBySupplyType []string `json:"filter_by_supply_type"`
// Search by warehouse name. To search for pick-up points, specify the full name
Search string `json:"search"`
}
type ListForShippingResponse struct {
core.CommonResponse
// Warehouse search result
Search []ListForShippingSearch `json:"search"`
}
type ListForShippingSearch struct {
// Warehouse address
Address string `json:"address"`
// Warehouse coordinates
Coordinates Coordinates `json:"coordinates"`
// Warehouse name
Name string `json:"name"`
// Identifier of the warehouse, pick-up point, or sorting center
WarehouseId int64 `json:"warehouse_id"`
// Type of warehouse, pick-up point, or sorting center
WarehouseType string `json:"warehouse_type"`
}
type Coordinates struct {
// Latitude
Latitude float64 `json:"latitude"`
// Longitude
Longitude float64 `json:"longitude"`
}
// Use the method to find sorting centres, pick-up points, and drop-off points available for cross-docking and direct supplies.
//
// You can view the addresses of all points on the map and in a table in the Knowledge Base.
func (c Warehouses) ListForShipping(ctx context.Context, params *ListForShippingParams) (*ListForShippingResponse, error) {
url := "/v1/warehouse/fbo/list"
resp := &ListForShippingResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -21,27 +21,37 @@ func TestGetListOfWarehouses(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{ `{
"result": [ "result": [
{ {
"warehouse_id": 15588127982000, "warehouse_id": 1020000177886000,
"name": "Proffi (Панорама Групп)", "name": "This is a test",
"is_rfbs": false "is_rfbs": false,
}, "has_entrusted_acceptance": false,
{ "first_mile_type": {
"warehouse_id": 22142605386000, "dropoff_point_id": "",
"name": "Склад на производственной", "dropoff_timeslot_id": 0,
"is_rfbs": true "first_mile_is_changing": false,
}, "first_mile_type": ""
{ },
"warehouse_id": 22208673494000, "is_kgt": false,
"name": "Тест 37349", "can_print_act_in_advance": false,
"is_rfbs": true "min_working_days": 5,
}, "is_karantin": false,
{ "has_postings_limit": false,
"warehouse_id": 22240462819000, "postings_limit": -1,
"name": "Тест12", "working_days": [
"is_rfbs": true 1,
} 2,
3,
4,
5,
6,
7
],
"min_postings_limit": 10,
"is_timetable_editable": true,
"status": "disabled"
}
] ]
}`, }`,
}, },
@@ -99,7 +109,7 @@ func TestGetListOfDeliveryMethods(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetListOfDeliveryMethodsParams{ &GetListOfDeliveryMethodsParams{
Filter: GetListOfDeliveryMethodsFilter{ Filter: &GetListOfDeliveryMethodsFilter{
WarehouseId: 15588127982000, WarehouseId: 15588127982000,
}, },
Limit: 100, Limit: 100,
@@ -117,7 +127,8 @@ func TestGetListOfDeliveryMethods(t *testing.T) {
"template_id": 0, "template_id": 0,
"warehouse_id": 15588127982000, "warehouse_id": 15588127982000,
"created_at": "2019-04-04T15:22:31.048202Z", "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 "has_next": false
@@ -169,3 +180,65 @@ func TestGetListOfDeliveryMethods(t *testing.T) {
} }
} }
} }
func TestListForShipping(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListForShippingParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListForShippingParams{
FilterBySupplyType: []string{"CREATE_TYPE_UNKNOWN"},
Search: "string",
},
`{
"search": [
{
"address": "string",
"coordinates": {
"latitude": 0,
"longitude": 0
},
"name": "string",
"warehouse_id": 0,
"warehouse_type": "WAREHOUSE_TYPE_UNKNOWN"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListForShippingParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Warehouses().ListForShipping(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListForShippingResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}