82 Commits

Author SHA1 Message Date
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
Kirill
1494ff5905 Better tests (check that response match the structure) (#52)
Trailing zeros were removed from time in responses because of json marshaling features
2023-12-11 03:19:12 +03:00
Kirill
922e2de8b0 Update December 7, 2023 (#51) 2023-12-09 16:05:52 +03:00
Kirill
af7c167edf Update November 30, 2023 (#50)
Method for RFBS returns operations
2023-11-30 18:16:34 +03:00
Kirill
ba8f4ca1b2 Update November 9, 2023 (#49) 2023-11-11 16:05:36 +03:00
Kirill
52b18252b1 Update November 8, 2023 (#48) 2023-11-11 15:37:26 +03:00
Kirill
b496767c5b Update October 30, 2023 (#47) 2023-10-31 01:44:56 +03:00
Kirill
40dd5b86a8 Update October 26, 2023 (#46) 2023-10-27 20:06:33 +03:00
Antares
e5f2007a8e Fix fields in Finance methods (#45)
Fixed operation type field in getting transaction list
Fixed fields in params for getting finance realization report
2023-10-26 14:48:31 +03:00
Kirill
40d9fc32cb Update October 19, 2023 (#44) 2023-10-20 00:58:22 +03:00
Kirill
b07968d280 update/6-october-2023 - New Categories' methods version (#43) 2023-10-08 19:02:14 +03:00
Kirill
699d210296 update/4-october-2023 - Barcodes (#42) 2023-10-08 18:27:39 +03:00
Kirill
07d38a8456 remove deprecated method Delete Polygon (#41) 2023-09-05 23:11:43 +03:00
Kirill
0f1d0410bc update some methods description (#40) 2023-09-05 23:06:04 +03:00
Kirill
6d4d97e3c8 added comments on restrictions for seller without Premium subscription (#39) 2023-08-25 16:38:38 +03:00
Kirill
85543f45b0 update comments on price in Update Price params (#38) 2023-08-25 16:36:25 +03:00
Kirill
854d110ab1 Configure HttpClient, added context parameter to all methods (#37)
Context is needed to limit time of execution of a method. Previously, context was passed to structure and it was stored inside structure which is a bad practice. Now we need to pass context to function, which is a best practice
2023-08-05 13:50:34 +03:00
Kirill
018d40e641 Update August 2, 2023 (#34) 2023-08-03 21:02:08 +03:00
Kirill
72b25b673d fix tests (#33) 2023-08-02 01:57:08 +03:00
Kirill
cb24f19e83 fbs_sku and fbo_sku are now just sku (#32) 2023-08-02 01:19:21 +03:00
Kirill
588f4748a9 Update 28 July, 2023 (#31) 2023-07-31 00:20:07 +03:00
Kirill
651c39595f add tests for notification server errors (#30) 2023-07-29 02:59:03 +03:00
Kirill
6c1a5e35c0 enhance tests for notification server
enhance tests for notification server
2023-07-29 01:37:01 +03:00
diPhantxm
580a752012 enhance tests for notification server 2023-07-29 01:29:38 +03:00
diPhantxm
83fd8cf825 fix examlpe for notification server 2023-07-27 03:20:32 +03:00
Kirill
ebbc21b618 Notification Server
Push-Notifications
2023-07-27 02:51:36 +03:00
diPhantxm
f53b573d62 fix tests 2023-07-27 02:49:55 +03:00
diPhantxm
eb0ce6feb6 update readme 2023-07-27 00:44:36 +03:00
diPhantxm
9a41bb1196 add notification server, tests 2023-07-27 00:23:47 +03:00
diPhantxm
e76b9f3961 add notification server message types 2023-07-26 04:18:24 +03:00
diPhantxm
add4202b3e added new response fields to GetProductPriceInfo: result.items.acquiring, result.items.commissions.sales_percent_fbo/fbs 2023-07-25 17:41:27 +03:00
diPhantxm
7beee39eb2 Added the prices.price_strategy_enabled parameter to the method request 2023-07-20 02:48:09 +03:00
diPhantxm
baeeef9b46 fix json names for GetDigitalActResponse 2023-07-15 03:02:45 +03:00
diPhantxm
336c49baa4 Added the response example to TestGetLabeling 2023-07-15 03:02:45 +03:00
diPhantxm
f11ccb4714 updated the schemas and examples for method responses: methods return the result in binary format 2023-07-15 03:02:45 +03:00
diPhantxm
1958dfb94e Added a method for getting a barcode from the /v2/posting/fbs/act/get-barcode response in text format 2023-07-12 19:14:48 +03:00
diPhantxm
2ca20d9b12 Added a method for getting a barcode used during product shipment 2023-07-12 19:14:48 +03:00
Anton Salnikov
eea0f99066 Change address field tag 2023-07-09 19:54:28 +03:00
diPhantxm
7adaa92ad4 Specify enum values for status in GetFBOReturns method 2023-07-06 19:48:33 +03:00
diPhantxm
f54aa64b63 Update GetFBSReturns to v3. Add exemplar_id and return_barcode fields to method reponse 2023-07-06 19:39:54 +03:00
diPhantxm
e7c5dc320e fix params field in GetReportsListResultReport 2023-06-24 02:06:18 +03:00
diPhantxm
006ecdf877 commmented all data types and methods 2023-06-23 23:23:23 +03:00
diPhantxm
f81e4ed350 flat structures 2023-06-23 20:57:38 +03:00
Kirill
e7290069f5 Create LICENSE
Add MIT License
2023-06-20 23:33:02 +03:00
diphantxm
422f959b9f add prr option to some methods 2023-06-16 21:52:49 +03:00
diphantxm
ac99c5ba2f added new status to getting act list filter, updated descriptions, changed test according to examples 2023-06-16 21:35:33 +03:00
diPhantxm
e60a3f1ca2 add method for getting ozon warehouses workload 2023-06-02 18:55:45 +03:00
diPhantxm
2f1dbd5c00 add method for updating product characteristics 2023-06-02 18:55:33 +03:00
diPhantxm
387af0e30e add pricing strategy methods 2023-04-24 01:03:24 +03:00
diPhantxm
c1e7f2b370 add optional paramter with_details to request and details to response in GetFinancial in reports 2023-04-24 01:03:24 +03:00
diPhantxm
a1c92ae26f change price index to multiple indexes in product/info/prices 2023-04-12 01:32:24 +03:00
diPhantxm
08180d901c update posting methods status/substatus, changed price index to multiple indexes in products responses 2023-04-12 01:32:24 +03:00
diPhantxm
838f28d3d9 fix 2023-04-11 23:20:46 +03:00
diPhantxm
226f40275a add supply order methods 2023-04-11 23:18:10 +03:00
Kirill
4139692ac3 Merge pull request #5 from diPhantxm/refactor-enum-types
add enum types where possible
2023-04-02 12:54:17 +03:00
diPhantxm
3507be6ec2 add enum types where possible 2023-04-02 12:52:55 +03:00
Kirill
9990719060 Merge pull request #4 from diPhantxm/feature/add-content-type-to-request
add Content-Type to request header
2023-03-25 17:22:33 +03:00
diPhantxm
18470fcae5 add Content-Type to request header 2023-03-25 17:18:20 +03:00
diPhantxm
3af6ad40c5 add methods for quality certificates 2023-03-23 23:41:02 +03:00
diPhantxm
89816cb239 add methods for chats with customers 2023-03-23 22:45:37 +03:00
diPhantxm
3f1f0711c8 add methods for brands 2023-03-23 21:55:02 +03:00
diPhantxm
6b921ad101 add methods for invoices 2023-03-23 21:45:02 +03:00
diPhantxm
021a577bf9 add methods for polygons 2023-03-23 00:22:27 +03:00
diPhantxm
c1661f8cb9 add methods for ozon attributes and characteristics 2023-03-21 19:31:52 +03:00
diPhantxm
098b257746 add all methods for cancellations, add methods for getting fbo shipment details 2023-03-20 02:36:46 +03:00
58 changed files with 16251 additions and 2994 deletions

View File

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

View File

@@ -1,9 +1,9 @@
# Supported Endpoints
## Ozon attributes and characteristics
- [ ] Product category tree
- [ ] Category characteristics list
- [ ] Characteristics value directory
- [x] Product category tree
- [x] Category characteristics list
- [x] Characteristics value directory
## Uploading and updating products
- [x] Create or update a product
@@ -51,36 +51,35 @@
- [x] Decline a discount request
## Brand certificates
- [ ] List of certified brands
- [x] List of certified brands
## Quality certificates
- [ ] List of accordance types (version 1)
- [ ] List of accordance types (version 2)
- [ ] Directory of document types
- [ ] List of certified categories
- [ ] Adding certificates for products
- [ ] Link the certificate to the product
- [ ] Delete certificate
- [ ] Certificate information
- [ ] Certificates list
- [ ] Product statuses list
- [ ] List of products associated with the certificate
- [ ] Unbind products from a certificate
- [ ] Possible certificate rejection reasons
- [ ] Possible certificate statuses
- [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
- [ ] Create delivery polygon
- [ ] Link delivery method to a delivery polygon
- [ ] Delete polygon
- [x] Create delivery polygon
- [x] Link delivery method to a delivery polygon
- [x] Delete polygon
## FBO
- [x] Shipments list
- [ ] Shipment details
- [x] Shipment details
## FBS and rFBS products labeling
- [x] Validate labeling codes
@@ -132,24 +131,24 @@
- [x] Get information about FBS returns
## Cancellations
- [ ] Get information about a rFBS cancellation request
- [ ] Get a list of rFBS cancellation requests
- [ ] Approve a rFBS cancellation request
- [ ] Reject a rFBS cancellation request
- [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
- [ ] Chats list
- [ ] Send message
- [ ] Send file
- [ ] Chat history
- [ ] Update chat
- [ ] Create a new chat
- [ ] Mark messages as read
- [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
- [ ] Create or edit proforma invoice link
- [ ] Get a proforma invoice link
- [ ] Delete the proforma invoice link
- [x] Create or edit proforma invoice link
- [x] Get a proforma invoice link
- [x] Delete the proforma invoice link
## Reports
- [x] Report details
@@ -160,9 +159,9 @@
- [x] Returns report
- [x] Shipment report
- [x] Financial report
- [ ] Issue a report on discounted products
- [ ] Report on discounted products
- [ ] List of reports on discounted products
- [x] Issue a report on discounted products
- [x] Report on discounted products
- [x] List of reports on discounted products
## Analytics
- [x] Analytics data
@@ -170,7 +169,7 @@
## Finance
- [x] Report on sold products
- [ ] Transactions list (version 3)
- [x] Transactions list (version 3)
- [x] Total transactions sum
## Seller rating

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Kirill
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -11,6 +11,7 @@ Read full [documentation](https://docs.ozon.ru/api/seller/en/#tag/Introduction)
You can check [list of supported endpoints](ENDPOINTS.md)
## How to start
### API
Get Client-Id and Api-Key in your seller profile [here](https://seller.ozon.ru/app/settings/api-keys?locale=en)
Just add dependency to your project and you're ready to go.
@@ -22,6 +23,7 @@ A simple example on how to use this library:
package main
import (
"context"
"fmt"
"log"
"net/http"
@@ -32,10 +34,14 @@ import (
func main() {
// Create a client with your Client-Id and Api-Key
// [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
resp, err := client.Products().GetProductDetails(&ozon.GetProductDetailsParams{
resp, err := client.Products().GetProductDetails(context.Background(), &ozon.GetProductDetailsParams{
ProductId: 123456789,
})
if err != nil || resp.StatusCode != http.StatusOK {
@@ -49,6 +55,43 @@ func main() {
}
```
### Notifications
Ozon can send push-notifications to your REST server. There is an implementation of REST server that handles notifications in this library.
[Official documentation](https://docs.ozon.ru/api/seller/en/#tag/push_intro)
How to use:
```Golang
package main
import (
"log"
"github.com/diphantxm/ozon-api-client/ozon/notifications"
)
func main() {
// Create server
port := 5000
server := notifications.NewNotificationServer(port)
// Register handlers passing message type and handler itself
server.Register(notifications.ChatClosedType, func(req interface{}) error {
notification := req.(*notifications.ChatClosed)
// Do something with the notification here...
log.Printf("chat %s has been closed\n", notification.ChatId)
return nil
})
// Run server
if err := server.Run(); err != nil {
log.Printf("error while running notification server: %s", err)
}
}
```
## Contribution
If you need some endpoints ASAP, create an issue and list all the endpoints. I will add them to library soon.

View File

@@ -7,6 +7,8 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
)
type HttpClient interface {
@@ -15,36 +17,44 @@ type HttpClient interface {
type Client struct {
baseUrl string
ctx context.Context
Options map[string]string
client HttpClient
}
func NewClient(baseUrl string, opts map[string]string) *Client {
func NewClient(client HttpClient, baseUrl string, opts map[string]string) *Client {
return &Client{
Options: opts,
ctx: context.Background(),
client: http.DefaultClient,
client: client,
baseUrl: baseUrl,
}
}
func NewMockClient(handler http.HandlerFunc) *Client {
return &Client{
ctx: context.Background(),
client: NewMockHttpClient(handler),
}
}
func (c Client) newRequest(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) {
// 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
}
url = c.baseUrl + url
req, err := http.NewRequestWithContext(c.ctx, method, url, bytes.NewBuffer(bodyJson))
uri, err = url.JoinPath(c.baseUrl, uri)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, method, uri, bytes.NewBuffer(bodyJson))
if err != nil {
return nil, err
}
@@ -56,16 +66,11 @@ func (c Client) newRequest(method string, url string, body interface{}) (*http.R
return req, nil
}
func (c Client) Request(method string, path string, req, resp interface{}) (*Response, error) {
httpReq, err := c.newRequest(method, path, req)
func (c Client) Request(ctx context.Context, method string, path string, req, resp interface{}, options map[string]string) (*Response, error) {
httpReq, err := c.newRequest(ctx, method, path, req)
if err != nil {
return nil, err
}
rawQuery, err := buildRawQuery(httpReq, req)
if err != nil {
return nil, err
}
httpReq.URL.RawQuery = rawQuery
httpResp, err := c.client.Do(httpReq)
if err != nil {

View File

@@ -1,8 +1,14 @@
package core
import (
"context"
"net/http"
"testing"
"time"
)
const (
testTimeout = 5 * time.Second
)
type TestRequestRequest struct {
@@ -55,10 +61,12 @@ func TestRequest(t *testing.T) {
c := NewMockClient(NewMockHttpHandler(test.statusCode, test.response, test.headers))
respStruct := &TestRequestResponse{}
resp, err := c.Request(http.MethodPost, "/", test.params, respStruct)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Request(ctx, http.MethodPost, "/", test.params, respStruct, nil)
if err != nil {
t.Error(err)
continue
}
if resp.StatusCode != test.statusCode {

130
core.go
View File

@@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"reflect"
"strconv"
"testing"
"time"
)
@@ -32,51 +33,103 @@ func (r Response) CopyCommonResponse(rhs *CommonResponse) {
rhs.Message = r.Message
}
func getDefaultValues(v interface{}) (map[string]string, error) {
isNil, err := isZero(v)
if err != nil {
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()
func getDefaultValues(v reflect.Value) error {
vValue := v.Elem()
vType := vValue.Type()
for i := 0; i < vType.NumField(); i++ {
field := vType.Field(i)
tag := field.Tag.Get("json")
defaultValue := field.Tag.Get("default")
if field.Type.Kind() == reflect.Slice {
// Attach any slices as query params
fieldVal := vValue.Field(i)
for j := 0; j < fieldVal.Len(); j++ {
out[tag] = fmt.Sprintf("%v", fieldVal.Index(j))
}
} 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 == "" {
switch field.Type.Kind() {
case reflect.Slice:
for j := 0; j < vValue.Field(i).Len(); j++ {
// skip if slice type is primitive
if vValue.Field(i).Index(j).Kind() != reflect.Struct {
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) {
@@ -86,23 +139,20 @@ func buildRawQuery(req *http.Request, v interface{}) (string, error) {
return query.Encode(), nil
}
values, err := getDefaultValues(v)
err := getDefaultValues(reflect.ValueOf(v))
if err != nil {
return "", err
}
for k, v := range values {
query.Add(k, v)
}
return query.Encode(), nil
}
func isZero(v interface{}) (bool, error) {
t := reflect.TypeOf(v)
func isZero(v reflect.Value) (bool, error) {
t := v.Elem().Type()
if !t.Comparable() {
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 {

View File

@@ -1,34 +1,56 @@
package core
import (
"log"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
type TestTagDefaultValueStruct struct {
TestString string `json:"test_string" default:"something"`
TestNumber int `json:"test_number" default:"12"`
type DefaultStructure struct {
EmptyField string `json:"empty_field" default:"empty_string"`
Field string `json:"field" default:"string"`
}
func TestTagDefaultValue(t *testing.T) {
testStruct := &TestTagDefaultValueStruct{}
values, err := getDefaultValues(testStruct)
if err != nil {
log.Fatalf("error when getting default values from tags: %s", err)
}
expected := map[string]string{
"test_string": "something",
"test_number": "12",
}
if len(values) != len(expected) {
log.Fatalf("expected equal length of values and expected: expected: %d, got: %d", len(expected), len(values))
}
for expKey, expValue := range expected {
if expValue != values[expKey] {
log.Fatalf("not equal values for key %s", expKey)
}
}
type DefaultRequest struct {
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"`
}
func TestDefaultValues(t *testing.T) {
req := &DefaultRequest{
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)
assert.Equal(t, 50, req.Field)
assert.Equal(t, 14, req.EmptyField)
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)
}

10
go.mod
View File

@@ -1,3 +1,11 @@
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

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"time"
@@ -19,21 +20,25 @@ type GetAnalyticsDataParams struct {
DateTo time.Time `json:"date_to"`
// Items Enum: "unknownDimension" "sku" "spu" "day" "week" "month" "year" "category1" "category2" "category3" "category4" "brand" "modelID"
Dimension []string `json:"dimension"`
// Data grouping available to all sellers:
// - unknownDimension—unknown,
// - sku—product identifier,
// - spu—product identifier,
// - day—day,
// - week—week,
// - month—month.
// Data grouping available to sellers with Premium subscription:
// - year—year,
// - category1—first level category,
// - category2—second level category,
// - category3—third level category,
// - category4—fourth level category,
// - brand—brand,
// - modelID—model.
Dimension []GetAnalyticsDataDimension `json:"dimension"`
// Filters
Filters []struct {
// Sorting parameter. You can pass any attribute from the `dimension` and `metric` parameters except the `brand` attribute
Key string `json:"key"`
// Comparison operation
//
// Enum: "EQ" "GT" "GTE" "LT" "LTE"
Operation string `json:"operation"`
// Value for comparison
Value string `json:"value"`
} `json:"filters"`
Filters []GetAnalyticsDataFilter `json:"filters"`
// Number of items in the respones:
// - maximum is 1000,
@@ -41,11 +46,31 @@ type GetAnalyticsDataParams struct {
Limit int64 `json:"limit"`
// Specify up to 14 metrics. If there are more, you will get an error with the InvalidArgument code
// The list of metrics for which the report will be generated.
//
// Items Enum: "unknown_metric" "hits_view_search" "hits_view_pdp" "hits_view" "hits_tocart_search" "hits_tocart_pdp" "hits_tocart" "session_view_search"
// "session_view_pdp" "session_view" "conv_tocart_search" "conv_tocart_pdp" "conv_tocart" "revenue" "returns" "cancellations" "ordered_units" "delivered_units"
// "adv_view_pdp" "adv_view_search_category" "adv_view_all" "adv_sum_all" "position_category" "postings" "postings_premium"
Metrics []string `json:"metrics"`
// Metrics available to all sellers:
//
// - revenue—ordered amount,
// - ordered_units—ordered products.
// Metrics available to sellers with Premium subscription:
// - unknown_metric—unknown metric,
// - hits_view_search—impressions in search and category,
// - hits_view_pdp—impressions on the product description page,
// - hits_view—total impressions,
// - hits_tocart_search—added to cart from search or category,
// - hits_tocart_pdp—added to cart from the product description page,
// - hits_tocart—added to cart total,
// - session_view_search—sessions with impressions in search or category,
// - session_view_pdp—sessions with impressions on the product description page,
// - session_view—sessions total,
// - conv_tocart_search—conversion to cart from search or category,
// - conv_tocart_pdp—conversion to cart from a product description page,
// - conv_tocart—total conversion to cart,
// - returns—returned products,
// - cancellations—canceled products,
// - delivered_units—delivered products,
// - position_category—position in search and category.
Metrics []GetAnalyticsDataFilterMetric `json:"metrics"`
// 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"`
@@ -54,52 +79,74 @@ type GetAnalyticsDataParams struct {
Sort []GetAnalyticsDataSort `json:"sort"`
}
type GetAnalyticsDataFilter struct {
// Sorting parameter. You can pass any attribute from the `dimension` and `metric` parameters except the `brand` attribute
Key string `json:"key"`
// Comparison operation
Operation GetAnalyticsDataFilterOperation `json:"operation"`
// Value for comparison
Value string `json:"value"`
}
// Report sorting settings
type GetAnalyticsDataSort struct {
// Metric by which the method result will be sorted
Key string `json:"key"`
Key GetAnalyticsDataFilterMetric `json:"key"`
// Sorting type
// - ASC — in ascending order,
// - DESC — in descending order.
Order string `json:"order"`
Order Order `json:"order"`
}
type GetAnalyticsDataResponse struct {
core.CommonResponse
// Method result
Result struct {
// Data array
Data []struct {
// Data grouping in the report
Dimensions []struct {
// Identifier
Id string `json:"id"`
// Name
Name string `json:"name"`
} `json:"dimensions"`
// Metric values list
Metrics []float64 `json:"metrics"`
} `json:"data"`
// Total and average metrics values
Totals []float64 `json:"totals"`
} `json:"result"`
Result GetAnalyticsDataResult `json:"result"`
// Report creation time
Timestamp string `json:"timestamp"`
}
type GetAnalyticsDataResult struct {
// Data array
Data []GetAnalyticsDataResultData `json:"data"`
// Total and average metrics values
Totals []float64 `json:"totals"`
}
type GetAnalyticsDataResultData struct {
// Data grouping in the report
Dimensions []GetAnalyticsDataResultDimension `json:"dimensions"`
// Metric values list
Metrics []float64 `json:"metrics"`
}
type GetAnalyticsDataResultDimension struct {
// Product SKU
Id string `json:"id"`
// Name
Name string `json:"name"`
}
// Specify the period and metrics that are required. The response will contain analytical data grouped by the `dimensions` parameter.
func (c Analytics) GetAnalyticsData(params *GetAnalyticsDataParams) (*GetAnalyticsDataResponse, error) {
//
// There are restrictions for sellers without Premium subscription:
//
// - data is available for the last 3 months,
// - some of the data grouping methods and metrics aren't available.
//
// There are no restrictions for sellers with Premium subscription
func (c Analytics) GetAnalyticsData(ctx context.Context, params *GetAnalyticsDataParams) (*GetAnalyticsDataResponse, error) {
url := "/v1/analytics/data"
resp := &GetAnalyticsDataResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -118,50 +165,51 @@ type GetStocksOnWarehousesParams struct {
Offset int64 `json:"offset"`
// Warehouse type filter:
// - EXPRESS_DARK_STORE — Ozon warehouses with Fresh delivery.
// - NOT_EXPRESS_DARK_STORE — Ozon warehouses without Fresh delivery.
// - ALL — all Ozon warehouses.
WarehouseType string `json:"warehouse_type"`
WarehouseType WarehouseType `json:"warehouse_type" default:"ALL"`
}
type GetStocksOnWarehousesResponse struct {
core.CommonResponse
// Method result
Result struct {
// Information about products and stocks
Rows []struct {
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
Result GetStocksOnWarehousesResult `json:"result"`
}
// Product identifier in the seller's system
ItemCode string `json:"item_code"`
type GetStocksOnWarehousesResult struct {
// Information about products and stocks
Rows []GetStocksOnWarehousesResultRow `json:"rows"`
}
// Product name in the Ozon system
ItemName string `json:"item_name"`
type GetStocksOnWarehousesResultRow struct {
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Product amount available for sale on Ozon
FreeToSellAmount int64 `json:"free_to_sell_amount"`
// Product identifier in the seller's system
ItemCode string `json:"item_code"`
// Product amount specified for confirmed future supplies
PromisedAmount int64 `json:"promised_amount"`
// Product name in the Ozon system
ItemName string `json:"item_name"`
// Product amount reserved for purchase, returns, and transportation between warehouses
ReservedAmount int64 `json:"reserved_amount"`
// Product amount available for sale on Ozon
FreeToSellAmount int64 `json:"free_to_sell_amount"`
// Name of the warehouse where the products are stored
WarehouseName string `json:"warehouse_name"`
} `json:"rows"`
} `json:"result"`
// Product amount specified for confirmed future supplies
PromisedAmount int64 `json:"promised_amount"`
// Product amount reserved for purchase, returns, and transportation between warehouses
ReservedAmount int64 `json:"reserved_amount"`
// Name of the warehouse where the products are stored
WarehouseName string `json:"warehouse_name"`
}
// Report on stocks and products movement at Ozon warehouses
func (c Analytics) GetStocksOnWarehouses(params *GetStocksOnWarehousesParams) (*GetStocksOnWarehousesResponse, error) {
func (c Analytics) GetStocksOnWarehouses(ctx context.Context, params *GetStocksOnWarehousesParams) (*GetStocksOnWarehousesResponse, error) {
url := "/v2/analytics/stock_on_warehouses"
resp := &GetStocksOnWarehousesResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"testing"
@@ -23,12 +24,12 @@ func TestGetAnalyticsData(t *testing.T) {
&GetAnalyticsDataParams{
DateFrom: core.TimeFromString(t, "2006-01-02", "2020-09-01"),
DateTo: core.TimeFromString(t, "2006-01-02", "2021-10-15"),
Dimension: []string{"sku", "day"},
Metrics: []string{"hits_view_search"},
Dimension: []GetAnalyticsDataDimension{SKUDimension, DayDimension},
Metrics: []GetAnalyticsDataFilterMetric{HistViewPDP},
Sort: []GetAnalyticsDataSort{
{
Key: "hits_view_search",
Order: "DESC",
Key: HistViewPDP,
Order: Descending,
},
},
Limit: 1000,
@@ -59,11 +60,15 @@ func TestGetAnalyticsData(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Analytics().GetAnalyticsData(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Analytics().GetAnalyticsData(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetAnalyticsDataResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -119,11 +124,15 @@ func TestGetStocksOnWarehouses(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Analytics().GetStocksOnWarehouses(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Analytics().GetStocksOnWarehouses(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetStocksOnWarehousesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}

111
ozon/barcodes.go Normal file
View File

@@ -0,0 +1,111 @@
package ozon
import (
"context"
"net/http"
core "github.com/diphantxm/ozon-api-client"
)
type Barcodes struct {
client *core.Client
}
type GenerateBarcodesParams struct {
// List of products for which you want to generate barcodes
ProductIds []int64 `json:"product_ids"`
}
type GenerateBarcodesResponse struct {
core.CommonResponse
Errors []GenerateBarcodesError `json:"errors"`
}
type GenerateBarcodesError struct {
// Error code
Code string `json:"code"`
// Error details
Error string `json:"error"`
// Barcode that is failed to generate
Barcode string `json:"barcode"`
// Product identifier for which the barcode generation failed
ProductId int64 `json:"product_id"`
}
// If a product doesn't have a barcode, you can create it using this method. If a barcode already exists,
// but it isn't specified in your account, you can bind it using the `/v1/barcode/add` method.
//
// You can't generate barcodes for more than 100 products per request.
// You can use the method no more than 20 times per minute.
func (b *Barcodes) Generate(ctx context.Context, params *GenerateBarcodesParams) (*GenerateBarcodesResponse, error) {
url := "/v1/barcode/generate"
resp := &GenerateBarcodesResponse{}
response, err := b.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type BindBarcodesParams struct {
// List of barcodes and products
Barcodes []BindBarcode `json:"barcodes"`
}
type BindBarcode struct {
// Barcode. Maximum 100 characters
Barcode string `json:"barcode"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
}
type BindBarcodesResponse struct {
core.CommonResponse
// Errors while binding barcodes
Errors []BindBarcodesError `json:"errors"`
}
type BindBarcodesError struct {
// Error code
Code string `json:"code"`
// Error details
Error string `json:"error"`
// Barcode that is failed to generate
Barcode string `json:"barcode"`
// SKU of the product for which the barcode binding failed
SKU int64 `json:"sku"`
}
// If a product has a barcode that isn't specified in your account,
// bind it using this method. If a product doesn't have a barcode,
// you can create it using the `/v1/barcode/generate` method.
//
// You can't bind barcodes to more than 100 products per request.
// Each product can have up to 100 barcodes.
// You can use the method no more than 20 times per minute.
func (b *Barcodes) Bind(ctx context.Context, params *BindBarcodesParams) (*BindBarcodesResponse, error) {
url := "/v1/barcode/add"
resp := &BindBarcodesResponse{}
response, err := b.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}

147
ozon/barcodes_test.go Normal file
View File

@@ -0,0 +1,147 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestGenerateBarcodes(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GenerateBarcodesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GenerateBarcodesParams{
ProductIds: []int64{123456789},
},
`{
"errors": [
{
"code": "code 200",
"error": "no error",
"barcode": "456",
"product_id": 123456789
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GenerateBarcodesParams{},
`{
"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.Barcodes().Generate(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GenerateBarcodesResponse{})
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.Errors) != 0 {
if resp.Errors[0].ProductId != test.params.ProductIds[0] {
t.Errorf("Product ids are not equal")
}
}
}
}
}
func TestBindBarcodes(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *BindBarcodesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&BindBarcodesParams{
Barcodes: []BindBarcode{
{
Barcode: "some barcode",
SKU: 123456789,
},
},
},
`{
"errors": [
{
"code": "code 200",
"error": "no error",
"barcode": "some barcode",
"sku": 123456789
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&BindBarcodesParams{},
`{
"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.Barcodes().Bind(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &BindBarcodesResponse{})
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.Errors) != 0 {
if resp.Errors[0].Barcode != test.params.Barcodes[0].Barcode {
t.Errorf("Barcodes are not equal")
}
if resp.Errors[0].SKU != test.params.Barcodes[0].SKU {
t.Errorf("Barcodes are not equal")
}
}
}
}
}

60
ozon/brands.go Normal file
View File

@@ -0,0 +1,60 @@
package ozon
import (
"context"
"net/http"
core "github.com/diphantxm/ozon-api-client"
)
type Brands struct {
client *core.Client
}
type ListCertifiedBrandsParams struct {
// Number of the page returned in the request
Page int32 `json:"page"`
// Number of elements on the page
PageSize int32 `json:"page_size"`
}
type ListCertifiedBrandsResponse struct {
core.CommonResponse
// Method result
Result ListCertifiedBrandsResult `json:"result"`
}
type ListCertifiedBrandsResult struct {
// Certified brands details
BrandCertification []ListCertifiedBrandsResultCertificate `json:"brand_certification"`
// Total number of brands
Total int64 `json:"total"`
}
type ListCertifiedBrandsResultCertificate struct {
// Brand name
BrandName string `json:"brand_name"`
// Indication that the certificate is required:
// - true if the certificate is required;
// - false if not
HasCertificate bool `json:"has_certificate"`
}
// List of certified brands
func (c Brands) List(ctx context.Context, params *ListCertifiedBrandsParams) (*ListCertifiedBrandsResponse, error) {
url := "/v1/brand/company-certification/list"
resp := &ListCertifiedBrandsResponse{}
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
}

74
ozon/brands_test.go Normal file
View File

@@ -0,0 +1,74 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestListCertifiedBrands(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListCertifiedBrandsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListCertifiedBrandsParams{
Page: 0,
PageSize: 0,
},
`{
"result": {
"brand_certification": [
{
"brand_name": "Sea of Spa",
"has_certificate": false
}
],
"total": 1
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListCertifiedBrandsParams{},
`{
"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.Brands().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListCertifiedBrandsResponse{})
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 int64(len(resp.Result.BrandCertification)) != resp.Result.Total {
t.Errorf("Length of brands in response is not equal to total field in response")
}
}
}
}

214
ozon/cancellations.go Normal file
View File

@@ -0,0 +1,214 @@
package ozon
import (
"context"
"net/http"
"time"
core "github.com/diphantxm/ozon-api-client"
)
type Cancellations struct {
client *core.Client
}
type GetCancellationInfoParams struct {
// Cancellation request identifier
CancellationId int64 `json:"cancellation_id"`
}
type GetCancellationInfoResponse struct {
core.CommonResponse
// Method result
Result CancellationInfo `json:"result"`
}
type CancellationInfo struct {
// Cancellation request identifier
CancellationId int64 `json:"cancellation_id"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Cancellation reason
CancellationReason CancellationInfoReason `json:"cancellation_reason"`
// Cancellation request creation date
CancelledAt time.Time `json:"cancelled_at"`
// Comment to cancellation submitted by cancellation initiator
CancellationReasonMessage string `json:"cancellation_reason_message"`
// Delivery service integration type
TPLIntegrationType TPLIntegrationType `json:"tpl_integration_type"`
// Cancellation request status
State CancellationInfoState `json:"state"`
// Cancellation initiator
CancellationInitiator string `json:"cancellation_initiator"`
// Order creation date
OrderDate time.Time `json:"order_date"`
// Comment submitted on the cancellation request approval or rejection
ApproveComment string `json:"approve_comment"`
// Cancellation request approval or rejection date
ApproveDate time.Time `json:"approve_date"`
// Date after which the request will be automatically approved
AutoApproveDate time.Time `json:"auto_approve_date"`
}
type CancellationInfoReason struct {
// Cancellation reason identifier
Id int64 `json:"id"`
// Cancellation reason name
Name string `json:"name"`
}
type CancellationInfoState struct {
// Status identifier
Id int64 `json:"id"`
// Status name
Name string `json:"name"`
// Request status
State string `json:"state"`
}
// Method for getting information about a rFBS cancellation request
func (c Cancellations) GetInfo(ctx context.Context, params *GetCancellationInfoParams) (*GetCancellationInfoResponse, error) {
url := "/v1/conditional-cancellation/get"
resp := &GetCancellationInfoResponse{}
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 ListCancellationsParams struct {
// Filters
Filter *ListCancellationsFilter `json:"filter,omitempty"`
// Number of cancellation requests in the response
Limit int32 `json:"limit,omitempty"`
// 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 int32 `json:"offset,omitempty"`
// Additional information
With *ListCancellationWith `json:"with,omitempty"`
}
type ListCancellationsFilter struct {
// Filter by cancellation initiator
CancellationInitiator []string `json:"cancellation_initiator"`
// Filter by shipment number.
//
// Optional parameter. You can pass several values here
PostingNumber string `json:"posting_number"`
// Filter by cancellation request status
State string `json:"state"`
}
type ListCancellationWith struct {
// Indication that the counter of requests in different statuses should be displayed in the response
Counters bool `json:"counters"`
}
type ListCancellationsResponse struct {
core.CommonResponse
// Cancellation requests list
Result []CancellationInfo `json:"result"`
// The total number of requests by the specified filters
Total int32 `json:"total"`
// Counter of requests in different statuses
Counters ListCancellationResponseCounters `json:"counters"`
}
type ListCancellationResponseCounters struct {
// Number of requests for approval
OnApproval int64 `json:"on_approval"`
// Number of approved requests
Approved int64 `json:"approved"`
// Number of rejected requests
Rejected int64 `json:"rejected"`
}
// Method for getting a list of rFBS cancellation requests
func (c Cancellations) List(ctx context.Context, params *ListCancellationsParams) (*ListCancellationsResponse, error) {
url := "/v1/conditional-cancellation/list"
resp := &ListCancellationsResponse{}
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 ApproveRejectCancellationsParams struct {
// Cancellation request identifier
CancellationId int64 `json:"cancellation_id"`
// Comment
Comment string `json:"comment,omitempty"`
}
type ApproveRejectCancellationsResponse struct {
core.CommonResponse
}
// The method allows to approve an rFBS cancellation request in the ON_APPROVAL status.
// The order will be canceled and the money will be returned to the customer
func (c Cancellations) Approve(ctx context.Context, params *ApproveRejectCancellationsParams) (*ApproveRejectCancellationsResponse, error) {
url := "/v1/conditional-cancellation/approve"
resp := &ApproveRejectCancellationsResponse{}
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
}
// The method allows to reject an rFBS cancellation request in the ON_APPROVAL status. Explain your decision in the comment parameter.
//
// The order will remain in the same status and must be delivered to the customer
func (c Cancellations) Reject(ctx context.Context, params *ApproveRejectCancellationsParams) (*ApproveRejectCancellationsResponse, error) {
url := "/v1/conditional-cancellation/reject"
resp := &ApproveRejectCancellationsResponse{}
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
}

288
ozon/cancellations_test.go Normal file
View File

@@ -0,0 +1,288 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestGetCancellationInfo(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetCancellationInfoParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetCancellationInfoParams{
CancellationId: 90066344,
},
`{
"result": {
"cancellation_id": 90066344,
"posting_number": "47134289-0029-1",
"cancellation_reason": {
"id": 508,
"name": "Покупатель отменил заказ"
},
"cancelled_at": "2022-04-07T06:37:26.871105Z",
"cancellation_reason_message": "Изменение пункта выдачи заказа.",
"tpl_integration_type": "ThirdPartyTracking",
"state": {
"id": 2,
"name": "Подтверждена",
"state": "APPROVED"
},
"cancellation_initiator": "CLIENT",
"order_date": "2022-04-06T17:17:24.517Z",
"approve_comment": "",
"approve_date": "2022-04-07T07:52:45.971824Z",
"auto_approve_date": "2022-04-09T06:37:26.871105Z"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetCancellationInfoParams{},
`{
"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.Cancellations().GetInfo(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetCancellationInfoResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.CancellationId != test.params.CancellationId {
t.Errorf("Cancellation ids in request and response are not equal")
}
}
}
}
func TestListCancellations(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListCancellationsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListCancellationsParams{
Filter: &ListCancellationsFilter{
CancellationInitiator: []string{"CLIENT"},
State: "ALL",
},
Limit: 2,
Offset: 0,
With: &ListCancellationWith{
Counters: true,
},
},
`{
"result": [
{
"cancellation_id": 50186754,
"posting_number": "41267064-0032-1",
"cancellation_reason": {
"id": 508,
"name": "Покупатель отменил заказ"
},
"cancelled_at": "2021-09-03T07:17:12.116114Z",
"cancellation_reason_message": "",
"tpl_integration_type": "ThirdPartyTracking",
"state": {
"id": 2,
"name": "Подтверждена",
"state": "APPROVED"
},
"cancellation_initiator": "CLIENT",
"order_date": "2021-09-03T07:04:53.22Z",
"approve_comment": "",
"approve_date": "2021-09-03T09:13:12.6142Z",
"auto_approve_date": "2021-09-06T07:17:12.116114Z"
},
{
"cancellation_id": 51956491,
"posting_number": "14094410-0018-1",
"cancellation_reason": {
"id": 507,
"name": "Покупатель передумал"
},
"cancelled_at": "2021-09-13T15:03:25.155827Z",
"cancellation_reason_message": "",
"tpl_integration_type": "ThirdPartyTracking",
"state": {
"id": 5,
"name": "Автоматически отменена",
"state": "REJECTED"
},
"cancellation_initiator": "CLIENT",
"order_date": "2021-09-13T07:48:50.143Z",
"approve_comment": "",
"approve_date": null,
"auto_approve_date": "2021-09-16T15:03:25.155827Z"
}
],
"total": 19,
"counters": {
"on_approval": 0,
"approved": 14,
"rejected": 5
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListCancellationsParams{},
`{
"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.Cancellations().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListCancellationsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestApproveCancellations(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ApproveRejectCancellationsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ApproveRejectCancellationsParams{
CancellationId: 74393917,
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ApproveRejectCancellationsParams{},
`{
"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.Cancellations().Approve(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ApproveRejectCancellationsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestRejectCancellations(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ApproveRejectCancellationsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ApproveRejectCancellationsParams{
CancellationId: 74393917,
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ApproveRejectCancellationsParams{},
`{
"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.Cancellations().Reject(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ApproveRejectCancellationsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

226
ozon/categories.go Normal file
View File

@@ -0,0 +1,226 @@
package ozon
import (
"context"
"net/http"
core "github.com/diphantxm/ozon-api-client"
)
type Categories struct {
client *core.Client
}
type GetProductTreeParams struct {
// Response language
Language Language `json:"language,omitempty"`
}
type GetProductTreeResponse struct {
core.CommonResponse
// Categories list
Result []GetProductTreeResult `json:"result"`
}
type GetProductTreeResult struct {
// Category identifier
DescriptionCategoryId int64 `json:"description_category_id"`
// Category name
CategoryName string `json:"category_name"`
// `true`, if you can't create products in the category. `false`, if you can
Disabled bool `json:"disabled"`
// Product type identifier
TypeId int64 `json:"type_id"`
// Product type name
TypeName string `json:"type_name"`
// Subcategory tree
Children []GetProductTreeResult `json:"children"`
}
// Returns product categories in the tree view.
//
// New products can be created in the last level categories only.
// This means that you need to match these particular categories with the categories of your site.
// We don't create new categories by user request.
func (c *Categories) Tree(ctx context.Context, params *GetProductTreeParams) (*GetProductTreeResponse, error) {
url := "/v1/description-category/tree"
resp := &GetProductTreeResponse{}
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 GetCategoryAttributesParams struct {
// Category identifier
DescriptionCategoryId int64 `json:"description_category_id"`
// Response language
Language Language `json:"language,omitempty"`
// Product type identifier
TypeId int64 `json:"type_id"`
}
type GetCategoryAttributesResponse struct {
core.CommonResponse
// Method result
Result []GetCategoryAttributesResult `json:"result"`
}
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
Description string `json:"description"`
// Directory identifier
DictionaryId int64 `json:"dictionary_id"`
// Characteristics group identifier
GroupId int64 `json:"group_id"`
// Characteristics group name
GroupName string `json:"group_name"`
// Number of document generation task
Id int64 `json:"id"`
// Indicates that the attribute is aspect. An aspect attribute is a characteristic that distinguishes products of the same model.
//
// For example, clothes or shoes of the same model may have different colors and sizes. That is, color and size are aspect attributes.
//
// Values description:
//
// - `true`—the attribute is aspect and can't be changed after the products are delivered to the warehouse or sold from the seller's warehouse.
// - `false`—the attribute is not aspect and can be changed at any time
IsAspect bool `json:"is_aspect"`
// Indicates that the characteristic is a set of values:
//
// - `true`—the characteristic is a set of values,
// - `false`—the characteristic consists of a single value
IsCollection bool `json:"is_collection"`
// Indicates that the characteristic is mandatory:
//
// - `true`—a mandatory characteristic,
// - `false`—an optional characteristic
IsRequired bool `json:"is_required"`
// Name
Name string `json:"name"`
// Characteristic 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.
//
// If the dictionary_id value is 0, there is no directory.
// If the value is different, there are directories.
// Get them using the `/v1/description-category/attribute/values` method.
func (c *Categories) Attributes(ctx context.Context, params *GetCategoryAttributesParams) (*GetCategoryAttributesResponse, error) {
url := "/v1/description-category/attribute"
resp := &GetCategoryAttributesResponse{}
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 GetAttributeDictionaryParams struct {
// Characteristics identifier
AttributeId int64 `json:"attribute_id"`
// Category identifier
DescriptionCategoryId int64 `json:"description_category_id"`
// Response language
Language Language `json:"language,omitempty"`
// Identifier of the directory to start the response with.
// If `last_value_id` is 10, the response will contain directories starting from the 11th
LastValueId int64 `json:"last_value_id"`
// Number of values in the response:
//
// - maximum—5000,
// - minimum—1.
Limit int64 `json:"limit,omitempty"`
// Product type identifier
TypeId int64 `json:"type_id"`
}
type GetAttributeDictionaryResponse struct {
core.CommonResponse
// Indication that only part of characteristic values was returned in the response:
//
// - true—make a request with a new last_value_id parameter value for getting the rest of characteristic values;
// - false—all characteristic values were returned
HasNext bool `json:"has_next"`
// Characteristic values
Result []GetAttributeDictionaryResult `json:"result"`
}
type GetAttributeDictionaryResult 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 characteristics value directory.
//
// To check if an attribute has a nested directory,
// use the `/v1/description-category/attribute` method.
func (c *Categories) AttributesDictionary(ctx context.Context, params *GetAttributeDictionaryParams) (*GetAttributeDictionaryResponse, error) {
url := "/v1/description-category/attribute/values"
resp := &GetAttributeDictionaryResponse{}
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
}

205
ozon/categories_test.go Normal file
View File

@@ -0,0 +1,205 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestGetProductTree(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetProductTreeParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetProductTreeParams{
Language: English,
},
`{
"result": [
{
"description_category_id": 0,
"category_name": "string",
"children": [],
"disabled": true,
"type_id": 0,
"type_name": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetProductTreeParams{},
`{
"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().Tree(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductTreeResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetCategoryAttributes(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetCategoryAttributesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetCategoryAttributesParams{
DescriptionCategoryId: 12345,
Language: English,
TypeId: 2,
},
`{
"result": [
{
"category_dependent": true,
"description": "string",
"dictionary_id": 0,
"group_id": 0,
"group_name": "string",
"id": 0,
"is_aspect": true,
"is_collection": true,
"is_required": true,
"name": "string",
"type": "string",
"attribute_complex_id": 0,
"max_value_count": 0
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetCategoryAttributesParams{},
`{
"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().Attributes(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetCategoryAttributesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetAttributeDictionary(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetAttributeDictionaryParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetAttributeDictionaryParams{
AttributeId: 123456,
DescriptionCategoryId: 12,
Language: English,
LastValueId: 1,
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{},
&GetAttributeDictionaryParams{},
`{
"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().AttributesDictionary(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")
}
}
}
}

574
ozon/certificates.go Normal file
View File

@@ -0,0 +1,574 @@
package ozon
import (
"context"
"net/http"
"time"
core "github.com/diphantxm/ozon-api-client"
)
type Certificates struct {
client *core.Client
}
type ListOfAccordanceTypesResponse struct {
core.CommonResponse
// Accordance types
Result ListOfAccordanceTypesResult `json:"result"`
}
type ListOfAccordanceTypesResult struct {
// Main accordance types
Base []ListOfAccordanceTypesResultBase `json:"base"`
// Main accordance types related to dangerous products
Hazard []ListOfAccordanceTypesResultHazard `json:"hazard"`
}
type ListOfAccordanceTypesResultBase struct {
// Accordance type code
Code string `json:"code"`
// Accordance type description
Title string `json:"title"`
}
type ListOfAccordanceTypesResultHazard struct {
// Accordance type code
Code string `json:"code"`
// Accordance type description
Title string `json:"title"`
}
// List of accordance types (version 2)
func (c Certificates) ListOfAccordanceTypes(ctx context.Context) (*ListOfAccordanceTypesResponse, error) {
url := "/v2/product/certificate/accordance-types/list"
resp := &ListOfAccordanceTypesResponse{}
response, err := c.client.Request(ctx, http.MethodGet, url, nil, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type DirectoryOfDocumentTypesResponse struct {
core.CommonResponse
// List of certificate types and names
Result []DirectoryOfDocumentTypesResult `json:"result"`
}
type DirectoryOfDocumentTypesResult struct {
// Certificate name
Name string `json:"name"`
// Certificate type
Value string `json:"value"`
}
// Directory of document types
func (c Certificates) DirectoryOfDocumentTypes(ctx context.Context) (*DirectoryOfDocumentTypesResponse, error) {
url := "/v1/product/certificate/types"
resp := &DirectoryOfDocumentTypesResponse{}
response, err := c.client.Request(ctx, http.MethodGet, url, nil, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type ListOfCertifiedCategoriesParams struct {
// Number of the page returned in the query
Page int32 `json:"page"`
// Number of elements on the page
PageSize int32 `json:"page_size"`
}
type ListOfCertifiedCategoriesResponse struct {
core.CommonResponse
// Method result
Result ListOfCertifiedCategoriesResult `json:"result"`
}
type ListOfCertifiedCategoriesResult struct {
// Certified categories details
Certification []ListOfCertifiedCategoriesResultCert `json:"certification"`
// Total number of categories
Total int64 `json:"total"`
}
type ListOfCertifiedCategoriesResultCert struct {
// Category name
CategoryName string `json:"category_name"`
// Indication of a mandatory category
IsRequired bool `json:"is_required"`
}
// List of certified categories
func (c Certificates) ListOfCertifiedCategories(ctx context.Context, params *ListOfCertifiedCategoriesParams) (*ListOfCertifiedCategoriesResponse, error) {
url := "/v1/product/certification/list"
resp := &ListOfCertifiedCategoriesResponse{}
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 LinkCertificateToProductParams struct {
// Certificate identifier that was assigned when it was uploaded
CertificateId int64 `json:"certificate_id"`
// An array of product identifiers that this certificate applies to
ProductId []int64 `json:"product_id"`
}
type LinkCertificateToProductResponse struct {
core.CommonResponse
// The result of processing the request. true if the request was executed without errors
Result bool `json:"result"`
}
// Link the certificate to the product
func (c Certificates) LinkToProduct(ctx context.Context, params *LinkCertificateToProductParams) (*LinkCertificateToProductResponse, error) {
url := "/v1/product/certificate/bind"
resp := &LinkCertificateToProductResponse{}
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 DeleteCertificateParams struct {
// Certificate identifier
CertificateId int32 `json:"certificate_id"`
}
type DeleteCertificateResponse struct {
core.CommonResponse
// Result of deleting the certificate
Result DeleteCertificateResult `json:"result"`
}
type DeleteCertificateResult struct {
// Indication that a certificate has been deleted:
// - true — deleted
// - false — not deleted
IsDelete bool `json:"is_delete"`
// Description of errors during certificate deletion
ErrorMessage string `json:"error_message"`
}
// Delete certificate
func (c Certificates) Delete(ctx context.Context, params *DeleteCertificateParams) (*DeleteCertificateResponse, error) {
url := "/v1/product/certificate/delete"
resp := &DeleteCertificateResponse{}
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 GetCertificateInfoParams struct {
// Certificate identifier
CertificateNumber string `json:"certificate_number"`
}
type GetCertificateInfoResponse struct {
core.CommonResponse
// Certificate information
Result GetCertificateInfoResult `json:"result"`
}
type GetCertificateInfoResult struct {
// Identifier
CertificateId int32 `json:"certificate_id"`
// Number
CertificateNumber string `json:"certificate_number"`
// Name
CertificateName string `json:"certificate_name"`
// Type
TypeCode string `json:"type_code"`
// Status
StatusCode string `json:"status_code"`
// Accordance type
AccordanceTypeCode string `json:"accordance_type_code"`
// Certificate rejection reason
RejectionReasonCode string `json:"rejection_reason_code"`
// Moderator's comment
VerificationComment string `json:"verification_comment"`
// Issue date
IssueDate time.Time `json:"issue_date"`
// Expire date
ExpireDate time.Time `json:"expire_date"`
// Number of products associated with a certificate
ProductsCount int32 `json:"products_count"`
}
// Certificate information
func (c Certificates) GetInfo(ctx context.Context, params *GetCertificateInfoParams) (*GetCertificateInfoResponse, error) {
url := "/v1/product/certificate/info"
resp := &GetCertificateInfoResponse{}
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 ListCertificatesParams struct {
// Product identifier associated with the certificate.
// Pass the parameter if you need certificates that certain products are associated with
OfferId string `json:"offer_id"`
// Certificate status. Pass the parameter if you need certificates with a certain status
Status string `json:"status"`
// Certificate type. Pass the parameter if you need certificates with a certain type
Type string `json:"type"`
// Page from which the list should be displayed. The minimum value is 1
Page int32 `json:"page"`
// Number of objects on the page. The value is from 1 to 1000
PageSize int32 `json:"page_size"`
}
type ListCertificatesResponse struct {
core.CommonResponse
// Certificates
Result ListCertificatesResult `json:"result"`
}
type ListCertificatesResult struct {
// Сertificate information
Certificates []ListCertificatesResultCert `json:"certificates"`
// Number of pages
PageCount int32 `json:"page_count"`
}
type ListCertificatesResultCert struct {
// Identifier
CertificateId int32 `json:"certificate_id"`
// Number
CertificateNumber string `json:"certificate_number"`
// Name
CertificateName string `json:"certificate_name"`
// Type
TypeCode string `json:"type_code"`
// Status
StatusCode string `json:"status_code"`
// Accordance type
AccordanceTypecode string `json:"accordance_type_code"`
// Certificate rejection reason
RejectionReasonCode string `json:"rejection_reason_code"`
// Moderator's comment
VerificationComment string `json:"verification_comment"`
// Issue date
IssueDate time.Time `json:"issue_date"`
// Expire date
ExpireDate time.Time `json:"expire_date"`
// Number of products associated with a certificate
ProductsCount int32 `json:"products_count"`
}
// Certificates list
func (c Certificates) List(ctx context.Context, params *ListCertificatesParams) (*ListCertificatesResponse, error) {
url := "/v1/product/certificate/list"
resp := &ListCertificatesResponse{}
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 ProductStatusesResponse struct {
core.CommonResponse
// Product statuses
Result []ProductStatusesResult `json:"result"`
}
type ProductStatusesResult struct {
// Product status code when linking it to the certificate
Code string `json:"code"`
// Status description
Name string `json:"name"`
}
func (c Certificates) ProductStatuses(ctx context.Context) (*ProductStatusesResponse, error) {
url := "/v1/product/certificate/list"
resp := &ProductStatusesResponse{}
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 ListProductsForCertificateParams struct {
// Certificate identifier
CertificateId int32 `json:"certificate_id"`
// Status of the product verification when binding to a certificate
ProductStatusCode string `json:"product_status_code"`
// Page from which the list should be displayed. The minimum value is 1
Page int32 `json:"page"`
// Number of objects on the page. The value is from 1 to 1000
PageSize int32 `json:"page_size"`
}
type ListProductsForCertificateResponse struct {
core.CommonResponse
// Method result
Result ListProductsForCertificateResult `json:"result"`
}
type ListProductsForCertificateResult struct {
// List of products
Items []struct {
// Product identifier
ProductId int64 `json:"product_id"`
// Status of the product processing when binding to a certificate
ProductStatusCode string `json:"product_status_code"`
} `json:"items"`
// Number of products found
Count int64 `json:"count"`
}
// A method for getting a list of possible statuses of products when binding them to a certificate
func (c Certificates) ListProductsForCertificate(ctx context.Context, params *ListProductsForCertificateParams) (*ListProductsForCertificateResponse, error) {
url := "/v1/product/certificate/products/list"
resp := &ListProductsForCertificateResponse{}
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 UnlinkFromProductParams struct {
// Certificate identifier
CertificateId int32 `json:"certificate_id"`
// List of product identifiers that you want to unbind from a certificate
ProductId []int64 `json:"product_id"`
}
type UnlinkFromProductResponse struct {
core.CommonResponse
// Method result
Result []UnlinkFromProductResult `json:"result"`
}
type UnlinkFromProductResult struct {
// Error message when unbinding a product
Error string `json:"error"`
// Product identifier
ProductId int64 `json:"product_id"`
// Indication that the product was unbound from a certificate:
// - true — it was unbound,
// - false — it is still bound
Updated bool `json:"updated"`
}
// Unbind products from a certificate
func (c Certificates) UnlinkFromProduct(ctx context.Context, params *UnlinkFromProductParams) (*UnlinkFromProductResponse, error) {
url := "/v1/product/certificate/unbind"
resp := &UnlinkFromProductResponse{}
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 PossibleRejectReasonsResponse struct {
core.CommonResponse
// Certificate rejection reasons
Result []PossibleRejectReasonsResult `json:"result"`
}
type PossibleRejectReasonsResult struct {
// Сode of a certificate rejection reason
Code string `json:"code"`
// Description of a certificate rejection reason
Name string `json:"name"`
}
// Possible certificate rejection reasons
func (c Certificates) PossibleRejectReasons(ctx context.Context) (*PossibleRejectReasonsResponse, error) {
url := "/v1/product/certificate/rejection_reasons/list"
resp := &PossibleRejectReasonsResponse{}
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 PossibleStatusesResponse struct {
core.CommonResponse
// Possible certificate statuses
Result []PossibleStatusesResult `json:"result"`
}
type PossibleStatusesResult struct {
// Certificate status code
Code string `json:"code"`
// Status description
Name string `json:"name"`
}
func (c Certificates) PossibleStatuses(ctx context.Context) (*PossibleStatusesResponse, error) {
url := "/v1/product/certificate/status/list"
resp := &PossibleStatusesResponse{}
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 AddCertificatesForProductsParams struct {
// Array of certificates for the product. Valid extensions are jpg, jpeg, png, pdf
Files []byte `json:"files"`
// Certificate name. No more than 100 characters
Name string `json:"name"`
// Certificate number. No more than 100 characters
Number string `json:"number"`
// Certificate type. To get the list of types, use the GET `/v1/product/certificate/types` method
TypeCode string `json:"type_code"`
// Accordance type. To get the list of types, use the GET `/v1/product/certificate/accordance-types` method
AccordanceTypeCode string `json:"accordance_type_code"`
// Issue date of the certificate
IssueDate time.Time `json:"issue_date"`
// Expiration date of the certificate. Can be empty for permanent certificates
ExpireDate time.Time `json:"expire_date"`
}
type AddCertificatesForProductsResponse struct {
core.CommonResponse
Id int `json:"id"`
}
// Adding certificates for products
func (c Certificates) AddForProducts(ctx context.Context, params *AddCertificatesForProductsParams) (*AddCertificatesForProductsResponse, error) {
url := "/v1/product/certificate/create"
resp := &AddCertificatesForProductsResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, map[string]string{
"Content-Type": "multipart/form-data",
})
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}

760
ozon/certificates_test.go Normal file
View File

@@ -0,0 +1,760 @@
package ozon
import (
"context"
"net/http"
"testing"
"time"
core "github.com/diphantxm/ozon-api-client"
)
func TestListOfAccordanceTypes(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"result": {
"base": [
{
"code": "string",
"title": "string"
}
],
"hazard": [
{
"code": "string",
"title": "string"
}
]
}
}`,
},
// 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.Certificates().ListOfAccordanceTypes(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListOfAccordanceTypesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestDirectoryOfDocumentTypes(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"result": [
{
"name": "Сертификат соответствия",
"value": "certificate_of_conformity"
},
{
"name": "Декларация",
"value": "declaration"
},
{
"name": "Свидетельство о гос регистрации",
"value": "certificate_of_registration"
},
{
"name": "Регистрационное удостоверение",
"value": "registration_certificate"
},
{
"name": "Отказное письмо",
"value": "refused_letter"
}
]
}`,
},
// 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.Certificates().DirectoryOfDocumentTypes(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DirectoryOfDocumentTypesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestListOfCertifiedCategories(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListOfCertifiedCategoriesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListOfCertifiedCategoriesParams{
Page: 1,
PageSize: 100,
},
`{
"result": {
"certification": [
{
"is_required": true,
"category_name": "Витаминно-минеральные комплексы для взрослых"
}
],
"total": 1
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListOfCertifiedCategoriesParams{},
`{
"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.Certificates().ListOfCertifiedCategories(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListOfCertifiedCategoriesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestLinkCertificateToProduct(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *LinkCertificateToProductParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&LinkCertificateToProductParams{
CertificateId: 50058,
ProductId: []int64{290},
},
`{
"result": true
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&LinkCertificateToProductParams{},
`{
"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.Certificates().LinkToProduct(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &LinkCertificateToProductResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestDeleteCertificate(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *DeleteCertificateParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&DeleteCertificateParams{
CertificateId: 0,
},
`{
"result": {
"is_delete": true,
"error_message": "string"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&DeleteCertificateParams{},
`{
"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.Certificates().Delete(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DeleteCertificateResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetCertificateInfo(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetCertificateInfoParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetCertificateInfoParams{
CertificateNumber: "certificate number",
},
`{
"result": {
"certificate_id": 0,
"certificate_number": "certificate number",
"certificate_name": "string",
"type_code": "string",
"status_code": "string",
"accordance_type_code": "string",
"rejection_reason_code": "string",
"verification_comment": "string",
"issue_date": "2019-08-24T14:15:22Z",
"expire_date": "2019-08-24T14:15:22Z",
"products_count": 0
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetCertificateInfoParams{},
`{
"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.Certificates().GetInfo(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetCertificateInfoResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.CertificateNumber != test.params.CertificateNumber {
t.Errorf("Certificate numbers in request and response are not equal")
}
}
}
}
func TestListCertificates(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListCertificatesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListCertificatesParams{
OfferId: "id",
Status: "some status",
Type: "some type",
Page: 1,
PageSize: 1,
},
`{
"result": {
"certificates": [
{
"certificate_id": 0,
"certificate_number": "string",
"certificate_name": "string",
"type_code": "string",
"status_code": "string",
"accordance_type_code": "string",
"rejection_reason_code": "string",
"verification_comment": "string",
"issue_date": "2019-08-24T14:15:22Z",
"expire_date": "2019-08-24T14:15:22Z",
"products_count": 0
}
],
"page_count": 0
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListCertificatesParams{},
`{
"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.Certificates().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListCertificatesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestProductStatuses(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"result": [
{
"code": "string",
"name": "string"
}
]
}`,
},
// 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.Certificates().ProductStatuses(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ProductStatusesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestListProductsForCertificate(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListProductsForCertificateParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListProductsForCertificateParams{
CertificateId: 0,
ProductStatusCode: "status code",
Page: 0,
PageSize: 0,
},
`{
"result": {
"items": [
{
"product_id": 0,
"product_status_code": "string"
}
],
"count": 0
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListProductsForCertificateParams{},
`{
"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.Certificates().ListProductsForCertificate(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListProductsForCertificateResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestUnlinkFromProduct(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *UnlinkFromProductParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&UnlinkFromProductParams{
CertificateId: 0,
ProductId: []int64{0},
},
`{
"result": [
{
"error": "string",
"product_id": 0,
"updated": true
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&UnlinkFromProductParams{},
`{
"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.Certificates().UnlinkFromProduct(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UnlinkFromProductResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestPossibleRejectReasons(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"result": [
{
"code": "string",
"name": "string"
}
]
}`,
},
// 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.Certificates().PossibleRejectReasons(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &PossibleRejectReasonsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestPossibleStatuses(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"result": [
{
"code": "string",
"name": "string"
}
]
}`,
},
// 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.Certificates().PossibleStatuses(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &PossibleStatusesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestAddCertificatesForProducts(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *AddCertificatesForProductsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&AddCertificatesForProductsParams{
Files: []byte{10, 15, 2, 0},
Name: "Certificate name",
Number: "10a-d5s9-4asdf2",
TypeCode: "declaration",
AccordanceTypeCode: "gost",
IssueDate: time.Now(),
ExpireDate: time.Now(),
},
`{
"id": 50058
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&AddCertificatesForProductsParams{},
`{
"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.Certificates().AddForProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &AddCertificatesForProductsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

415
ozon/chats.go Normal file
View File

@@ -0,0 +1,415 @@
package ozon
import (
"context"
"net/http"
"time"
core "github.com/diphantxm/ozon-api-client"
)
type Chats struct {
client *core.Client
}
type ListChatsParams struct {
// Chats filter
Filter *ListChatsFilter `json:"filter,omitempty"`
// Number of values in the response. The default value is 30. The maximum value is 1000
Limit int64 `json:"limit" default:"30"`
// 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,omitempty"`
}
type ListChatsFilter struct {
// Filter by chat status:
// - All
// - Opened
// - Closed
ChatStatus string `json:"chat_status" default:"ALL"`
// Filter by chats with unread messages
UnreadOnly bool `json:"unread_only"`
}
type ListChatsResponse struct {
core.CommonResponse
// Chats data
Chats []ListChatsChat `json:"chats"`
// Total number of chats
TotalChatsCount int64 `json:"total_chats_count"`
// Total number of unread messages
TotalUnreadCount int64 `json:"total_unread_count"`
}
type ListChatsChat struct {
// Chat data
Chat ListChatsChatData `json:"chat"`
// Identifier of the first unread chat message
FirstUnreadMessageId string `json:"first_unread_message_id"`
// Identifier of the last message in the chat
LastMessageId string `json:"last_message_id"`
// Number of unread messages in the chat
UnreadCount int64 `json:"unread_count"`
}
type ListChatsChatData struct {
// Chat identifier
ChatId string `json:"chat_id"`
// Chat status:
// - All
// - Opened
// - Closed
ChatStatus string `json:"chat_status"`
// Chat type:
// - Seller_Support — support chat
// - Buyer_Seller — chat with a customer
ChatType string `json:"chat_type"`
// Chat creation date
CreatedAt time.Time `json:"created_at"`
}
// Returns information about chats by specified filters
func (c Chats) List(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) {
url := "/v2/chat/list"
resp := &ListChatsResponse{}
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 SendMessageParams struct {
// Chat identifier
ChatId string `json:"chat_id"`
// Message text in the plain text format
Text string `json:"text"`
}
type SendMessageResponse struct {
core.CommonResponse
// Method result
Result string `json:"result"`
}
// Sends a message to an existing chat by its identifier
func (c Chats) SendMessage(ctx context.Context, params *SendMessageParams) (*SendMessageResponse, error) {
url := "/v1/chat/send/message"
resp := &SendMessageResponse{}
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 SendFileParams struct {
// File as a base64 string
Base64Content string `json:"base64_content"`
// Chat identifier
ChatId string `json:"chat_id"`
// File name with extension
Name string `json:"name"`
}
type SendFileResponse struct {
core.CommonResponse
// Method result
Result string `json:"result"`
}
// Sends a file to an existing chat by its identifier
func (c Chats) SendFile(ctx context.Context, params *SendFileParams) (*SendFileResponse, error) {
url := "/v1/chat/send/file"
resp := &SendFileResponse{}
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 ChatHistoryParams struct {
// Chat idenitifier
ChatId string `json:"chat_id"`
// Messages sorting direction:
// - Forward—from old messages to new ones.
// - Backward—from new messages to old ones.
// The default value is `Backward`. You can set the number of messages in the limit parameter
Direction string `json:"direction" default:"Backward"`
// Identifier of the message from which the chat history will be displayed.
// Default value is the last visible message
FromMessageId string `json:"from_message_id"`
// Number of messages in the response. The default value is 50. The maximum value is 1000
Limit int64 `json:"limit" default:"50"`
}
type ChatHistoryResponse struct {
core.CommonResponse
// Indicates that the response returned only a part of messages
HasNext bool `json:"has_next"`
// An array of messages sorted according to the direction parameter in the request body
Messages []ChatHistoryMessage `json:"messages"`
}
type ChatHistoryMessage struct {
// Message creation date
CreatedAt time.Time `json:"created_at"`
// Array with message content in Markdown format
Data []string `json:"data"`
// Indication of the read message
IsRead bool `json:"is_read"`
// Message identifier
MessageId string `json:"message_id"`
// Chat participant identifier
User ChatHistoryMessageUser `json:"user"`
}
type ChatHistoryMessageUser struct {
// Chat participant identifier
Id string `json:"id"`
// Chat participant type:
// - customer
// - seller
// - crm—system messages
// - courier
// - support
Type string `json:"type"`
}
// 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) {
url := "/v2/chat/history"
resp := &ChatHistoryResponse{}
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 UpdateChatParams struct {
// Chat identifier
ChatId string `json:"chat_id"`
// Message identifier
FromMessageId uint64 `json:"from_message_id"`
// Number of messages in the response
Limit int64 `json:"limit,omitempty"`
}
type UpdateChatResponse struct {
core.CommonResponse
// Method result
Result []UpdateChatResult `json:"result"`
}
type UpdateChatResult struct {
// An order or a product user wrote about in the chat
Context UpdateChatResultContext `json:"context"`
// Creation date and time
CreatedAt time.Time `json:"created_at"`
// Information about the file in the chat. Displayed only for `type = file`
File UpdateChatResultFile `json:"file"`
// File identifier
Id uint64 `json:"id"`
// Message. Displayed only for `type = text`
Text string `json:"text"`
// Message type:
// - text
// - file
Type string `json:"type"`
// Chat participant information
User UpdateChatResultUser `json:"user"`
}
type UpdateChatResultContext struct {
// Product inforamtion
Item UpdateChatResultContextItem `json:"item"`
// Order information
Order UpdateChatResultContextOrder `json:"order"`
}
type UpdateChatResultContextItem struct {
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
}
type UpdateChatResultContextOrder struct {
// Order number
OrderNumber string `json:"order_number"`
// Shipment information
Postings []UpdateChatResultContextOrderPosting `json:"postings"`
}
type UpdateChatResultContextOrderPosting struct {
// Delivery scheme:
// - FBO
// - FBS
// - RFBS
// - Crossborder
DeliverySchema string `json:"delivery_schema"`
// Shipment number
PostingNumber string `json:"posting_number"`
// List of product identifiers in the shipment
SKUList []int64 `json:"sku_list"`
}
type UpdateChatResultFile struct {
// File type
Mime string `json:"mime"`
// File name
Name string `json:"name"`
// File size in bytes
Size int64 `json:"size"`
// File URL
URL string `json:"url"`
}
type UpdateChatResultUser struct {
// Chat participant identifier
Id string `json:"id"`
// Chat participant chat:
// - customer
// - seller
// - crm—system messages
// - courier
Type string `json:"type"`
}
// Update chat
func (c Chats) Update(ctx context.Context, params *UpdateChatParams) (*UpdateChatResponse, error) {
url := "/v1/chat/updates"
resp := &UpdateChatResponse{}
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 CreateNewChatParams struct {
// Shipment identifier
PostingNumber string `json:"posting_number"`
}
type CreateNewChatResponse struct {
core.CommonResponse
//Method result
Result CreateNewChatResult `json:"result"`
}
type CreateNewChatResult struct {
// Chat identifier
ChatId string `json:"chat_id"`
}
// Creates a new chat on the shipment with the customer. For example, to clarify the address or the product model
func (c Chats) Create(ctx context.Context, params *CreateNewChatParams) (*CreateNewChatResponse, error) {
url := "/v1/chat/start"
resp := &CreateNewChatResponse{}
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 MarkAsReadParams struct {
// Chat identifier
Chatid string `json:"chat_id"`
// Message identifier
FromMessageId uint64 `json:"from_message_id"`
}
type MarkAsReadResponse struct {
core.CommonResponse
// Number of unread messages in the chat
UnreadCount int64 `json:"unread_count"`
}
// A method for marking the selected message and messages before it as read
func (c Chats) MarkAsRead(ctx context.Context, params *MarkAsReadParams) (*MarkAsReadResponse, error) {
url := "/v2/chat/read"
resp := &MarkAsReadResponse{}
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
}

438
ozon/chats_test.go Normal file
View File

@@ -0,0 +1,438 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestListChats(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListChatsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListChatsParams{
Filter: &ListChatsFilter{
ChatStatus: "Opened",
UnreadOnly: true,
},
Limit: 1,
Offset: 0,
},
`{
"chats": [
{
"chat": {
"created_at": "2022-07-22T08:07:19.581Z",
"chat_id": "5e767w03-b400-4y1b-a841-75319ca8a5c8",
"chat_status": "Opened",
"chat_type": "Seller_Support"
},
"first_unread_message_id": "3000000000118021931",
"last_message_id": "30000000001280042740",
"unread_count": 1
}
],
"total_chats_count": 25,
"total_unread_count": 5
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListChatsParams{},
`{
"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.Chats().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListChatsResponse{})
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.Chats) > 0 {
if resp.Chats[0].Chat.ChatStatus == "" {
t.Errorf("Chat status cannot be empty")
}
if resp.Chats[0].Chat.ChatType == "" {
t.Errorf("Chat type cannot be empty")
}
}
}
}
}
func TestSendMessage(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SendMessageParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SendMessageParams{
ChatId: "99feb3fc-a474-469f-95d5-268b470cc607",
Text: "test",
},
`{
"result": "success"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SendMessageParams{},
`{
"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.Chats().SendMessage(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &SendMessageResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestSendFile(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SendFileParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SendFileParams{
ChatId: "99feb3fc-a474-469f-95d5-268b470cc607",
Name: "tempor",
Base64Content: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
},
`{
"result": "success"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SendFileParams{},
`{
"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.Chats().SendFile(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &SendFileResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestChatHistory(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ChatHistoryParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ChatHistoryParams{
ChatId: "18b8e1f9-4ae7-461c-84ea-8e1f54d1a45e",
Direction: "Forward",
FromMessageId: "3000000000118032000",
Limit: 1,
},
`{
"has_next": true,
"messages": [
{
"message_id": "3000000000817031942",
"user": {
"id": "115568",
"type": "Сustomer"
},
"created_at": "2022-07-18T20:58:04.528Z",
"is_read": true,
"data": [
"Здравствуйте, у меня вопрос по вашему товару \"Стекло защитное для смартфонов\", артикул 11223. Подойдет ли он на данную [ модель ](https://www.ozon.ru/product/smartfon-samsung-galaxy-a03s-4-64-gb-chernyy) телефона?"
]
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ChatHistoryParams{},
`{
"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.Chats().History(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ChatHistoryResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestUpdateChat(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *UpdateChatParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&UpdateChatParams{
ChatId: "99feb3fc-a474-469f-95d5-268b470cc607",
FromMessageId: 0,
Limit: 1000,
},
`{
"result": [
{
"id": 3000000000012735500,
"user": {
"id": "15",
"type": "seller"
},
"type": "file",
"text": "",
"file": {
"url": "https://cdn-stg.ozonru.me/s3/fs-chat-api/108a0370-4dfa-11ec-bd02-06a332735108.png",
"mime": "image/png",
"size": 68,
"name": "tempor"
},
"created_at": "2021-11-25T14:14:55Z",
"context": null
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&UpdateChatParams{},
`{
"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.Chats().Update(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UpdateChatResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestCreateNewChat(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *CreateNewChatParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&CreateNewChatParams{
PostingNumber: "47873153-0052-1",
},
`{
"result": {
"chat_id": "5969c331-2e64-44b7-8a0e-ff9526762c62"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&CreateNewChatParams{},
`{
"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.Chats().Create(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CreateNewChatResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.ChatId == "" {
t.Errorf("Chat id cannot be empty")
}
}
}
}
func TestMarkAsRead(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *MarkAsReadParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&MarkAsReadParams{
Chatid: "99feb3fc-a474-469f-95d5-268b470cc607",
FromMessageId: 3000000000118032000,
},
`{
"unread_count": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&MarkAsReadParams{},
`{
"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.Chats().MarkAsRead(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &MarkAsReadResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

686
ozon/common.go Normal file
View File

@@ -0,0 +1,686 @@
package ozon
import "time"
const (
testTimeout = 5 * time.Second
)
type Order string
const (
Ascending Order = "ASC"
Descending Order = "DESC"
)
type GetAnalyticsDataFilterOperation string
const (
Equal GetAnalyticsDataFilterOperation = "EQ"
Greater GetAnalyticsDataFilterOperation = "GT"
GreaterEqual GetAnalyticsDataFilterOperation = "GTE"
Lesser GetAnalyticsDataFilterOperation = "LT"
LesserEqual GetAnalyticsDataFilterOperation = "LTE"
)
type GetAnalyticsDataFilterMetric string
const (
UnknownMetric GetAnalyticsDataFilterMetric = "unknown_metric"
HitsViewSearch GetAnalyticsDataFilterMetric = "hits_view_search"
HistViewPDP GetAnalyticsDataFilterMetric = "hits_view_pdp"
HitsView GetAnalyticsDataFilterMetric = "hist_view"
HitsToCartSearch GetAnalyticsDataFilterMetric = "hits_tocart_search"
HitsToCartPDP GetAnalyticsDataFilterMetric = "hits_tocart_pdp"
SessionViewSearch GetAnalyticsDataFilterMetric = "session_view_search"
SessionViewPDP GetAnalyticsDataFilterMetric = "session_view_pdp"
SessionView GetAnalyticsDataFilterMetric = "session_view"
ConvToCartSearch GetAnalyticsDataFilterMetric = "conv_tocart_search"
ConvToCartPDP GetAnalyticsDataFilterMetric = "conv_tocart_pdp"
ConvToCart GetAnalyticsDataFilterMetric = "conv_tocart"
Revenue GetAnalyticsDataFilterMetric = "revenue"
ReturnsMetric GetAnalyticsDataFilterMetric = "returns"
CancellationsMetric GetAnalyticsDataFilterMetric = "cancellations"
OrderedUnits GetAnalyticsDataFilterMetric = "ordered_units"
DeliveredUnits GetAnalyticsDataFilterMetric = "delivered_units"
PositionCategory GetAnalyticsDataFilterMetric = "position_category"
)
type WarehouseType string
const (
// Ozon warehouses with Fresh delivery
ExpressDarkStore WarehouseType = "EXPRESS_DARK_STORE"
// Ozon warehouses without Fresh delivery
NotExressDarkStore WarehouseType = "NOT_EXPRESS_DARK_STORE"
// All Ozon warehouses
ALLWarehouseType WarehouseType = "ALL"
)
type Language string
const (
Default Language = "DEFAULT"
Russian Language = "RU"
English Language = "EN"
Turkish Language = "TR"
Chinese Language = "ZH_HANS"
)
type AttributeType string
const (
All AttributeType = "ALL"
Required AttributeType = "REQUIRED"
Optional AttributeType = "OPTIONAL"
)
type ListDiscountRequestsStatus string
const (
New ListDiscountRequestsStatus = "NEW"
Seen ListDiscountRequestsStatus = "SEEN"
Approved ListDiscountRequestsStatus = "APPROVED"
PartlyApproved ListDiscountRequestsStatus = "PARTLY_APPROVED"
Declined ListDiscountRequestsStatus = "DECLINED"
AutoDeclined ListDiscountRequestsStatus = "AUTO_DECLINED"
DeclinedByUser ListDiscountRequestsStatus = "DECLINED_BY_USER"
Coupon ListDiscountRequestsStatus = "COUPON"
Purchased ListDiscountRequestsStatus = "PURCHASED"
)
type WorkingDay string
const (
Mon WorkingDay = "1"
Tue WorkingDay = "2"
Wed WorkingDay = "3"
Thu WorkingDay = "4"
Fri WorkingDay = "5"
Sat WorkingDay = "6"
Sun WorkingDay = "7"
)
type GetAnalyticsDataDimension string
const (
UnknownDimension GetAnalyticsDataDimension = "unknownDimension"
SKUDimension GetAnalyticsDataDimension = "sku"
SPUDimension GetAnalyticsDataDimension = "spu"
DayDimension GetAnalyticsDataDimension = "day"
WeekDimension GetAnalyticsDataDimension = "week"
MonthDimension GetAnalyticsDataDimension = "month"
YearDimension GetAnalyticsDataDimension = "year"
Category1Dimension GetAnalyticsDataDimension = "category1"
Category2Dimension GetAnalyticsDataDimension = "category2"
Category3Dimension GetAnalyticsDataDimension = "category3"
Category4Dimension GetAnalyticsDataDimension = "category4"
BrandDimension GetAnalyticsDataDimension = "brand"
ModelIDDimension GetAnalyticsDataDimension = "modelID"
)
type SupplyRequestState string
const (
// request draft. Only for supplies via vDC
Draft SupplyRequestState = "DRAFT"
// selecting supply options. Only for supplies via vDC
SupplyVariantsArranging SupplyRequestState = "SUPPLY_VARIANTS_ARRANGING"
// no supply options, the request is archived. Only for supplies via vDC
HasNoSupplyVariantsArchive SupplyRequestState = "HAS_NO_SUPPLY_VARIANTS_ARCHIVE"
// no supply options. Only for supplies via vDC
HasNoSupplyVariantsNew SupplyRequestState = "HAS_NO_SUPPLY_VARIANTS_NEW"
// supply being approved. Only for supplies via vDC
SupplyVariantsConfirmation SupplyRequestState = "SUPPLY_VARIANTS_CONFIRMATION"
// time reservation
TimeslotBooking SupplyRequestState = "TIMESLOT_BOOKING"
// filling in the data
DATA_FILLING SupplyRequestState = "DATA_FILLING"
// ready for shipment
ReadyToSupply SupplyRequestState = "READY_TO_SUPPLY"
// accepted at the shipping point
AcceptedAtSupplyWarehouse SupplyRequestState = "ACCEPTED_AT_SUPPLY_WAREHOUSE"
// on the way
InTransit SupplyRequestState = "IN_TRANSIT"
// acceptance at the warehouse
AcceptanceAtStorageWarehouse SupplyRequestState = "ACCEPTANCE_AT_STORAGE_WAREHOUSE"
// acts being approved
ReportsConfirmationAwaiting SupplyRequestState = "REPORTS_CONFIRMATION_AWAITING"
// dispute
ReportRejected SupplyRequestState = "REPORT_REJECTED"
// completed
Completed SupplyRequestState = "COMPLETED"
// refused acceptance
RejectedAtSupplyWarehouse SupplyRequestState = "REJECTED_AT_SUPPLY_WAREHOUSE"
// cancelled
Cancelled SupplyRequestState = "CANCELLED"
// overdue
Overdue SupplyRequestState = "OVERDUE"
)
type ShipmentStatus string
const (
// acceptance is in progress
AcceptanceInProgress ShipmentStatus = "acceptance_in_progress"
// arbitration
Arbitration ShipmentStatus = "arbitration"
// awaiting confirmation
AwaitingApprove ShipmentStatus = "awaiting_approve"
// awaiting shipping
AwaitingDeliver ShipmentStatus = "awaiting_deliver"
// awaiting packaging
AwaitingPackaging ShipmentStatus = "awaiting_packaging"
// created
AwaitingVerification ShipmentStatus = "awaiting_verification"
// cancelled
CancelledSubstatus ShipmentStatus = "cancelled"
// delivered
Delivered ShipmentStatus = "delivered"
// delivery is in progress
Delivering ShipmentStatus = "delivering"
// picked up by driver
DriverPickup ShipmentStatus = "driver_pickup"
// not accepted at the sorting center
NotAccepted ShipmentStatus = "not_accepted"
// sent by the seller
SentBySeller ShipmentStatus = "sent_by_seller"
)
type ShipmentSubstatus string
const (
// acceptance in progress
PostingAcceptanceInProgress ShipmentStatus = "posting_acceptance_in_progress"
// arbitrage
PostingInArbitration ShipmentStatus = "posting_in_arbitration"
// created
PostingCreated ShipmentStatus = "posting_created"
// in the freight
PostingInCarriage ShipmentStatus = "posting_in_carriage"
// not added to the freight
PostingNotInCarriage ShipmentStatus = "posting_not_in_carriage"
// registered
PostingRegistered ShipmentStatus = "posting_registered"
// is handed over to the delivery service
PostingTransferringToDelivery ShipmentStatus = "posting_transferring_to_delivery"
// waiting for passport data
PostingAwaitingPassportData ShipmentStatus = "posting_awaiting_passport_data"
// created
PostingCreatedSubstatus ShipmentStatus = "posting_created"
// awaiting registration
PostingAwaitingRegistration ShipmentStatus = "posting_awaiting_registration"
// registration error
PostingRegistrationError ShipmentStatus = "posting_registration_error"
// created
PostingSplitPending ShipmentStatus = "posting_split_pending"
// canceled
PostingCancelled ShipmentStatus = "posting_canceled"
// customer delivery arbitrage
PostingInClientArbitration ShipmentStatus = "posting_in_client_arbitration"
// delivered
PostingDelivered ShipmentStatus = "posting_delivered"
// recieved
PostingReceived ShipmentStatus = "posting_received"
// presumably delivered
PostingConditionallyDelivered ShipmentStatus = "posting_conditionally_delivered"
// courier on the way
PostingInCourierService ShipmentStatus = "posting_in_courier_service"
// at the pick-up point
PostingInPickupPoint ShipmentStatus = "posting_in_pickup_point"
// on the way to the city
PostingOnWayToCity ShipmentStatus = "posting_on_way_to_city"
// on the way to the pick-up point
PostingOnWayToPickupPoint ShipmentStatus = "posting_on_way_to_pickup_point"
// returned to the warehouse
PostingReturnedToWarehouse ShipmentStatus = "posting_returned_to_warehouse"
// is handed over to the courier
PostingTransferredToCourierService ShipmentStatus = "posting_transferred_to_courier_service"
// handed over to the driver
PostingDriverPickup ShipmentStatus = "posting_driver_pick_up"
// not accepted at the sorting center
PostingNotInSortCenter ShipmentStatus = "posting_not_in_sort_center"
// sent by the seller
SentBySellerSubstatus ShipmentStatus = "sent_by_seller"
)
type TPLIntegrationType string
const (
// delivery by the Ozon logistics
OzonTPLType TPLIntegrationType = "ozon"
// delivery by a third-party service, Ozon registers the order
AggregatorTPLType TPLIntegrationType = "aggregator"
// delivery by a third-party service, the seller registers the order
TrackingTPLType TPLIntegrationType = "3pl_tracking"
// delivery by the seller
NonIntegratedTPLType TPLIntegrationType = "non_integrated"
// Russian Post delivery scheme
HybrydTPLType TPLIntegrationType = "hybryd"
)
type DetailsDeliveryItemName string
const (
DirectFlowLogisticSumDetailsDeliveryItemName DetailsDeliveryItemName = "MarketplaceServiceItemDirectFlowLogisticSum"
DropoffDetailsDeliveryItemName DetailsDeliveryItemName = "MarketplaceServiceItemDropoff"
DelivToCustomerDetailsDeliveryItemName DetailsDeliveryItemName = "MarketplaceServiceItemDelivToCustomer"
)
type DetailsReturnServiceName string
const (
ReturnAfterDelivToCustomerDetailsReturnServiceName DetailsReturnServiceName = "MarketplaceServiceItemReturnAfterDelivToCustomer"
ReturnPartGoodsCustomerDetailsReturnServiceName DetailsReturnServiceName = "MarketplaceServiceItemReturnPartGoodsCustomer"
ReturnNotDelivToCustomerDetailsReturnServiceName DetailsReturnServiceName = "MarketplaceServiceItemReturnNotDelivToCustomer"
ReturnFlowLogisticDetailsReturnServiceName DetailsReturnServiceName = "MarketplaceServiceItemReturnFlowLogistic"
)
type DetailsServiceItemName string
const (
OtherMarketAndTech DetailsServiceItemName = "MarketplaceServiceItemOtherMarketAndTechService"
ReturnStorageServiceAtThePickupPointFbsItem DetailsServiceItemName = "MarketplaceReturnStorageServiceAtThePickupPointFbsItem"
SaleReviewsItem DetailsServiceItemName = "MarketplaceSaleReviewsItem"
ServicePremiumCashbackIndividualPoints DetailsServiceItemName = "MarketplaceServicePremiumCashbackIndividualPoints"
ServiceStorageItem DetailsServiceItemName = "MarketplaceServiceStorageItem"
ServiceStockDisposal DetailsServiceItemName = "MarketplaceServiceStockDisposal"
ReturnDisposalServiceFbsItem DetailsServiceItemName = "MarketplaceReturnDisposalServiceFbsItem"
ServiceItemFlexiblePaymentSchedule DetailsServiceItemName = "MarketplaceServiceItemFlexiblePaymentSchedule"
ServiceProcessingSpoilage DetailsServiceItemName = "MarketplaceServiceProcessingSpoilage"
ServiceProcessingIdentifiedSurplus DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedSurplus"
ServiceProcessingIdentifiedDiscrepancies DetailsServiceItemName = "MarketplaceServiceProcessingIdentifiedDiscrepancies"
ServiceItemInternetSiteAdvertising DetailsServiceItemName = "MarketplaceServiceItemInternetSiteAdvertising"
ServiceItemPremiumSubscribtion DetailsServiceItemName = "MarketplaceServiceItemPremiumSubscribtion"
AgencyFeeAggregator3PLGlobalItem DetailsServiceItemName = "MarketplaceAgencyFeeAggregator3PLGlobalItem"
)
type DetailsOtherItemName string
const (
RedistributionOfAcquiringOperation DetailsOtherItemName = "MarketplaceRedistributionOfAcquiringOperation"
CompensationLossOfGoodsOperation DetailsOtherItemName = "MarketplaceSellerCompensationLossOfGoodsOperation"
CorrectionOperation DetailsOtherItemName = "MarketplaceSellerCorrectionOperation"
OperationCorrectionSeller DetailsOtherItemName = "OperationCorrectionSeller"
OperationMarketplaceWithHoldingForUndeliverableGoods DetailsOtherItemName = "OperationMarketplaceWithHoldingForUndeliverableGoods"
OperationClaim DetailsOtherItemName = "OperationClaim"
)
type StrategyType string
const (
MinExtPrice StrategyType = "MIN_EXT_PRICE"
CompPrice StrategyType = "COMP_PRICE"
)
type StrategyUpdateType string
const (
StrategyEnabled StrategyUpdateType = "strategyEnabled"
StrategyDisabled StrategyUpdateType = "strategyDisabled"
StrategyChanged StrategyUpdateType = "strategyChanged"
StrategyCreated StrategyUpdateType = "strategyCreated"
StrategyItemsListChanged StrategyUpdateType = "strategyItemsListChanged"
)
type ShipmentCertificateFilterStatus string
const (
// new
ShitmentCertificateFilterNew ShipmentCertificateFilterStatus = "new"
// retry creation
ShitmentCertificateFilterAwaitingRetry ShipmentCertificateFilterStatus = "awaiting-retry"
// is being packaged
ShitmentCertificateFilterInProcess ShipmentCertificateFilterStatus = "in_process"
// created
ShitmentCertificateFilterSuccess ShipmentCertificateFilterStatus = "success"
// creation error
ShitmentCertificateFilterError ShipmentCertificateFilterStatus = "error"
// sent
ShitmentCertificateFilterSend ShipmentCertificateFilterStatus = "sent"
// received
ShitmentCertificateFilterReceived ShipmentCertificateFilterStatus = "received"
// packaged
ShitmentCertificateFilterFormed ShipmentCertificateFilterStatus = "formed"
// canceled
ShitmentCertificateFilterCancelled ShipmentCertificateFilterStatus = "cancelled"
// in the queue for packaging
ShitmentCertificateFilterPending ShipmentCertificateFilterStatus = "pending"
// in the queue for completion
ShitmentCertificateFilterCompletionEnqueued ShipmentCertificateFilterStatus = "completion_enqueued"
// in the process of completion
ShitmentCertificateFilterCompletionProcessing ShipmentCertificateFilterStatus = "completion_processing"
// completion error
ShitmentCertificateFilterCompletionFailed ShipmentCertificateFilterStatus = "completion_failed"
// in the queue for cancellation
ShitmentCertificateFilterCancelationEnqueued ShipmentCertificateFilterStatus = "cancelation_enqueued"
// in the process of cancellation
ShitmentCertificateFilterCancelationProcessing ShipmentCertificateFilterStatus = "cancelation_processing"
// cancellation error
ShitmentCertificateFilterCancelationFailed ShipmentCertificateFilterStatus = "cancelation_failed"
// completed
ShitmentCertificateFilterCompleted ShipmentCertificateFilterStatus = "completed"
// closed
ShitmentCertificateFilterClosed ShipmentCertificateFilterStatus = "closed"
)
type PRROptionStatus string
const (
// carrying the bulky product using the elevator
PRROptionLift PRROptionStatus = "lift"
// carrying the bulky product upstairs
PRROptionStairs PRROptionStatus = "stairs"
// the customer canceled the service,
// you don't need to lift the shipment
PRROptionNone PRROptionStatus = "none"
// delivery is included in the price.
// According to the offer you need to
// deliver products to the floor
PRROptionDeliveryDefault PRROptionStatus = "delivery_default"
)
type GetFBSReturnsFilterStatus string
const (
ReturnedToSeller GetFBSReturnsFilterStatus = "returned_to_seller"
WaitingForSeller GetFBSReturnsFilterStatus = "waiting_for_seller"
AcceptedFromCustomer GetFBSReturnsFilterStatus = "accepted_from_customer"
CancelledWithCompensation GetFBSReturnsFilterStatus = "cancelled_with_compensation"
ReadyForShipment GetFBSReturnsFilterStatus = "ready_for_shipment"
)
type GetFBOReturnsFilterStatus string
const (
GetFBOReturnsFilterStatusCreated GetFBOReturnsFilterStatus = "Created"
GetFBOReturnsFilterStatusReturnedToOzon GetFBOReturnsFilterStatus = "ReturnedToOzon"
GetFBOReturnsFilterStatusCancelled GetFBOReturnsFilterStatus = "Cancelled"
GetFBOReturnsFilterStatusCancelledWithCompensation GetFBOReturnsFilterStatus = "CancelledWithCompensation"
)
type GetFBOReturnsReturnStatus string
const (
GetFBOReturnsReturnStatusCancelled GetFBOReturnsReturnStatus = "Возврат отменен"
GetFBOReturnsReturnStatusAcceptedFromCustomer GetFBOReturnsReturnStatus = "Принят от покупателя"
GetFBOReturnsReturnStatusReceivedAtOzon GetFBOReturnsReturnStatus = "Получен в Ozon"
)
type DigitalActType string
const (
// acceptance certificate
DigitalActTypeOfAcceptance DigitalActType = "act_of_acceptance"
// discrepancy certificate
DigitalActTypeOfMismatch DigitalActType = "act_of_mismatch"
// surplus certificate
DigitalActTypeOfExcess DigitalActType = "act_of_excess"
)
type PriceStrategy string
const (
// enable
PriceStrategyEnabled PriceStrategy = "ENABLED"
// disable
PriceStrategyDisabled PriceStrategy = "DISABLED"
// don't change anything. Default value
PriceStrategyUnknown PriceStrategy = "UNKNOWN"
)
type FBPFilter string
const (
// all shipments matching other filters will be returned in the response
FBPFilterAll FBPFilter = "all"
// only FBP shipments will be returned
FBPFilterOnly FBPFilter = "only"
// all shipments except FBP will be returned
FBPFilterWithout FBPFilter = "without"
)
type InvoiceCurrency string
const (
// dollar
InvoiceCurrencyUSD InvoiceCurrency = "USD"
// euro
InvoiceCurrencyEUR InvoiceCurrency = "EUR"
// Turkish lira
InvoiceCurrencyTRY InvoiceCurrency = "TRY"
// yuan
InvoiceCurrencyCNY InvoiceCurrency = "CNY"
// ruble
InvoiceCurrencyRUB InvoiceCurrency = "RUB"
// pound sterling
InvoiceCurrencyGBP InvoiceCurrency = "GBP"
)
type ReportType string
const (
// products report
ReportTypeSellerProducts ReportType = "SELLER_PRODUCTS"
// transactions report
ReportTypeSellerTransactions ReportType = "SELLER_TRANSACTIONS"
// product prices report
ReportTypeSellerProductPrices ReportType = "SELLER_PRODUCT_PRICES"
// stocks report
ReportTypeSellerStock ReportType = "SELLER_STOCK"
// products movement report
ReportTypeSellerProductMovement ReportType = "SELLER_PRODUCT_MOVEMENT"
// returns report
ReportTypeSellerReturns ReportType = "SELLER_RETURNS"
// shipments report
ReportTypeSellerPostings ReportType = "SELLER_POSTINGS"
// financial report
ReportTypeSellerFinance ReportType = "SELLER_FINANCE"
)
type ReportInfoStatus string
const (
ReportInfoWaiting ReportInfoStatus = "waiting"
ReportInfoProcessing ReportInfoStatus = "processing"
ReportInfoSuccess ReportInfoStatus = "success"
ReportInfoFailed ReportInfoStatus = "failed"
)
type SKUAvailability string
const (
SKUAvailabilityHidden = "HIDDEN"
SKUAvailabilityAvailable = "AVAILABLE"
// SKU is deleted
SKUAvailabilityUnavailable = "UNAVAILABLE"
)
type RFBSReturnsGroupState string
const (
// All requests
RFBSReturnsGroupStateAll RFBSReturnsGroupState = "All"
// New
RFBSReturnsGroupStateNew RFBSReturnsGroupState = "New"
// Returned product is on the way for check
RFBSReturnsGroupStateDelivering RFBSReturnsGroupState = "Delivering"
// Returned product is being checked
RFBSReturnsGroupStateCheckout RFBSReturnsGroupState = "Checkout"
// Disputed
RFBSReturnsGroupStateArbitration RFBSReturnsGroupState = "Arbitration"
// Approved
RFBSReturnsGroupStateApproved RFBSReturnsGroupState = "Approved"
// Rejected
RFBSReturnsGroupStateRejected RFBSReturnsGroupState = "Rejected"
)
type GetRFBSReturnsCurrency string
const (
// Russian ruble
GetRFBSReturnsCurrencyRUB GetRFBSReturnsCurrency = "RUB"
// Belarusian ruble
GetRFBSReturnsCurrencyBYN GetRFBSReturnsCurrency = "BYN"
// Tenge
GetRFBSReturnsCurrencyKZT GetRFBSReturnsCurrency = "KZT"
// Euro
GetRFBSReturnsCurrencyEUR GetRFBSReturnsCurrency = "EUR"
// US dollar
GetRFBSReturnsCurrencyUSD GetRFBSReturnsCurrency = "USD"
// Yuan
GetRFBSReturnsCurrencyCNY GetRFBSReturnsCurrency = "CNY"
)
type GiveoutStatus string
const (
// Undefined, contact support team
GiveoutStatusUnspecified GiveoutStatus = "GIVEOUT_STATUS_UNSPECIFIED"
// Created
GiveoutStatusCreated GiveoutStatus = "GIVEOUT_STATUS_CREATED"
// Approved
GiveoutStatusApproved GiveoutStatus = "GIVEOUT_STATUS_APPROVED"
// Completed
GiveoutStatusCompleted GiveoutStatus = "GIVEOUT_STATUS_COMPLETED"
// Cancelled
GiveoutStatusCancelled GiveoutStatus = "GIVEOUT_STATUS_CANCELLED"
)
type GiveoutDeliverySchema string
const (
// Undefined, contact support team
GiveoutDeliverySchemaUnspecified GiveoutDeliverySchema = "GIVEOUT_DELIVERY_SCHEMA_UNSPECIFIED"
// FBO
GiveoutDeliverySchemaFBO GiveoutDeliverySchema = "GIVEOUT_DELIVERY_SCHEMA_FBO"
// FBS
GiveoutDeliverySchemaFBS GiveoutDeliverySchema = "GIVEOUT_DELIVERY_SCHEMA_FBS"
)
type MandatoryMarkStatus string
const (
// Labeling is processed
MandatoryMarkStatusProcessing MandatoryMarkStatus = "processing"
// Check is passed
MandatoryMarkStatusPassed MandatoryMarkStatus = "passed"
// Check is failed
MandatoryMarkStatusFailed MandatoryMarkStatus = "failed"
)

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"time"
@@ -13,7 +14,7 @@ type FBO struct {
type GetFBOShipmentsListParams struct {
// Sorting direction
Direction string `json:"dir"`
Direction string `json:"dir,omitempty"`
// Shipment search filter
Filter GetFBOShipmentsListFilter `json:"filter"`
@@ -22,13 +23,13 @@ type GetFBOShipmentsListParams struct {
Limit int64 `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
Offset int64 `json:"offset"`
Offset int64 `json:"offset,omitempty"`
// true if the address transliteration from Cyrillic to Latin is enabled
Translit bool `json:"translit"`
Translit bool `json:"translit,omitempty"`
// Additional fields to add to the response
With GetFBOShipmentsListWith `json:"with"`
With *GetFBOShipmentsListWith `json:"with,omitempty"`
}
// Shipment search filter
@@ -56,111 +57,505 @@ type GetFBOShipmentsListResponse struct {
core.CommonResponse
// Shipments list
Result []struct {
// Additional data for shipment list
AdditionalData []struct {
Key string `json:"key"`
Value string `json:"value"`
} `json:"additional_data"`
Result []GetFBOShipmentsListResult `json:"result"`
}
// Analytical data
AnalyticsData struct {
// Delivery city
City string `json:"city"`
type GetFBOShipmentsListResult struct {
// Additional data for shipment list
AdditionalData []GetFBOShipmentsListResultAdditionalData `json:"additional_data"`
// Delivery method
DeliveryType string `json:"delivery_type"`
// Analytical data
AnalyticsData GetFBOShipmentsListResultAnalyticsData `json:"analytics_data"`
// Indication that the recipient is a legal person
// * true — a legal person,
// * false — a natural person.
IsLegal bool `json:"is_legal"`
// Shipment cancellation reason identifier
CancelReasonId int64 `json:"cancel_reason_id"`
// Premium subscription
IsPremium bool `json:"is_premium"`
// Date and time of shipment creation
CreatedAt time.Time `json:"created_at"`
// Payment method
PaymentTypeGroupName string `json:"payment_type_group_name"`
// Financial data
FinancialData FBOFinancialData `json:"financial_data"`
// Delivery region
Region string `json:"region"`
// Date and time of shipment processing start
InProccessAt time.Time `json:"in_process_at"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
// Identifier of the order to which the shipment belongs
OrderId int64 `json:"order_id"`
// Name of the warehouse from which the order is shipped
WarehouseName string `json:"warehouse_name"`
} `json:"analytics_data"`
// Number of the order to which the shipment belongs
OrderNumber string `json:"order_number"`
// Shipment cancellation reason identifier
CancelReasonId int64 `json:"cancel_reason_id"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Date and time of shipment creation
CreatedAt time.Time `json:"created_at"`
// Number of products in the shipment
Products []FBOPostingProduct `json:"products"`
// Financial data
FinancialData struct {
// Identifier of the cluster, where the shipment is sent from
ClusterFrom string `json:"cluster_from"`
// Shipment status
Status string `json:"status"`
}
// Identifier of the cluster, where the shipment is delivered to
ClusterTo string `json:"cluster_to"`
type GetFBOShipmentsListResultAdditionalData struct {
Key string `json:"key"`
Value string `json:"value"`
}
// Services
PostingServices MarketplaceServices `json:"posting_services"`
type GetFBOShipmentsListResultAnalyticsData struct {
// Delivery city
City string `json:"city"`
// Products list
Products []FinancialDataProduct `json:"products"`
} `json:"financial_data"`
// Delivery method
DeliveryType string `json:"delivery_type"`
// Date and time of shipment processing start
InProccessAt time.Time `json:"in_process_at"`
// Indication that the recipient is a legal person
// * true — a legal person,
// * false — a natural person.
IsLegal bool `json:"is_legal"`
// Identifier of the order to which the shipment belongs
OrderId int64 `json:"order_id"`
// Premium subscription
IsPremium bool `json:"is_premium"`
// Number of the order to which the shipment belongs
OrderNumber string `json:"order_number"`
// Payment method
PaymentTypeGroupName string `json:"payment_type_group_name"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Delivery region
Region string `json:"region"`
// Number of products in the shipment
Products []struct {
// Activation codes for services and digital products
DigitalCodes []string `json:"digital_codes"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
// Currency of your prices. It matches the currency set in the personal account settings
CurrencyCode string `json:"currency_code"`
// Name of the warehouse from which the order is shipped
WarehouseName string `json:"warehouse_name"`
}
// Product name
Name string `json:"name"`
type FBOPostingProduct struct {
// Activation codes for services and digital products
DigitalCodes []string `json:"digital_codes"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Currency of your prices. It matches the currency set in the personal account settings
CurrencyCode string `json:"currency_code"`
// Product price
Price string `json:"price"`
// Product name
Name string `json:"name"`
// Quantity of products in the shipment
Quantity int64 `json:"quantity"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
} `json:"products"`
// Product price
Price string `json:"price"`
// Shipment status
Status string `json:"status"`
} `json:"result"`
// Quantity of products in the shipment
Quantity int64 `json:"quantity"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
}
type FBOFinancialData struct {
// Identifier of the cluster, where the shipment is sent from
ClusterFrom string `json:"cluster_from"`
// Identifier of the cluster, where the shipment is delivered to
ClusterTo string `json:"cluster_to"`
// Services
PostingServices MarketplaceServices `json:"posting_services"`
// Products list
Products []FinancialDataProduct `json:"products"`
}
// Returns a list of shipments for a specified period of time. You can additionally filter the shipments by their status
func (c FBO) GetFBOShipmentsList(params *GetFBOShipmentsListParams) (*GetFBOShipmentsListResponse, error) {
func (c FBO) GetShipmentsList(ctx context.Context, params *GetFBOShipmentsListParams) (*GetFBOShipmentsListResponse, error) {
url := "/v2/posting/fbo/list"
resp := &GetFBOShipmentsListResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
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 GetShipmentDetailsParams struct {
// Shipment number
PostingNumber string `json:"posting_number"`
// true if the address transliteration from Cyrillic to Latin is enabled
Translit bool `json:"translit,omitempty"`
// Additional fields to add to the response
With *GetShipmentDetailsWith `json:"with,omitempty"`
}
type GetShipmentDetailsWith struct {
// Specify true to add analytics data to the response
AnalyticsData bool `json:"analytics_data"`
// Specify true to add financial data to the response
FinancialData bool `json:"financial_data"`
}
type GetShipmentDetailsResponse struct {
core.CommonResponse
// Method result
Result GetShipmentDetailsResult `json:"result"`
}
type GetShipmentDetailsResult struct {
// Additional data
AdditionalData []GetShipmentDetailsResultAdditionalData `json:"additional_data"`
// Analytical data
AnalyticsData GetShipmentDetailsResultAnalyticsData `json:"analytics_data"`
// Shipment cancellation reason identifier
CancelReasonId int64 `json:"cancel_reason_id"`
// Date and time of shipment creation
CreatedAt time.Time `json:"created_at"`
// Financial data
FinancialData FBOFinancialData `json:"financial_data"`
// Date and time of shipment processing start
InProcessAt time.Time `json:"in_process_at"`
// Identifier of the order to which the shipment belongs
OrderId int64 `json:"order_id"`
// Number of the order to which the shipment belongs
OrderNumber string `json:"order_number"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Number of products in the shipment
Products []FBOPostingProduct `json:"products"`
// Shipment status
Status string `json:"status"`
}
type GetShipmentDetailsResultAdditionalData struct {
Key string `json:"key"`
Value string `json:"value"`
}
type GetShipmentDetailsResultAnalyticsData struct {
// Delivery city
City string `json:"city"`
// Delivery method
DeliveryType string `json:"delivery_type"`
// Indication that the recipient is a legal person:
// - true — a legal person
// - false — a natural person
IsLegal bool `json:"is_legal"`
// Premium subscription
IsPremium bool `json:"is_premium"`
// Payment method
PaymentTypeGroupName string `json:"payment_type_group_name"`
// Delivery region
Region string `json:"region"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
// Name of the warehouse from which the order is shipped
WarehouseName string `json:"warehouse_name"`
}
// Returns information about the shipment by its identifier
func (c FBO) GetShipmentDetails(ctx context.Context, params *GetShipmentDetailsParams) (*GetShipmentDetailsResponse, error) {
url := "/v2/posting/fbo/get"
resp := &GetShipmentDetailsResponse{}
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 ListSupplyRequestsParams struct {
// Number of the page returned in the request
Page int32 `json:"page"`
// Number of elements on the page
PageSize int32 `json:"page_size"`
// Filter on status of a supply by request
States []SupplyRequestState `json:"states"`
}
type ListSupplyRequestsResponse struct {
core.CommonResponse
// Indicates that the response contains not the entire array of supply requests:
// - true — make a new request with a different page and page_size values to get information on the remaining requests;
// - false — the entire array of requests for the filter specified in the request was returned in the response
HasNext bool `json:"has_next"`
// Supply requests list
SupplyOrders []SupplyRequestCommonResponse `json:"supply_orders"`
// Total requests number
TotalSupplyOrdersCount int32 `json:"total_supply_orders_count"`
}
type SupplyRequestCommonResponse struct {
// Supply request creation date
CreatedAt string `json:"created_at"`
// Local time supply interval
LocalTimeslot SupplyRequestCommonResponseLocalTimeslot `json:"local_timeslot"`
// Date from which you want to bring the supply to the warehouse. Only for supplies via vDC
PreferredSupplyDateFrom string `json:"preferred_supply_date_from"`
// Date by which you want to bring the supply to the warehouse. Only for supplies via vDC
PreferredSupplyDateTo string `json:"preferred_supply_date_to"`
// Your own warehouse from which you'll take the products to the supply warehouse.
// Only for supplies via vDC
SellerWarehouse SupplyRequestSellerWarehouse `json:"seller_warehouse"`
// Status of a supply by request
State string `json:"state"`
// Supply request identifier
SupplyOrderId int64 `json:"supply_order_id"`
// Supply request number
SupplyOrderNumber string `json:"supply_order_number"`
// Supply warehouse
SupplyWarehouse SupplyRequestCommonResponseSupplyWarehouse `json:"supply_warehouse"`
// time_left_to_prepare_supply
TimeLeftToPrepareSupply int64 `json:"time_left_to_prepare_supply"`
// Time in seconds left to select the supply option. Only for supplies via vDC
TimeLeftToSelectSupplyVariant int64 `json:"time_left_to_select_supply_variant"`
// total_items_count
TotalItemsCount int32 `json:"total_items_count"`
// Total number of items in the request
TotalQuantity int32 `json:"total_quantity"`
}
type SupplyRequestSellerWarehouse struct {
// Warehouse address
Address string `json:"address"`
// Warehouse name
Name string `json:"name"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
type SupplyRequestCommonResponseLocalTimeslot struct {
// Interval start
From string `json:"from"`
// Interval end
To string `json:"to"`
}
type SupplyRequestCommonResponseSupplyWarehouse struct {
// Warehouse address
Address string `json:"address"`
// Warehouse name
Name string `json:"name"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
// Method for getting a list of supply requests to the Ozon warehouse.
// Requests with supply both to a specific warehouse and via a virtual
// distribution center (vDC) are taken into account
func (c FBO) ListSupplyRequests(ctx context.Context, params *ListSupplyRequestsParams) (*ListSupplyRequestsResponse, error) {
url := "/v1/supply-order/list"
resp := &ListSupplyRequestsResponse{}
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 GetSupplyRequestInfoParams struct {
// Supply request identifier
SupplyOrderId int64 `json:"supply_order_id"`
}
type GetSupplyRequestInfoResponse struct {
core.CommonResponse
SupplyRequestCommonResponse
// Driver and car information
VehicleInfo GetSupplyRequestInfoVehicle `json:"vehicle_info"`
}
type GetSupplyRequestInfoVehicle struct {
// Driver name
DriverName string `json:"driver_name"`
// Driver phone number
DriverPhone string `json:"driver_phone"`
// Car model
VehicleModel string `json:"vehicle_model"`
// Car number
VehicleNumber string `json:"vehicle_number"`
}
// Method for getting detailed information on a supply request.
// Requests with supply both to a specific warehouse and via a
// virtual distribution center (vDC) are taken into account
func (c FBO) GetSupplyRequestInfo(ctx context.Context, params *GetSupplyRequestInfoParams) (*GetSupplyRequestInfoResponse, error) {
url := "/v1/supply-order/get"
resp := &GetSupplyRequestInfoResponse{}
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 ListProductsInSupplyRequestParams struct {
// Number of the page returned in the query
Page int32 `json:"page"`
// Number of elements on the page
PageSize int32 `json:"page_size"`
// Supply request identifier
SupplyOrderId int64 `json:"supply_order_id"`
}
type ListProductsInSupplyRequestResponse struct {
core.CommonResponse
// Indicates that the response contains not the entire array of supply requests:
// - true — make a new request with a different page and page_size values to get the remaining products;
// - false — the entire array of product was returned in the response
HasNext bool `json:"has_next"`
// Products list
Items []ListProductsInSupplyRequestItem `json:"items"`
// Total number of products in the request
TotalItemsCount int32 `json:"total_items_count"`
}
type ListProductsInSupplyRequestItem struct {
// Link to product image
IconPath string `json:"icon_path"`
// Product name
Name string `json:"name"`
// Product ID
OfferId string `json:"offer_id"`
// Product quantity
Quantity int64 `json:"quantity"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
}
// List of products in the sullpy request
func (c FBO) ListProductsInSupplyRequest(ctx context.Context, params *ListProductsInSupplyRequestParams) (*ListProductsInSupplyRequestResponse, error) {
url := "/v1/supply-order/items"
resp := &ListProductsInSupplyRequestResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type GetWarehouseWorkloadResponse struct {
core.CommonResponse
// Method result
Result []GetWarehouseWorkloadResult `json:"result"`
}
type GetWarehouseWorkloadResult struct {
// Workload
Schedule GetWarehouseWorkloadResultSchedule `json:"schedule"`
// Warehouse
Warehouse GetWarehouseWorkloadResultWarehouse `json:"warehouse"`
}
type GetWarehouseWorkloadResultSchedule struct {
// Data on the products quantity supplied to the warehouse
Capacity []GetWarehouseWorkloadResultScheduleCapacity `json:"capacity"`
// The closest available date for supply, local time
Date time.Time `json:"date"`
}
type GetWarehouseWorkloadResultScheduleCapacity struct {
// Period start, local time
Start time.Time `json:"start"`
// Period end, local time
End time.Time `json:"end"`
// Average number of products that the warehouse can accept per day for the period
Value int32 `json:"value"`
}
type GetWarehouseWorkloadResultWarehouse struct {
// Warehouse identifier
Id string `json:"id"`
// Warehouse name
Name string `json:"name"`
}
// Method returns a list of active Ozon warehouses with information about their average workload in the nearest future
func (c FBO) GetWarehouseWorkload(ctx context.Context) (*GetWarehouseWorkloadResponse, error) {
url := "/v1/supplier/available_warehouses"
resp := &GetWarehouseWorkloadResponse{}
response, err := c.client.Request(ctx, http.MethodGet, url, nil, resp, nil)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"testing"
@@ -30,7 +31,7 @@ func TestGetFBOShipmentsList(t *testing.T) {
Limit: 5,
Offset: 0,
Translit: true,
With: GetFBOShipmentsListWith{
With: &GetFBOShipmentsListWith{
AnalyticsData: true,
FinancialData: true,
},
@@ -44,7 +45,7 @@ func TestGetFBOShipmentsList(t *testing.T) {
"status": "delivered",
"cancel_reason_id": 0,
"created_at": "2021-09-01T00:23:45.607Z",
"in_process_at": "2021-09-01T00:25:30.120Z",
"in_process_at": "2021-09-01T00:25:30.12Z",
"products": [
{
"sku": 160249683,
@@ -133,11 +134,432 @@ func TestGetFBOShipmentsList(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.FBO().GetFBOShipmentsList(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBO().GetShipmentsList(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetFBOShipmentsListResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetShipmentDetails(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetShipmentDetailsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetShipmentDetailsParams{
PostingNumber: "50520644-0012-7",
Translit: true,
With: &GetShipmentDetailsWith{
AnalyticsData: true,
FinancialData: true,
},
},
`{
"result": {
"order_id": 354679434,
"order_number": "50520644-0012",
"posting_number": "50520644-0012-7",
"status": "delivered",
"cancel_reason_id": 0,
"created_at": "2021-09-01T00:34:56.563Z",
"in_process_at": "2021-09-01T00:34:56.103Z",
"products": [
{
"sku": 254665483,
"name": "Мочалка натуральная из люфы с деревянной ручкой",
"quantity": 1,
"offer_id": "PS1033",
"price": "137.00",
"digital_codes": [],
"currency_code": "RUB"
}
],
"analytics_data": {
"region": "МОСКВА",
"city": "Москва",
"delivery_type": "Courier",
"is_premium": false,
"payment_type_group_name": "Карты оплаты",
"warehouse_id": 15431806189000,
"warehouse_name": "ХОРУГВИНО_РФЦ",
"is_legal": false
},
"financial_data": {
"products": [
{
"commission_amount": 13.7,
"commission_percent": 10,
"payout": 123.3,
"product_id": 254665483,
"currency_code": "RUB",
"old_price": 198,
"price": 137,
"total_discount_value": 61,
"total_discount_percent": 30.81,
"actions": [
"Системная виртуальная скидка селлера"
],
"picking": null,
"quantity": 0,
"client_price": "",
"item_services": {
"marketplace_service_item_fulfillment": -31.5,
"marketplace_service_item_pickup": 0,
"marketplace_service_item_dropoff_pvz": 0,
"marketplace_service_item_dropoff_sc": 0,
"marketplace_service_item_dropoff_ff": 0,
"marketplace_service_item_direct_flow_trans": -5,
"marketplace_service_item_return_flow_trans": 0,
"marketplace_service_item_deliv_to_customer": -20,
"marketplace_service_item_return_not_deliv_to_customer": 0,
"marketplace_service_item_return_part_goods_customer": 0,
"marketplace_service_item_return_after_deliv_to_customer": 0
}
}
],
"posting_services": {
"marketplace_service_item_fulfillment": 0,
"marketplace_service_item_pickup": 0,
"marketplace_service_item_dropoff_pvz": 0,
"marketplace_service_item_dropoff_sc": 0,
"marketplace_service_item_dropoff_ff": 0,
"marketplace_service_item_direct_flow_trans": 0,
"marketplace_service_item_return_flow_trans": 0,
"marketplace_service_item_deliv_to_customer": 0,
"marketplace_service_item_return_not_deliv_to_customer": 0,
"marketplace_service_item_return_part_goods_customer": 0,
"marketplace_service_item_return_after_deliv_to_customer": 0
}
},
"additional_data": []
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetShipmentDetailsParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBO().GetShipmentDetails(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetShipmentDetailsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.PostingNumber != test.params.PostingNumber {
t.Errorf("Posting numbers in request and response are not equal")
}
}
}
}
func TestListSupplyRequests(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListSupplyRequestsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListSupplyRequestsParams{
Page: 0,
PageSize: 0,
States: []SupplyRequestState{AcceptanceAtStorageWarehouse},
},
`{
"has_next": true,
"supply_orders": [
{
"created_at": "string",
"local_timeslot": {
"from": "string",
"to": "string"
},
"preferred_supply_date_from": "string",
"preferred_supply_date_to": "string",
"state": "string",
"supply_order_id": 0,
"supply_order_number": "string",
"supply_warehouse": {
"address": "string",
"name": "string",
"warehouse_id": 0
},
"time_left_to_prepare_supply": 0,
"time_left_to_select_supply_variant": 0,
"total_items_count": 0,
"total_quantity": 0
}
],
"total_supply_orders_count": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListSupplyRequestsParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBO().ListSupplyRequests(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListSupplyRequestsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetSupplyRequestInfo(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetSupplyRequestInfoParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetSupplyRequestInfoParams{
SupplyOrderId: 0,
},
`{
"created_at": "string",
"local_timeslot": {
"from": "string",
"to": "string"
},
"preferred_supply_date_from": "string",
"preferred_supply_date_to": "string",
"seller_warehouse": {
"address": "string",
"name": "string",
"warehouse_id": 0
},
"state": "string",
"supply_order_id": 0,
"supply_order_number": "string",
"supply_warehouse": {
"address": "string",
"name": "string",
"warehouse_id": 0
},
"time_left_to_prepare_supply": 0,
"time_left_to_select_supply_variant": 0,
"total_items_count": 0,
"total_quantity": 0,
"vehicle_info": {
"driver_name": "string",
"driver_phone": "string",
"vehicle_model": "string",
"vehicle_number": "string"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetSupplyRequestInfoParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBO().GetSupplyRequestInfo(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetSupplyRequestInfoResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestListProductsInSupplyRequest(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListProductsInSupplyRequestParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListProductsInSupplyRequestParams{
Page: 0,
PageSize: 0,
SupplyOrderId: 0,
},
`{
"has_next": true,
"items": [
{
"icon_path": "string",
"name": "string",
"offer_id": "string",
"quantity": 0,
"sku": 0
}
],
"total_items_count": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListProductsInSupplyRequestParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBO().ListProductsInSupplyRequest(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListProductsInSupplyRequestResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetWarehouseWorkload(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"result": [
{
"schedule": {
"capacity": [
{
"start": "2019-08-24T14:15:22Z",
"end": "2019-08-24T14:15:22Z",
"value": 0
}
],
"date": "2019-08-24T14:15:22Z"
},
"warehouse": {
"id": "string",
"name": "string"
}
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBO().GetWarehouseWorkload(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetWarehouseWorkloadResponse{})
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

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"time"
@@ -20,137 +21,143 @@ type ReportOnSoldProductsResponse struct {
core.CommonResponse
// Query result
Result []struct {
// Report title page
Header []struct {
// Report ID
Id string `json:"num"`
Result ReportonSoldProductsResult `json:"result"`
}
// Report generation date
DocDate string `json:"doc_date"`
type ReportonSoldProductsResult struct {
// Report title page
Header ReportOnSoldProductsResultHeader `json:"header"`
// Date of the offer agreement
ContractDate string `json:"contract_date"`
// Report table
Rows []ReportOnSoldProductsResultRow `json:"rows"`
}
// Offer agreement number
ContractNum string `json:"contract_num"`
type ReportOnSoldProductsResultHeader struct {
// Report ID
Id string `json:"num"`
// Currency of your prices
CurrencyCode string `json:"currency_code"`
// Report generation date
DocDate string `json:"doc_date"`
// Amount to accrue
DocAmount float64 `json:"doc_amount"`
// Date of the offer agreement
ContractDate string `json:"contract_date"`
// Amount to accrue with VAT
VATAmount float64 `json:"vat_amount"`
// Offer agreement number
ContractNum string `json:"contract_num"`
// Payer's TIN
PayerINN string `json:"payer_inn"`
// Currency of your prices
CurrencyCode string `json:"currency_code"`
// Payer's Tax Registration Reason Code (KPP)
PayerKPP string `json:"payer_kpp"`
// Amount to accrue
DocAmount float64 `json:"doc_amount"`
// Payer's name
PayerName string `json:"payer_name"`
// Amount to accrue with VAT
VATAmount float64 `json:"vat_amount"`
// Recipient's TIN
RecipientINN string `json:"rcv_inn"`
// Payer's TIN
PayerINN string `json:"payer_inn"`
// Recipient's Tax Registration Reason Code (KPP)
RecipientKPP string `json:"rcv_kpp"`
// Payer's Tax Registration Reason Code (KPP)
PayerKPP string `json:"payer_kpp"`
// Recipient's name
RecipientName string `json:"rcv_name"`
// Payer's name
PayerName string `json:"payer_name"`
// Period start in the report
StartDate string `json:"start_date"`
// Recipient's TIN
RecipientINN string `json:"rcv_inn"`
// Period end in the report
StopDate string `json:"stop_date"`
} `json:"header"`
// Recipient's Tax Registration Reason Code (KPP)
RecipientKPP string `json:"rcv_kpp"`
// Report table
Rows []struct {
// Row number
RowNumber int32 `json:"row_number"`
// Recipient's name
RecipientName string `json:"rcv_name"`
// Product ID
ProductId int64 `json:"product_id"`
// Period start in the report
StartDate string `json:"start_date"`
// Product name
ProductName string `json:"product_name"`
// Period end in the report
StopDate string `json:"stop_date"`
}
// Product barcode
Barcode string `json:"barcode"`
type ReportOnSoldProductsResultRow struct {
// Row number
RowNumber int32 `json:"row_number"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product ID
ProductId int64 `json:"product_id"`
// Sales commission by category
CommissionPercent float64 `json:"commission_percent"`
// Product name
ProductName string `json:"product_name"`
// Seller's price with their discount
Price float64 `json:"price"`
// Product barcode
Barcode string `json:"barcode"`
// Selling price: the price at which the customer purchased the product. For sold products
PriceSale float64 `json:"price_sale"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// 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"`
// Sales commission by category
CommissionPercent float64 `json:"commission_percent"`
// Commission for sold products, including discounts and extra charges
SaleCommission float64 `json:"sale_commission"`
// Seller's price with their discount
Price float64 `json:"price"`
// Extra charge at the expense of Ozon.
//
// Amount that Ozon will compensate the seller if the Ozon discount is greater than or equal to the sales commission
SaleDiscount float64 `json:"sale_discount"`
// Selling price: the price at which the customer purchased the product. For sold products
PriceSale float64 `json:"price_sale"`
// Total accrual for the products sold.
//
// Amount after deduction of sales commission, application of discounts and extra charges
SalePriceSeller float64 `json:"sale_price_seller"`
// 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"`
// Quantity of products sold at the price_sale price
SaleQuantity int32 `json:"sale_qty"`
// Commission for sold products, including discounts and extra charges
SaleCommission float64 `json:"sale_commission"`
// Price at which the customer purchased the product. For returned products
ReturnSale float64 `json:"return_sale"`
// Extra charge at the expense of Ozon.
//
// Amount that Ozon will compensate the seller if the Ozon discount is greater than or equal to the sales commission
SaleDiscount float64 `json:"sale_discount"`
// Cost of returned products, taking into account the quantity and regional coefficients.
// Calculation is carried out at the return_sale price
ReturnAmount float64 `json:"return_amount"`
// Total accrual for the products sold.
//
// Amount after deduction of sales commission, application of discounts and extra charges
SalePriceSeller float64 `json:"sale_price_seller"`
// Commission including the quantity of products, discounts and extra charges.
// Ozon compensates it for the returned products
ReturnCommission float64 `json:"return_commission"`
// Quantity of products sold at the price_sale price
SaleQuantity int32 `json:"sale_qty"`
// Extra charge at the expense of Ozon.
//
// Amount of the discount at the expense of Ozon on returned products.
// Ozon will compensate it to the seller if the Ozon discount is greater than or equal to the sales commission
ReturnDiscount float64 `json:"return_discount"`
// Price at which the customer purchased the product. For returned products
ReturnSale float64 `json:"return_sale"`
// Amount charged to the seller for returned products after deducing sales commissions, applying discounts and extra charges
ReturnPriceSeller float64 `json:"return_price_seller"`
// Cost of returned products, taking into account the quantity and regional coefficients.
// Calculation is carried out at the return_sale price
ReturnAmount float64 `json:"return_amount"`
// Quantity of returned products
ReturnQuantity int32 `json:"return_qty"`
} `json:"rows"`
} `json:"result"`
// Commission including the quantity of products, discounts and extra charges.
// Ozon compensates it for the returned products
ReturnCommission float64 `json:"return_commission"`
// Extra charge at the expense of Ozon.
//
// Amount of the discount at the expense of Ozon on returned products.
// Ozon will compensate it to the seller if the Ozon discount is greater than or equal to the sales commission
ReturnDiscount float64 `json:"return_discount"`
// Amount charged to the seller for returned products after deducing sales commissions, applying discounts and extra charges
ReturnPriceSeller float64 `json:"return_price_seller"`
// Quantity of returned products
ReturnQuantity int32 `json:"return_qty"`
}
// Returns information on products sold and returned within a month. Canceled or non-purchased products are not included.
//
// Report is returned no later than the 5th day of the next month
func (c Finance) ReportOnSoldProducts(params *ReportOnSoldProductsParams) (*ReportOnSoldProductsResponse, error) {
func (c Finance) ReportOnSoldProducts(ctx context.Context, params *ReportOnSoldProductsParams) (*ReportOnSoldProductsResponse, error) {
url := "/v1/finance/realization"
resp := &ReportOnSoldProductsResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -196,49 +203,200 @@ type GetTotalTransactionsSumResponse struct {
core.CommonResponse
// Method result
Result struct {
// Total cost of products and returns for specified period
AccrualsForSale float64 `json:"accruals_for_sale"`
Result GetTotalTransactionsSumResult `json:"result"`
}
// Compensations
CompensationAmount float64 `json:"compensatino_amount"`
type GetTotalTransactionsSumResult struct {
// Total cost of products and returns for specified period
AccrualsForSale float64 `json:"accruals_for_sale"`
// Charges for delivery and returns when working under rFBS scheme
MoneyTransfer float64 `json:"money_transfer"`
// Compensations
CompensationAmount float64 `json:"compensation_amount"`
// Other accurals
OthersAmount float64 `json:"others_amount"`
// Charges for delivery and returns when working under rFBS scheme
MoneyTransfer float64 `json:"money_transfer"`
// Cost of shipment processing, orders packaging, pipeline and last mile services, and delivery cost before the new commissions and rates applied from February 1, 2021.
//
// Pipeline is delivery of products from one cluster to another.
//
// Last mile is products delivery to the pick-up point, parcle terminal, or by courier
ProcessingAndDelivery float64 `json:"processing_and_delivery"`
// Other accurals
OthersAmount float64 `json:"others_amount"`
// Cost of reverse pipeline, returned, canceled and unredeemed orders processing, and return cost before the new commissions and rates applied from February 1, 2021.
//
// Pipeline is delivery of products from one cluster to another.
//
// Last mile is products delivery to the pick-up point, parcle terminal, or by courier
RefundsAndCancellations float64 `json:"refunds_and_cancellations"`
// Cost of shipment processing, orders packaging, pipeline and last mile services, and delivery cost before the new commissions and rates applied from February 1, 2021.
//
// Pipeline is delivery of products from one cluster to another.
//
// Last mile is products delivery to the pick-up point, parcle terminal, or by courier
ProcessingAndDelivery float64 `json:"processing_and_delivery"`
// The commission withheld when the product was sold and refunded when the product was returned
SaleCommission float64 `json:"sale_commission"`
// Cost of reverse pipeline, returned, canceled and unredeemed orders processing, and return cost before the new commissions and rates applied from February 1, 2021.
//
// Pipeline is delivery of products from one cluster to another.
//
// Last mile is products delivery to the pick-up point, parcle terminal, or by courier
RefundsAndCancellations float64 `json:"refunds_and_cancellations"`
// The additional services cost that are not directly related to deliveries and returns.
// For example, promotion or product placement
ServicesAmount float64 `json:"services_amount"`
} `json:"result"`
// The commission withheld when the product was sold and refunded when the product was returned
SaleCommission float64 `json:"sale_commission"`
// The additional services cost that are not directly related to deliveries and returns.
// For example, promotion or product placement
ServicesAmount float64 `json:"services_amount"`
}
// Returns total sums for transactions for specified period
func (c Finance) GetTotalTransactionsSum(params *GetTotalTransactionsSumParams) (*GetTotalTransactionsSumResponse, error) {
func (c Finance) GetTotalTransactionsSum(ctx context.Context, params *GetTotalTransactionsSumParams) (*GetTotalTransactionsSumResponse, error) {
url := "/v3/finance/transaction/totals"
resp := &GetTotalTransactionsSumResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
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 ListTransactionsParams struct {
// Filter
Filter ListTransactionsFilter `json:"filter"`
// Number of the page returned in the request
Page int64 `json:"page"`
// Number of items on the page
PageSize int64 `json:"page_size"`
}
type ListTransactionsFilter struct {
// Filter by date
Date ListTransactionsFilterDate `json:"date"`
// Operation type
OperationType []string `json:"operation_type"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Transaction type
TransactionType string `json:"transaction_type"`
}
type ListTransactionsFilterDate struct {
// Period start.
//
// Format: YYYY-MM-DDTHH:mm:ss.sssZ.
// Example: 2019-11-25T10:43:06.51
From time.Time `json:"from"`
// Period end.
//
// Format: YYYY-MM-DDTHH:mm:ss.sssZ.
// Example: 2019-11-25T10:43:06.51
To time.Time `json:"to"`
}
type ListTransactionsResponse struct {
core.CommonResponse
// Method result
Result ListTransactionsResult `json:"result"`
}
type ListTransactionsResult struct {
// Transactions infromation
Operations []ListTransactionsResultOperation `json:"operations"`
// Number of pages
PageCount int64 `json:"page_count"`
// Number of products
RowCount int64 `json:"row_count"`
}
type ListTransactionsResultOperation struct {
// Cost of the products with seller's discounts applied
AccrualsForSale float64 `json:"accruals_for_sale"`
// Total transaction sum
Amount float64 `json:"amount"`
// Delivery cost for charges by rates that were in effect until February 1, 2021, and for charges for bulky products
DeliveryCharge float64 `json:"delivery_charge"`
// Product information
Items []ListTransactionsResultOperationItem `json:"items"`
// Operation date
OperationDate string `json:"operation_date"`
// Operation identifier
OperationId int64 `json:"operation_id"`
// Operation type
OperationType string `json:"operation_type"`
// Operation type name
OperationTypeName string `json:"operation_type_name"`
// Shipment information
Posting ListTransactionsResultOperationPosting `json:"posting"`
// Returns and cancellation cost for charges by rates that were in effect until February 1, 2021, and for charges for bulky products
ReturnDeliveryCharge float64 `json:"return_delivery_charge"`
// Sales commission or sales commission refund
SaleCommission float64 `json:"sale_commission"`
// Additional services
Services []ListTransactionsResultOperationService `json:"services"`
// Transaction type
Type string `json:"type"`
}
type ListTransactionsResultOperationItem struct {
// Product name
Name string `json:"name"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
}
type ListTransactionsResultOperationPosting struct {
// Delivery scheme:
// - FBO — delivery to Ozon warehouse
// - FBS — delivery from seller's warehouse
// - RFBS — delivery service of seller's choice
// - Crossborder — delivery from abroad
DeliverySchema string `json:"delivery_schema"`
// Date the product was accepted for processing
OrderDate string `json:"order_date"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
type ListTransactionsResultOperationService struct {
// Service name
Name string `json:"name"`
// Price
Price float64 `json:"price"`
}
// Returns detailed information on all accruals. The maximum period for which you can get information in one request is 1 month.
//
// If you don't specify the posting_number in request, the response contains all shipments for the specified period or shipments of a certain type
func (c Finance) ListTransactions(ctx context.Context, params *ListTransactionsParams) (*ListTransactionsResponse, error) {
url := "/v3/finance/transaction/list"
resp := &ListTransactionsResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"testing"
@@ -25,53 +26,49 @@ func TestReportOnSoldProducts(t *testing.T) {
Date: "2022-09",
},
`{
"result": [
{
"header": [
{
"doc_date": "2022-09-22",
"num": "string",
"start_date": "2022-09-02",
"stop_date": "2022-09-22",
"contract_date": "2022-09-02",
"contract_num": "string",
"payer_name": "string",
"payer_inn": "string",
"payer_kpp": "string",
"rcv_name": "string",
"rcv_inn": "string",
"rcv_kpp": "string",
"doc_amount": 1,
"vat_amount": 1,
"currency_code": "string"
}
],
"rows": [
{
"row_number": 0,
"product_id": 0,
"product_name": "string",
"offer_id": "string",
"barcode": "string",
"price": 0,
"commission_percent": 0,
"price_sale": 0,
"sale_qty": 0,
"sale_amount": 0,
"sale_discount": 0,
"sale_commission": 0,
"sale_price_seller": 0,
"return_sale": 0,
"return_qty": 0,
"return_amount": 0,
"return_discount": 0,
"return_commission": 0,
"return_price_seller": 0
}
]
}
]
}`,
"result": {
"header": {
"doc_date": "2022-09-22",
"num": "string",
"start_date": "2022-09-02",
"stop_date": "2022-09-22",
"contract_date": "2022-09-02",
"contract_num": "string",
"payer_name": "string",
"payer_inn": "string",
"payer_kpp": "string",
"rcv_name": "string",
"rcv_inn": "string",
"rcv_kpp": "string",
"doc_amount": 1,
"vat_amount": 1,
"currency_code": "string"
},
"rows": [
{
"row_number": 0,
"product_id": 0,
"product_name": "string",
"offer_id": "string",
"barcode": "string",
"price": 0,
"commission_percent": 0,
"price_sale": 0,
"sale_qty": 0,
"sale_amount": 0,
"sale_discount": 0,
"sale_commission": 0,
"sale_price_seller": 0,
"return_sale": 0,
"return_qty": 0,
"return_amount": 0,
"return_discount": 0,
"return_commission": 0,
"return_price_seller": 0
}
]
}
}`,
"",
},
// Test No Client-Id or Api-Key
@@ -90,11 +87,15 @@ func TestReportOnSoldProducts(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Finance().ReportOnSoldProducts(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Finance().ReportOnSoldProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ReportOnSoldProductsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -158,11 +159,107 @@ func TestGetTotalTransactionsSum(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Finance().GetTotalTransactionsSum(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Finance().GetTotalTransactionsSum(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetTotalTransactionsSumResponse{})
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 TestListTransactions(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListTransactionsParams
response string
errorMessage string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListTransactionsParams{
Filter: ListTransactionsFilter{
Date: ListTransactionsFilterDate{
From: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-01T00:00:00.000Z"),
To: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-02T00:00:00.000Z"),
},
TransactionType: "ALL",
},
Page: 1,
PageSize: 1000,
},
`{
"result": {
"operations": [
{
"operation_id": 11401182187840,
"operation_type": "MarketplaceMarketingActionCostOperation",
"operation_date": "2021-11-01 00:00:00",
"operation_type_name": "Услуги продвижения товаров",
"delivery_charge": 0,
"return_delivery_charge": 0,
"accruals_for_sale": 0,
"sale_commission": 0,
"amount": -6.46,
"type": "services",
"posting": {
"delivery_schema": "",
"order_date": "",
"posting_number": "",
"warehouse_id": 0
},
"items": [],
"services": []
}
],
"page_count": 1,
"row_count": 355
}
}`,
"",
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListTransactionsParams{},
`{
"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().ListTransactions(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListTransactionsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}

116
ozon/invoices.go Normal file
View File

@@ -0,0 +1,116 @@
package ozon
import (
"context"
"net/http"
"time"
core "github.com/diphantxm/ozon-api-client"
)
type Invoices struct {
client *core.Client
}
type CreateUpdateProformaLinkParams struct {
// Shipment number
PostingNumber string `json:"posting_number"`
// Proforma invoice link
URL string `json:"url"`
// Invoice HS-code. Pass a number up to 12 characters long
HSCode string `json:"hs_code"`
// Invoice date
Date time.Time `json:"date"`
// Invoice number. The number can contain letters and digits, maximum length is 50 characters
Number string `json:"number"`
// Cost stated in the invoice. The fractional part is separated by decimal point, up to two digits after the decimal poin
Price float64 `json:"price"`
// Invoice currency
PriceCurrency InvoiceCurrency `json:"price_currency" default:"USD"`
}
type CreateUpdateProformaLinkResponse struct {
core.CommonResponse
// Method result
Result bool `json:"result"`
}
// Create or edit proforma invoice link for VAT refund to Turkey sellers
func (c Invoices) CreateUpdate(ctx context.Context, params *CreateUpdateProformaLinkParams) (*CreateUpdateProformaLinkResponse, error) {
url := "/v1/invoice/create-or-update"
resp := &CreateUpdateProformaLinkResponse{}
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 GetProformaLinkParams struct {
// Shipment number
PostingNumber string `json:"posting_number"`
}
type GetProformaLinkResponse struct {
core.CommonResponse
// Method result
Result GetProformaLinkResult `json:"result"`
}
type GetProformaLinkResult struct {
// Proforma invoice link
FileURL string `json:"file_url"`
}
// Get a proforma invoice link
func (c Invoices) Get(ctx context.Context, params *GetProformaLinkParams) (*GetProformaLinkResponse, error) {
url := "/v1/invoice/get"
resp := &GetProformaLinkResponse{}
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 DeleteProformaLinkParams struct {
// Shipment number
PostingNumber string `json:"posting_number"`
}
type DeleteProformaLinkResponse struct {
core.CommonResponse
// Method result
Result bool `json:"result"`
}
func (c Invoices) Delete(ctx context.Context, params *DeleteProformaLinkParams) (*DeleteProformaLinkResponse, error) {
url := "/v1/invoice/delete"
resp := &DeleteProformaLinkResponse{}
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
}

167
ozon/invoices_test.go Normal file
View File

@@ -0,0 +1,167 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestCreateUpdateProformaLink(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *CreateUpdateProformaLinkParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&CreateUpdateProformaLinkParams{
PostingNumber: "33920146-0252-1",
URL: "https://cdn.ozone.ru/s3/ozon-disk-api/techdoc/seller-api/earsivfatura_1690960445.pdf",
HSCode: "2134322",
Date: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2023-08-01T12:08:44.342Z"),
Number: "424fdsf234",
Price: 234.34,
PriceCurrency: InvoiceCurrencyRUB,
},
`{
"result": true
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&CreateUpdateProformaLinkParams{},
`{
"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().CreateUpdate(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CreateUpdateProformaLinkResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetProformaLink(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetProformaLinkParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetProformaLinkParams{
PostingNumber: "posting number",
},
`{
"result": {
"file_url": "string"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetProformaLinkParams{},
`{
"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().Get(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProformaLinkResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestDeleteProformaLink(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *DeleteProformaLinkParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&DeleteProformaLinkParams{
PostingNumber: "posting number",
},
`{
"result": true
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&DeleteProformaLinkParams{},
`{
"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().Delete(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DeleteProformaLinkResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

@@ -0,0 +1,19 @@
package notifications
type MessageType string
const (
PingType MessageType = "TYPE_PING"
NewPostingType MessageType = "TYPE_NEW_POSTING"
PostingCancelledType MessageType = "TYPE_POSTING_CANCELLED"
StateChangedType MessageType = "TYPE_STATE_CHANGED"
CutoffDateChangedType MessageType = "TYPE_CUTOFF_DATE_CHANGED"
DeliveryDateChangedType MessageType = "TYPE_DELIVERY_DATE_CHANGED"
CreateOrUpdateType MessageType = "TYPE_CREATE_OR_UPDATE_ITEM"
PriceIndexChangedType MessageType = "TYPE_PRICE_INDEX_CHANGED"
StocksChangedType MessageType = "TYPE_STOCKS_CHANGED"
NewMessageType MessageType = "TYPE_NEW_MESSAGE"
UpdateMessageType MessageType = "TYPE_UPDATE_MESSAGE"
MessageReadType MessageType = "TYPE_MESSAGE_READ"
ChatClosedType MessageType = "TYPE_CHAT_CLOSED"
)

View File

@@ -0,0 +1,158 @@
package notifications
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)
type Handler func(req interface{}) error
type NotificationServer struct {
port int
handlers map[MessageType]Handler
}
func NewNotificationServer(port int) *NotificationServer {
return &NotificationServer{
port: port,
handlers: map[MessageType]Handler{},
}
}
func (ns *NotificationServer) Run() error {
mux := http.NewServeMux()
mux.HandleFunc("/", ns.handler)
server := http.Server{
Addr: fmt.Sprintf("0.0.0.0:%d", ns.port),
Handler: mux,
}
return server.ListenAndServe()
}
func (ns *NotificationServer) handler(rw http.ResponseWriter, httpReq *http.Request) {
mt := &Common{}
content, err := ioutil.ReadAll(httpReq.Body)
if err != nil {
log.Print(err)
ns.error(rw, http.StatusBadRequest, err)
return
}
if err := json.Unmarshal(content, mt); err != nil {
log.Print(err)
ns.error(rw, http.StatusBadRequest, err)
return
}
if mt.MessageType == PingType {
resp := pingResponse{
Version: "1.0",
Name: "Ozon Seller API",
Time: time.Now(),
}
respJson, _ := json.Marshal(resp)
rw.WriteHeader(http.StatusOK)
rw.Write(respJson)
return
}
req, err := ns.unmarshal(mt.MessageType, content)
if err != nil {
log.Print(err)
ns.error(rw, http.StatusInternalServerError, err)
return
}
h, ok := ns.handlers[mt.MessageType]
if !ok {
ns.result(rw, true)
log.Printf("handler for %s is not registered", mt.MessageType)
return
}
if err := h(req); err != nil {
log.Print(err)
ns.result(rw, true)
return
}
ns.result(rw, true)
}
func (ns *NotificationServer) Register(mt MessageType, handler func(req interface{}) error) {
ns.handlers[mt] = handler
}
func (ns *NotificationServer) unmarshal(messageType MessageType, content []byte) (interface{}, error) {
switch messageType {
case NewPostingType:
v := &NewPosting{}
err := json.Unmarshal(content, v)
return v, err
case PostingCancelledType:
v := &PostingCancelled{}
err := json.Unmarshal(content, v)
return v, err
case StateChangedType:
v := &StateChanged{}
err := json.Unmarshal(content, v)
return v, err
case CutoffDateChangedType:
v := &CutoffDateChanged{}
err := json.Unmarshal(content, v)
return v, err
case DeliveryDateChangedType:
v := &DeliveryDateChanged{}
err := json.Unmarshal(content, v)
return v, err
case CreateOrUpdateType:
v := &CreateOrUpdateItem{}
err := json.Unmarshal(content, v)
return v, err
case PriceIndexChangedType:
v := &PriceIndexChanged{}
err := json.Unmarshal(content, v)
return v, err
case StocksChangedType:
v := &StocksChanged{}
err := json.Unmarshal(content, v)
return v, err
case NewMessageType:
v := &NewMessage{}
err := json.Unmarshal(content, v)
return v, err
case UpdateMessageType:
v := &UpdateMessage{}
err := json.Unmarshal(content, v)
return v, err
case MessageReadType:
v := &MessageRead{}
err := json.Unmarshal(content, v)
return v, err
case ChatClosedType:
v := &ChatClosed{}
err := json.Unmarshal(content, v)
return v, err
default:
return nil, fmt.Errorf("unsupported type: %s", messageType)
}
}
func (ns *NotificationServer) error(rw http.ResponseWriter, statusCode int, err error) {
errResp := errorResponse{
Data: errorData{
Code: fmt.Sprintf("%d", statusCode),
Message: err.Error(),
},
}
errJson, _ := json.Marshal(errResp)
rw.WriteHeader(statusCode)
rw.Write(errJson)
}
func (ns *NotificationServer) result(rw http.ResponseWriter, res bool) {
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(fmt.Sprintf(`{"result": %t}`, res)))
}

View File

@@ -0,0 +1,687 @@
package notifications
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"reflect"
"strings"
"testing"
"time"
core "github.com/diphantxm/ozon-api-client"
)
type testData struct {
raw string
object interface{}
}
func pingTest(t *testing.T) testData {
return testData{
object: &pingRequest{
Common: Common{MessageType: PingType},
Time: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
},
raw: `{
"message_type": "TYPE_PING",
"time": "2019-08-24T14:15:22Z"
}`,
}
}
func newPostingTest(t *testing.T) testData {
return testData{
object: &NewPosting{
Common: Common{MessageType: NewPostingType},
PostingNumber: "24219509-0020-1",
Products: []Product{
{
SKU: 147451959,
Quantity: 2,
},
},
InProccessAt: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-01-26T06:56:36.294Z"),
WarehouseId: 18850503335000,
SellerId: 15,
},
raw: `{
"message_type": "TYPE_NEW_POSTING",
"posting_number": "24219509-0020-1",
"products": [
{
"sku": 147451959,
"quantity": 2
}
],
"in_process_at": "2021-01-26T06:56:36.294Z",
"warehouse_id": 18850503335000,
"seller_id": 15
}`,
}
}
func postingCancelledTest(t *testing.T) testData {
return testData{
object: &PostingCancelled{
Common: Common{MessageType: PostingCancelledType},
PostingNumber: "24219509-0020-1",
Products: []Product{
{
SKU: 147451959,
Quantity: 1,
},
},
OldState: "posting_transferred_to_courier_service",
NewState: "posting_canceled",
ChangedStateDate: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-01-26T06:56:36.294Z"),
Reason: Reason{Id: 1, Message: "message"},
WarehouseId: 1,
SellerId: 15,
},
raw: `{
"message_type": "TYPE_POSTING_CANCELLED",
"posting_number": "24219509-0020-1",
"products": [
{
"sku": 147451959,
"quantity": 1
}
],
"old_state": "posting_transferred_to_courier_service",
"new_state": "posting_canceled",
"changed_state_date": "2021-01-26T06:56:36.294Z",
"reason": {
"id": 1,
"message": "message"
},
"warehouse_id": 1,
"seller_id": 15
}`,
}
}
func cutoffDateChangedTest(t *testing.T) testData {
return testData{
object: &CutoffDateChanged{
Common: Common{MessageType: CutoffDateChangedType},
PostingNumber: "24219509-0020-2",
NewCutoffDate: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-24T07:00:00Z"),
OldCutoffDate: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-21T10:00:00Z"),
WarehouseId: 1,
SellerId: 15,
},
raw: `{
"message_type": "TYPE_CUTOFF_DATE_CHANGED",
"posting_number": "24219509-0020-2",
"new_cutoff_date": "2021-11-24T07:00:00Z",
"old_cutoff_date": "2021-11-21T10:00:00Z",
"warehouse_id": 1,
"seller_id": 15
}`,
}
}
func deliveryDateChangedTest(t *testing.T) testData {
return testData{
object: &DeliveryDateChanged{
Common: Common{MessageType: DeliveryDateChangedType},
PostingNumber: "24219509-0020-2",
NewDeliveryDateBegin: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-24T07:00:00Z"),
NewDeliveryDateEnd: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-24T16:00:00Z"),
OldDeliveryDateBegin: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-21T10:00:00Z"),
OldDeliveryDateEnd: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-21T19:00:00Z"),
WarehouseId: 1,
SellerId: 15,
},
raw: `{
"message_type": "TYPE_DELIVERY_DATE_CHANGED",
"posting_number": "24219509-0020-2",
"new_delivery_date_begin": "2021-11-24T07:00:00Z",
"new_delivery_date_end": "2021-11-24T16:00:00Z",
"old_delivery_date_begin": "2021-11-21T10:00:00Z",
"old_delivery_date_end": "2021-11-21T19:00:00Z",
"warehouse_id": 1,
"seller_id": 15
}`,
}
}
func priceIndexChangedTest(t *testing.T) testData {
return testData{
object: &PriceIndexChanged{
Common: Common{MessageType: PriceIndexChangedType},
UpdatedAt: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2022-06-21T05:52:46.648533678Z"),
SKU: 147451959,
ProductId: 1234,
PriceIndex: 5678,
SellerId: 15,
},
raw: `{
"seller_id": 15,
"message_type": "TYPE_PRICE_INDEX_CHANGED",
"updated_at":"2022-06-21T05:52:46.648533678Z",
"sku": 147451959,
"product_id": 1234,
"price_index": 5678
}`,
}
}
func stocksChangedTest(t *testing.T) testData {
return testData{
object: &StocksChanged{
Common: Common{MessageType: StocksChangedType},
Items: []Item{
{
UpdatedAt: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-09-01T14:15:22Z"),
SKU: 5678,
ProductId: 1234,
Stocks: []Stock{
{
WarehouseId: 10,
Present: 50,
Reserved: 5,
},
},
},
},
SellerId: 15,
},
raw: `{
"message_type": "TYPE_STOCKS_CHANGED",
"seller_id": 15,
"items": [
{
"product_id": 1234,
"sku": 5678,
"updated_at": "2021-09-01T14:15:22Z",
"stocks": [
{
"warehouse_id": 10,
"present": 50,
"reserved": 5
}
]
}
]
}`,
}
}
func newMessageTest(t *testing.T) testData {
return testData{
object: &NewMessage{
Common: Common{MessageType: NewMessageType},
ChatId: "b646d975-0c9c-4872-9f41-8b1e57181063",
ChatType: "Buyer_Seller",
MessageId: "3000000000817031942",
CreatedAt: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2022-07-18T20:58:04.528Z"),
User: User{Id: "115568", Type: "Customer"},
Data: []string{"Message text"},
SellerId: 7,
},
raw: `{
"message_type": "TYPE_NEW_MESSAGE",
"chat_id": "b646d975-0c9c-4872-9f41-8b1e57181063",
"chat_type": "Buyer_Seller",
"message_id": "3000000000817031942",
"created_at": "2022-07-18T20:58:04.528Z",
"user": {
"id": "115568",
"type": "Customer"
},
"data": [
"Message text"
],
"seller_id": 7
}`,
}
}
func updateMessageTest(t *testing.T) testData {
return testData{
object: &UpdateMessage{
NewMessage: NewMessage{
Common: Common{MessageType: UpdateMessageType},
ChatId: "b646d975-0c9c-4872-9f41-8b1e57181063",
ChatType: "Buyer_Seller",
MessageId: "3000000000817031942",
CreatedAt: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2022-07-18T20:58:04.528Z"),
User: User{
Id: "115568",
Type: "Сustomer",
},
Data: []string{"Message text"},
SellerId: 7,
},
UpdatedAt: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2022-07-18T20:59:04.528Z"),
},
raw: `{
"message_type": "TYPE_UPDATE_MESSAGE",
"chat_id": "b646d975-0c9c-4872-9f41-8b1e57181063",
"chat_type": "Buyer_Seller",
"message_id": "3000000000817031942",
"created_at": "2022-07-18T20:58:04.528Z",
"updated_at": "2022-07-18T20:59:04.528Z",
"user": {
"id": "115568",
"type": "Сustomer"
},
"data": [
"Message text"
],
"seller_id": 7
}`,
}
}
func createUpdateItemTest(t *testing.T) testData {
return testData{
object: &CreateOrUpdateItem{
Common: Common{MessageType: "TYPE_CREATE_OR_UPDATE_ITEM"},
OfferId: "1234",
ProductId: 5678,
IsError: false,
ChangedAt: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2022-09-01T14:15:22Z"),
SellerId: 15,
},
raw: `{
"message_type": "TYPE_CREATE_OR_UPDATE_ITEM",
"seller_id": 15,
"offer_id": "1234",
"product_id": 5678,
"is_error": false,
"changed_at": "2022-09-01T14:15:22Z"
}`,
}
}
func stateChangedTest(t *testing.T) testData {
return testData{
object: &StateChanged{
Common: Common{MessageType: "TYPE_STATE_CHANGED"},
PostingNumber: "24219509-0020-2",
NewState: "posting_delivered",
ChangedStateDate: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-02-02T15:07:46.765Z"),
WarehouseId: 1,
SellerId: 15,
},
raw: `{
"message_type": "TYPE_STATE_CHANGED",
"posting_number": "24219509-0020-2",
"new_state": "posting_delivered",
"changed_state_date": "2021-02-02T15:07:46.765Z",
"warehouse_id": 1,
"seller_id": 15
}`,
}
}
func messageReadTest(t *testing.T) testData {
return testData{
object: &MessageRead{
LastReadMessageId: "3000000000817031942",
NewMessage: NewMessage{
Common: Common{MessageType: "TYPE_MESSAGE_READ"},
ChatId: "b646d975-0c9c-4872-9f41-8b1e57181063",
ChatType: "Buyer_Seller",
MessageId: "3000000000817031942",
CreatedAt: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2022-07-18T20:58:04.528Z"),
User: User{
Id: "115568",
Type: "Сustomer",
},
SellerId: 7,
},
},
raw: `{
"message_type": "TYPE_MESSAGE_READ",
"chat_id": "b646d975-0c9c-4872-9f41-8b1e57181063",
"chat_type": "Buyer_Seller",
"message_id": "3000000000817031942",
"created_at": "2022-07-18T20:58:04.528Z",
"user": {
"id": "115568",
"type": "Сustomer"
},
"last_read_message_id": "3000000000817031942",
"seller_id": 7
}`,
}
}
func chatClosedTest(t *testing.T) testData {
return testData{
object: &ChatClosed{
Common: Common{MessageType: ChatClosedType},
ChatId: "b646d975-0c9c-4872-9f41-8b1e57181063",
ChatType: "Buyer_Seller",
User: User{Id: "115568", Type: "Customer"},
SellerId: 7,
},
raw: `{
"message_type": "TYPE_CHAT_CLOSED",
"chat_id": "b646d975-0c9c-4872-9f41-8b1e57181063",
"chat_type": "Buyer_Seller",
"user": {
"id": "115568",
"type": "Customer"
},
"seller_id": 7
}`,
}
}
func TestNotificationServer(t *testing.T) {
testCases := []struct {
request testData
response string
}{
{
pingTest(t),
`{
"version": "1.0",
"name": "Ozon Seller API"
}`,
},
{
newPostingTest(t),
`{
"result": true
}`,
},
{
postingCancelledTest(t),
`{
"result": true
}`,
},
{
stateChangedTest(t),
`{
"result": true
}`,
},
{
cutoffDateChangedTest(t),
`{
"result": true
}`,
},
{
deliveryDateChangedTest(t),
`{
"result": true
}`,
},
{
createUpdateItemTest(t),
`{
"result": true
}`,
},
{
priceIndexChangedTest(t),
`{
"result": true
}`,
},
{
stocksChangedTest(t),
`{
"result": true
}`,
},
{
newMessageTest(t),
`{
"result": true
}`,
},
{
updateMessageTest(t),
`{
"result": true
}`,
},
{
messageReadTest(t),
`{
"result": true
}`,
},
{
chatClosedTest(t),
`{
"result": true
}`,
},
}
port := getFreePort()
client := http.Client{}
server := NewNotificationServer(port)
server.Register(NewPostingType, comparatorWith(newPostingTest(t).object))
server.Register(PostingCancelledType, comparatorWith(postingCancelledTest(t).object))
server.Register(StateChangedType, comparatorWith(stateChangedTest(t).object))
server.Register(CutoffDateChangedType, comparatorWith(cutoffDateChangedTest(t).object))
server.Register(DeliveryDateChangedType, comparatorWith(deliveryDateChangedTest(t).object))
server.Register(CreateOrUpdateType, comparatorWith(createUpdateItemTest(t).object))
server.Register(PriceIndexChangedType, comparatorWith(priceIndexChangedTest(t).object))
server.Register(StocksChangedType, comparatorWith(stocksChangedTest(t).object))
server.Register(NewMessageType, comparatorWith(newMessageTest(t).object))
server.Register(UpdateMessageType, comparatorWith(updateMessageTest(t).object))
server.Register(MessageReadType, comparatorWith(messageReadTest(t).object))
server.Register(ChatClosedType, comparatorWith(chatClosedTest(t).object))
go func() {
if err := server.Run(); err != nil {
t.Fatalf("notification server is down: %s", err)
}
}()
// TODO: get rid of it
// Needed to make sure server is running
time.Sleep(3 * time.Second)
for _, testCase := range testCases {
httpResp, err := client.Post(fmt.Sprintf("http://0.0.0.0:%d/", port), "application/json", strings.NewReader(testCase.request.raw))
if err != nil {
t.Error(err)
continue
}
gotJson, err := ioutil.ReadAll(httpResp.Body)
if err != nil {
t.Error(err)
continue
}
expected := map[string]interface{}{}
got := map[string]interface{}{}
err = json.Unmarshal(gotJson, &got)
if err != nil {
t.Error(err)
continue
}
err = json.Unmarshal([]byte(testCase.response), &expected)
if err != nil {
t.Error(err)
continue
}
if err := compare(expected, got); err != nil {
t.Error(err)
continue
}
}
}
func TestNotificationServerErrors(t *testing.T) {
testCases := []struct {
request testData
response string
}{
{
testData{
raw: `{
"message_type": "string"
}`,
},
`
{
"error": {
"code": "500",
"message": "unsupported type: string",
"details": ""
}
}`,
},
{
testData{
raw: `invalid json`,
},
`{
"error": {
"code": "400",
"message": "invalid character 'i' looking for beginning of value",
"details": ""
}
}`,
},
{
testData{
raw: `{
"message_type": "TYPE_NEW_POSTING",
"field": [[
}`,
},
`{
"error": {
"code": "400",
"message": "invalid character '}' looking for beginning of value",
"details": ""
}
}`,
},
{
testData{
raw: `{
"message_type": "TYPE_NEW_POSTING"
}`,
},
`{
"result": true
}`,
},
{
testData{
raw: `{
"message_type": "TYPE_PING",
"time": "2019-08-24T14:15:22Z",
}`,
},
`{
"error": {
"code": "400",
"message": "invalid character '}' looking for beginning of object key string",
"details": ""
}
}`,
},
{
testData{
raw: `{
"message_type": "TYPE_CHAT_CLOSED"
}`,
},
`{
"result": true
}`,
},
}
port := getFreePort()
client := http.Client{}
server := NewNotificationServer(port)
server.Register(NewPostingType, func(req interface{}) error {
return fmt.Errorf("just error")
})
go func() {
if err := server.Run(); err != nil {
t.Fatalf("notification server is down: %s", err)
}
}()
// TODO: get rid of it
// Needed to make sure server is running
time.Sleep(3 * time.Second)
for _, testCase := range testCases {
httpResp, err := client.Post(fmt.Sprintf("http://0.0.0.0:%d/", port), "application/json", strings.NewReader(testCase.request.raw))
if err != nil {
t.Error(err)
continue
continue
}
gotJson, err := ioutil.ReadAll(httpResp.Body)
if err != nil {
t.Error(err)
continue
continue
}
expected := map[string]interface{}{}
got := map[string]interface{}{}
err = json.Unmarshal(gotJson, &got)
if err != nil {
t.Error(err)
continue
continue
}
err = json.Unmarshal([]byte(testCase.response), &expected)
if err != nil {
t.Error(err)
continue
continue
}
if err := compare(expected, got); err != nil {
t.Error(err)
continue
continue
}
}
}
func compare(expected map[string]interface{}, got map[string]interface{}) error {
for k, v := range expected {
if gotValue, ok := got[k]; !ok {
return fmt.Errorf("key %s is expected to present", k)
} else if !reflect.DeepEqual(gotValue, v) {
return fmt.Errorf("key %s is not equal, got: %v, want: %v", k, gotValue, v)
}
}
return nil
}
func getFreePort() int {
listener, _ := net.Listen("tcp", ":0")
defer listener.Close()
return listener.Addr().(*net.TCPAddr).Port
}
func comparatorWith(v1 interface{}) func(v2 interface{}) error {
return func(v2 interface{}) error {
if !reflect.DeepEqual(v1, v2) {
return fmt.Errorf("objects are not equal:\n got: %#v,\n want: %#v", v2, v1)
}
return nil
}
}

321
ozon/notifications/types.go Normal file
View File

@@ -0,0 +1,321 @@
package notifications
import "time"
// Checking if the service is ready at initial connection and periodically after it
type pingRequest struct {
Common
// Date and time when the notification was sent in UTC format
Time time.Time `json:"time"`
}
type pingResponse struct {
// Application version
Version string `json:"version"`
// Application name
Name string `json:"name"`
// Date and time when notification processing started in UTC format
Time time.Time `json:"time"`
}
type Common struct {
MessageType MessageType `json:"message_type"`
}
// New shipment
type NewPosting struct {
Common
// Shipment number
PostingNumber string `json:"posting_number"`
// Products information
Products []Product `json:"products"`
// Date and time when the shipment processing started in the UTC format
InProccessAt time.Time `json:"in_process_at"`
// Warehouse identifier where the products for this shipment are stored
WarehouseId int64 `json:"warehouse_id"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
type Product struct {
// Product SKU
SKU int64 `json:"sku"`
// Product quantity
Quantity int64 `json:"quantity"`
}
// Shipment cancellation
type PostingCancelled struct {
Common
// Shipment number
PostingNumber string `json:"posting_number"`
// Products information
Products []Product `json:"products"`
// Previous shipment status
OldState string `json:"old_state"`
// New shipment status: posting_canceled—canceled
NewState string `json:"new_state"`
// Date and time when the shipment status was changed in UTC format
ChangedStateDate time.Time `json:"changed_state_date"`
// Information about cancellation reason
Reason Reason `json:"reason"`
// Warehouse identifier where the products for this shipment are stored
WarehouseId int64 `json:"warehouse_id"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
type Reason struct {
// Cancellation reason identifier
Id int64 `json:"id"`
// Cancellation reason
Message string `json:"message"`
}
// Shipment status change
type StateChanged struct {
Common
// Shipment number
PostingNumber string `json:"posting_number"`
// New shipment status
NewState string `json:"new_state"`
// Date and time when the shipment status was changed in UTC format
ChangedStateDate time.Time `json:"changed_state_date"`
// Warehouse identifier where the products for this shipment are stored
WarehouseId int64 `json:"warehouse_id"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
// Shipment shipping date change
type CutoffDateChanged struct {
Common
// Shipment number
PostingNumber string `json:"posting_number"`
// New shipping date and time in UTC format
NewCutoffDate time.Time `json:"new_cutoff_date"`
// Previous shipping date and time in UTC format
OldCutoffDate time.Time `json:"old_cutoff_date"`
// Warehouse identifier where the products for this shipment are stored
WarehouseId int64 `json:"warehouse_id"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
// Shipment delivery date change
type DeliveryDateChanged struct {
Common
// Shipment number
PostingNumber string `json:"posting_number"`
// New delivery start date and time in UTC format
NewDeliveryDateBegin time.Time `json:"new_delivery_date_begin"`
// New delivery end date and time in UTC format
NewDeliveryDateEnd time.Time `json:"new_delivery_date_end"`
// Previous delivery start date and time in UTC format
OldDeliveryDateBegin time.Time `json:"old_delivery_date_begin"`
// Previous delivery end date and time in UTC format
OldDeliveryDateEnd time.Time `json:"old_delivery_date_end"`
// Warehouse identifier where the products for this shipment are stored
WarehouseId int64 `json:"warehouse_id"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
// Product creation and update or processing error
type CreateOrUpdateItem struct {
Common
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product identifier
ProductId int64 `json:"product_id"`
// An indication that errors occurred during the product creation or update
IsError bool `json:"is_error"`
// Update date and time
ChangedAt time.Time `json:"changed_at"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
// Product price index change
type PriceIndexChanged struct {
Common
// Date and time of price index change
UpdatedAt time.Time `json:"updated_at"`
// Product SKU
SKU int64 `json:"sku"`
// Product identifier
ProductId int64 `json:"product_id"`
// Price index
PriceIndex int64 `json:"price_index"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
// Stock change at the seller's warehouse
type StocksChanged struct {
Common
// Array with products data
Items []Item `json:"items"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
type Item struct {
// Update date and time
UpdatedAt time.Time `json:"updated_at"`
// Product SKU when working under the FBS or rFBS schemes
SKU int64 `json:"sku"`
// Product identifier
ProductId int64 `json:"product_id"`
// Array with product stocks data
Stocks []Stock `json:"stocks"`
}
type Stock struct {
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
// Total product stocks at the warehouse
Present int64 `json:"present"`
// Number of reserved products at the warehouse
Reserved int64 `json:"reserved"`
}
// New message in chat
type NewMessage struct {
Common
// Chat identifier
ChatId string `json:"chat_id"`
// Chat type
ChatType string `json:"chat_type"`
// Message identifier
MessageId string `json:"message_id"`
// Message creation date
CreatedAt time.Time `json:"created_at"`
// Information about message sender
User User `json:"user"`
// Array with message content in Markdown format
Data []string `json:"data"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
type User struct {
// Sender identifier
Id string `json:"id"`
// Sender type
Type string `json:"type"`
}
// Message in chat has changed
type UpdateMessage struct {
NewMessage
// Message update date
UpdatedAt time.Time `json:"updated_at"`
}
// Customer or support read your message
type MessageRead struct {
NewMessage
// Last read message identifier
LastReadMessageId string `json:"last_read_message_id"`
}
// Chat is closed
type ChatClosed struct {
Common
// Chat identifier
ChatId string `json:"chat_id"`
// Chat type
ChatType string `json:"chat_type"`
// Information about the user who closed the chat
User User `json:"user"`
// Seller identifier
SellerId int64 `json:"seller_id"`
}
type Response struct {
// Notification is received
Result bool `json:"result"`
}
type errorResponse struct {
// Information about the error
Data errorData `json:"error"`
}
type errorData struct {
// Error code
Code string `json:"code"`
// Detailed error description
Message string `json:"message"`
// Additional information
Details string `json:"details"`
}

View File

@@ -10,19 +10,38 @@ const (
DefaultAPIBaseUrl = "https://api-seller.ozon.ru"
)
type ClientOptions struct {
client core.HttpClient
baseUri string
apiKey string
clientId string
}
type Client struct {
client *core.Client
analytics *Analytics
fbo *FBO
fbs *FBS
finance *Finance
products *Products
promotions *Promotions
rating *Rating
warehouses *Warehouses
returns *Returns
reports *Reports
analytics *Analytics
fbo *FBO
fbs *FBS
finance *Finance
products *Products
promotions *Promotions
rating *Rating
warehouses *Warehouses
returns *Returns
reports *Reports
cancellations *Cancellations
categories *Categories
polygons *Polygons
invoices *Invoices
brands *Brands
chats *Chats
certificates *Certificates
strategies *Strategies
barcodes *Barcodes
passes *Passes
}
func (c Client) Analytics() *Analytics {
@@ -65,24 +84,110 @@ func (c Client) Reports() *Reports {
return c.reports
}
func NewClient(clientId, apiKey string) *Client {
coreClient := core.NewClient(DefaultAPIBaseUrl, map[string]string{
"Client-Id": clientId,
"Api-Key": apiKey,
func (c Client) Cancellations() *Cancellations {
return c.cancellations
}
func (c Client) Categories() *Categories {
return c.categories
}
func (c Client) Polygons() *Polygons {
return c.polygons
}
func (c Client) Invoices() *Invoices {
return c.invoices
}
func (c Client) Brands() *Brands {
return c.brands
}
func (c Client) Chats() *Chats {
return c.chats
}
func (c Client) Certificates() *Certificates {
return c.certificates
}
func (c Client) Strategies() *Strategies {
return c.strategies
}
func (c Client) Barcodes() *Barcodes {
return c.barcodes
}
func (c Client) Passes() *Passes {
return c.passes
}
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{
client: coreClient,
analytics: &Analytics{client: coreClient},
fbo: &FBO{client: coreClient},
fbs: &FBS{client: coreClient},
finance: &Finance{client: coreClient},
products: &Products{client: coreClient},
promotions: &Promotions{client: coreClient},
rating: &Rating{client: coreClient},
warehouses: &Warehouses{client: coreClient},
returns: &Returns{client: coreClient},
reports: &Reports{client: coreClient},
client: coreClient,
analytics: &Analytics{client: coreClient},
fbo: &FBO{client: coreClient},
fbs: &FBS{client: coreClient},
finance: &Finance{client: coreClient},
products: &Products{client: coreClient},
promotions: &Promotions{client: coreClient},
rating: &Rating{client: coreClient},
warehouses: &Warehouses{client: coreClient},
returns: &Returns{client: coreClient},
reports: &Reports{client: coreClient},
cancellations: &Cancellations{client: coreClient},
categories: &Categories{client: coreClient},
polygons: &Polygons{client: coreClient},
invoices: &Invoices{client: coreClient},
brands: &Brands{client: coreClient},
chats: &Chats{client: coreClient},
certificates: &Certificates{client: coreClient},
strategies: &Strategies{client: coreClient},
barcodes: &Barcodes{client: coreClient},
passes: &Passes{client: coreClient},
}
}
@@ -90,16 +195,26 @@ func NewMockClient(handler http.HandlerFunc) *Client {
coreClient := core.NewMockClient(handler)
return &Client{
client: coreClient,
analytics: &Analytics{client: coreClient},
fbo: &FBO{client: coreClient},
fbs: &FBS{client: coreClient},
finance: &Finance{client: coreClient},
products: &Products{client: coreClient},
promotions: &Promotions{client: coreClient},
rating: &Rating{client: coreClient},
warehouses: &Warehouses{client: coreClient},
returns: &Returns{client: coreClient},
reports: &Reports{client: coreClient},
client: coreClient,
analytics: &Analytics{client: coreClient},
fbo: &FBO{client: coreClient},
fbs: &FBS{client: coreClient},
finance: &Finance{client: coreClient},
products: &Products{client: coreClient},
promotions: &Promotions{client: coreClient},
rating: &Rating{client: coreClient},
warehouses: &Warehouses{client: coreClient},
returns: &Returns{client: coreClient},
reports: &Reports{client: coreClient},
cancellations: &Cancellations{client: coreClient},
categories: &Categories{client: coreClient},
polygons: &Polygons{client: coreClient},
invoices: &Invoices{client: coreClient},
brands: &Brands{client: coreClient},
chats: &Chats{client: coreClient},
certificates: &Certificates{client: coreClient},
strategies: &Strategies{client: coreClient},
barcodes: &Barcodes{client: coreClient},
passes: &Passes{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)
}
}
}

87
ozon/polygons.go Normal file
View File

@@ -0,0 +1,87 @@
package ozon
import (
"context"
"net/http"
core "github.com/diphantxm/ozon-api-client"
)
type Polygons struct {
client *core.Client
}
type CreateDeliveryPolygonParams struct {
// Delivery polygon coordinates in [[[lat long]]] format
Coordinates string `json:"coordinates"`
}
type CreateDeliveryPolygonResponse struct {
core.CommonResponse
// Polygon identifier
PolygonId int64 `json:"polygon_id"`
}
// You can link a polygon to the delivery method.
//
// Create a polygon getting its coordinates on https://geojson.io: mark at least 3 points on the map and connect them
func (c Polygons) CreateDelivery(ctx context.Context, params *CreateDeliveryPolygonParams) (*CreateDeliveryPolygonResponse, error) {
url := "/v1/polygon/create"
resp := &CreateDeliveryPolygonResponse{}
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 LinkDeliveryMethodToPolygonParams struct {
// Delivery method identifier
DeliveryMethodId int32 `json:"delivery_method_id"`
// Polygons list
Polygons []LinkDeliveryMethodToPolygonPolygon `json:"polygons"`
// Warehouse location
WarehouseLocation LinkDeliveryMethodToPolygonWarehouse `json:"warehouse_location"`
}
type LinkDeliveryMethodToPolygonPolygon struct {
// Polygon identifier
PolygonId int64 `json:"polygon_id"`
// Delivery time within polygon in minutes
Time int64 `json:"time"`
}
type LinkDeliveryMethodToPolygonWarehouse struct {
// Warehouse location latitude
Latitude string `json:"lat"`
// Warehouse location longitude
Longitude string `json:"log"`
}
type LinkDeliveryMethodToPolygonResponse struct {
core.CommonResponse
}
// Link delivery method to a delivery polygon
func (c Polygons) Link(ctx context.Context, params *LinkDeliveryMethodToPolygonParams) (*LinkDeliveryMethodToPolygonResponse, error) {
url := "/v1/polygon/bind"
resp := &LinkDeliveryMethodToPolygonResponse{}
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
}

117
ozon/polygons_test.go Normal file
View File

@@ -0,0 +1,117 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestCreateDeliveryPolygon(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *CreateDeliveryPolygonParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&CreateDeliveryPolygonParams{
Coordinates: "[[[30.149574279785153,59.86550435303646],[30.21205902099609,59.846884387977326],[30.255661010742184,59.86240174913176],[30.149574279785153,59.86550435303646]]]",
},
`{
"polygon_id": 1323
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&CreateDeliveryPolygonParams{},
`{
"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.Polygons().CreateDelivery(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CreateDeliveryPolygonResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestLinkDeliveryMethodToPolygon(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *LinkDeliveryMethodToPolygonParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&LinkDeliveryMethodToPolygonParams{
DeliveryMethodId: 0,
Polygons: []LinkDeliveryMethodToPolygonPolygon{
{
PolygonId: 1323,
Time: 30,
},
},
WarehouseLocation: LinkDeliveryMethodToPolygonWarehouse{
Latitude: "58.52391272075821",
Longitude: "31.236791610717773",
},
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&LinkDeliveryMethodToPolygonParams{},
`{
"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.Polygons().Link(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &LinkDeliveryMethodToPolygonResponse{})
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

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"fmt"
"net/http"
"testing"
@@ -70,11 +71,15 @@ func TestGetStocksInfo(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetStocksInfo(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetStocksInfo(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetStocksInfoResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -122,7 +127,7 @@ func TestGetProductDetails(t *testing.T) {
"7533900005"
],
"buybox_price": "",
"category_id": 17038062,
"type_id": 0,
"created_at": "2021-10-21T15:48:03.529178Z",
"images": [
"https://cdn1.ozone.ru/s3/multimedia-5/6088931525.jpg",
@@ -136,12 +141,12 @@ func TestGetProductDetails(t *testing.T) {
"reserved": 0
},
"currency_code": "RUB",
"description_category_id": 12,
"marketing_price": "",
"min_price": "",
"old_price": "",
"premium_price": "",
"price": "590.0000",
"recommended_price": "",
"sources": [
{
"is_enabled": true,
@@ -159,7 +164,6 @@ func TestGetProductDetails(t *testing.T) {
"present": 0,
"reserved": 0
},
"errors": [],
"updated_at": "2023-02-09T06:46:44.152Z",
"vat": "0.0",
"visible": false,
@@ -168,7 +172,24 @@ func TestGetProductDetails(t *testing.T) {
"has_stock": false,
"active_product": false
},
"price_index": "0.00",
"price_indexes": {
"external_index_data": {
"minimal_price": "string",
"minimal_price_currency": "string",
"price_index_value": 0
},
"ozon_index_data": {
"minimal_price": "string",
"minimal_price_currency": "string",
"price_index_value": 0
},
"price_index": "WITHOUT_INDEX",
"self_marketplaces_index_data": {
"minimal_price": "string",
"minimal_price_currency": "string",
"price_index_value": 0
}
},
"commissions": [],
"volume_weight": 0.1,
"is_prepayment": false,
@@ -233,7 +254,7 @@ func TestGetProductDetails(t *testing.T) {
"state_updated_at": "2021-10-21T15:48:03.927309Z"
}
}
}`,
}`,
},
// Test No Client-Id or Api-Key
{
@@ -250,11 +271,15 @@ func TestGetProductDetails(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetProductDetails(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetProductDetails(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductDetailsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -266,7 +291,7 @@ func TestGetProductDetails(t *testing.T) {
if resp.Result.OfferId == "" {
t.Errorf("Offer id cannot be empty")
}
if resp.Result.CategoryId == 0 {
if resp.Result.DescriptionCategoryId == 0 {
t.Errorf("Category id cannot be 0")
}
if resp.Result.CurrencyCode == "" {
@@ -324,11 +349,15 @@ func TestUpdateStocks(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().UpdateStocks(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().UpdateStocks(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UpdateStocksResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -363,12 +392,12 @@ func TestStocksInSellersWarehouse(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&StocksInSellersWarehouseParams{
FBSSKU: []string{"123"},
SKU: []string{"123"},
},
`{
"result": [
{
"fbs_sku": 12,
"sku": 12,
"present": 34,
"product_id": 548761,
"reserved": 5,
@@ -393,21 +422,25 @@ func TestStocksInSellersWarehouse(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().StocksInSellersWarehouse(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().StocksInSellersWarehouse(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &StocksInSellersWarehouseResponse{})
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) != len(test.params.FBSSKU) {
if len(resp.Result) != len(test.params.SKU) {
t.Errorf("Length of skus in request and response must be equal")
}
if len(resp.Result) > 0 {
if fmt.Sprint(resp.Result[0].FBSSKU) == test.params.FBSSKU[0] {
if fmt.Sprint(resp.Result[0].SKU) == test.params.SKU[0] {
t.Errorf("fbs sku in request and response are not equal")
}
}
@@ -431,12 +464,13 @@ func TestUpdatePrices(t *testing.T) {
&UpdatePricesParams{
Prices: []UpdatePricesPrice{
{
AutoActionEnabled: "UNKNOWN",
CurrencyCode: "RUB",
MinPrice: "800",
OldPrice: "0",
Price: "1448",
ProductId: 1386,
AutoActionEnabled: "UNKNOWN",
CurrencyCode: "RUB",
MinPrice: "800",
OldPrice: "0",
Price: "1448",
ProductId: 1386,
PriceStrategyEnabled: PriceStrategyUnknown,
},
},
},
@@ -466,11 +500,15 @@ func TestUpdatePrices(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().UpdatePrices(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().UpdatePrices(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UpdatePricesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -538,11 +576,15 @@ func TestUpdateQuantityStockProducts(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().UpdateQuantityStockProducts(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().UpdateQuantityStockProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UpdateQuantityStockProductsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -633,21 +675,21 @@ func TestCreateOrUpdateProduct(t *testing.T) {
},
},
},
Barcode: "112772873170",
CategoryId: 17033876,
CurrencyCode: "RUB",
Depth: 10,
DimensionUnit: "mm",
Height: 250,
Name: "Комплект защитных плёнок для X3 NFC. Темный хлопок",
OfferId: "143210608",
OldPrice: "1100",
PremiumPrice: "900",
Price: "1000",
VAT: "0.1",
Weight: 100,
WeightUnit: "g",
Width: 150,
Barcode: "112772873170",
DescriptionCategoryId: 17033876,
CurrencyCode: "RUB",
Depth: 10,
DimensionUnit: "mm",
Height: 250,
Name: "Комплект защитных плёнок для X3 NFC. Темный хлопок",
OfferId: "143210608",
OldPrice: "1100",
PremiumPrice: "900",
Price: "1000",
VAT: "0.1",
Weight: 100,
WeightUnit: "g",
Width: 150,
},
},
},
@@ -672,11 +714,15 @@ func TestCreateOrUpdateProduct(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().CreateOrUpdateProduct(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().CreateOrUpdateProduct(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CreateOrUpdateProductResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -739,11 +785,15 @@ func TestGetListOfProducts(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetListOfProducts(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetListOfProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetListOfProductsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -985,11 +1035,15 @@ func TestGetProductsRatingBySKU(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetProductsRatingBySKU(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetProductsRatingBySKU(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductsRatingBySKUResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1052,11 +1106,15 @@ func TestGetProductImportStatus(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetProductImportStatus(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetProductImportStatus(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductImportStatusResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1123,11 +1181,15 @@ func TestCreateProductByOzonID(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().CreateProductByOzonID(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().CreateProductByOzonID(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CreateProductByOzonIDResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1191,11 +1253,15 @@ func TestUpdateProductImages(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().UpdateProductImages(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().UpdateProductImages(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ProductInfoResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1259,11 +1325,15 @@ func TestCheckImageUploadingStatus(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().CheckImageUploadingStatus(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().CheckImageUploadingStatus(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ProductInfoResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1307,7 +1377,8 @@ func TestListProductsByIDs(t *testing.T) {
"7533900005"
],
"buybox_price": "",
"category_id": 93726157,
"description_category_id": 93726157,
"type_id": 0,
"created_at": "2021-06-03T03:40:05.871465Z",
"images": [],
"has_discounted_item": true,
@@ -1323,7 +1394,6 @@ func TestListProductsByIDs(t *testing.T) {
"old_price": "1000.0000",
"premium_price": "590.0000",
"price": "690.0000",
"recommended_price": "",
"sources": [
{
"is_enabled": true,
@@ -1336,13 +1406,11 @@ func TestListProductsByIDs(t *testing.T) {
"source": "fbs"
}
],
"state": "",
"stocks": {
"coming": 0,
"present": 13,
"reserved": 0
},
"errors": [],
"updated_at": "2023-02-09T06:46:44.152Z",
"vat": "0.0",
"visible": true,
@@ -1352,7 +1420,24 @@ func TestListProductsByIDs(t *testing.T) {
"active_product": false,
"reasons": {}
},
"price_index": "0.00",
"price_indexes": {
"external_index_data": {
"minimal_price": "string",
"minimal_price_currency": "string",
"price_index_value": 0
},
"ozon_index_data": {
"minimal_price": "string",
"minimal_price_currency": "string",
"price_index_value": 0
},
"price_index": "WITHOUT_INDEX",
"self_marketplaces_index_data": {
"minimal_price": "string",
"minimal_price_currency": "string",
"price_index_value": 0
}
},
"images360": [],
"is_kgt": false,
"color_image": "",
@@ -1378,7 +1463,6 @@ func TestListProductsByIDs(t *testing.T) {
"offer_id": "23",
"barcode": "",
"buybox_price": "",
"category_id": 90635895,
"created_at": "2021-05-26T20:26:07.565586Z",
"images": [],
"marketing_price": "",
@@ -1386,7 +1470,6 @@ func TestListProductsByIDs(t *testing.T) {
"old_price": "12200.0000",
"premium_price": "5490.0000",
"price": "6100.0000",
"recommended_price": "",
"sources": [
{
"is_enabled": true,
@@ -1399,13 +1482,11 @@ func TestListProductsByIDs(t *testing.T) {
"source": "fbs"
}
],
"state": "",
"stocks": {
"coming": 0,
"present": 19,
"reserved": 0
},
"errors": [],
"updated_at": "2023-02-09T06:46:44.152Z",
"vat": "0.0",
"visible": true,
@@ -1454,11 +1535,15 @@ func TestListProductsByIDs(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().ListProductsByIDs(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().ListProductsByIDs(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListProductsByIDsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1503,7 +1588,7 @@ func TestGetDescriptionOfProduct(t *testing.T) {
{
"id": 213761435,
"barcode": "",
"category_id": 17038062,
"description_category_id": 17038062,
"name": "Пленка защитная для Xiaomi Redmi Note 10 Pro 5G",
"offer_id": "21470",
"height": 10,
@@ -1665,8 +1750,7 @@ func TestGetDescriptionOfProduct(t *testing.T) {
}
],
"complex_attributes": [],
"color_image": "",
"last_id": ""
"color_image": ""
}
],
"total": 1,
@@ -1688,11 +1772,15 @@ func TestGetDescriptionOfProduct(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetDescriptionOfProduct(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetDescriptionOfProduct(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetDescriptionOfProductResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1746,11 +1834,15 @@ func TestGetProductDescription(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetProductDescription(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetProductDescription(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductDescriptionResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1809,11 +1901,15 @@ func TestGetProductRangeLimit(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetProductRangeLimit()
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetProductRangeLimit(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductRangeLimitResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1865,11 +1961,15 @@ func TestChangeProductIDs(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().ChangeProductIDs(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().ChangeProductIDs(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ChangeProductIDsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1911,11 +2011,15 @@ func TestArchiveProduct(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().ArchiveProduct(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().ArchiveProduct(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ArchiveProductResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -1957,11 +2061,15 @@ func TestUnarchiveProduct(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().UnarchiveProduct(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().UnarchiveProduct(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ArchiveProductResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -2013,11 +2121,15 @@ func TestRemoveProductWithoutSKU(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().RemoveProductWithoutSKU(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().RemoveProductWithoutSKU(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &RemoveProductWithoutSKUResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -2090,11 +2202,15 @@ func TestListGeoRestrictions(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().ListGeoRestrictions(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().ListGeoRestrictions(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListGeoRestrictionsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -2139,11 +2255,15 @@ func TestUploadActivationCodes(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().UploadActivationCodes(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().UploadActivationCodes(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UploadActivationCodesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -2187,11 +2307,15 @@ func TestStatusOfUploadingActivationCodes(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().StatusOfUploadingActivationCodes(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().StatusOfUploadingActivationCodes(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &StatusOfUploadingActivationCodesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -2222,6 +2346,7 @@ func TestGetProductPriceInfo(t *testing.T) {
"result": {
"items": [
{
"acquiring": 0,
"product_id": 243686911,
"offer_id": "356792",
"price": {
@@ -2229,7 +2354,6 @@ func TestGetProductPriceInfo(t *testing.T) {
"price": "499.0000",
"old_price": "579.0000",
"premium_price": "",
"recommended_price": "",
"retail_price": "",
"vat": "0.200000",
"min_ozon_price": "",
@@ -2237,9 +2361,28 @@ func TestGetProductPriceInfo(t *testing.T) {
"marketing_seller_price": "",
"auto_action_enabled": true
},
"price_index": "0.00",
"price_indexes": {
"external_index_data": {
"minimal_price": "string",
"minimal_price_currency": "string",
"price_index_value": 0
},
"ozon_index_data": {
"minimal_price": "string",
"minimal_price_currency": "string",
"price_index_value": 0
},
"price_index": "WITHOUT_INDEX",
"self_marketplaces_index_data": {
"minimal_price": "string",
"minimal_price_currency": "string",
"price_index_value": 0
}
},
"commissions": {
"sales_percent": 15,
"sales_percent_fbo": 15,
"sales_percent_fbs": 0,
"fbo_fulfillment_amount": 0,
"fbo_direct_flow_trans_min_amount": 31,
"fbo_direct_flow_trans_max_amount": 46.5,
@@ -2248,7 +2391,7 @@ func TestGetProductPriceInfo(t *testing.T) {
"fbo_return_flow_trans_min_amount": 21.7,
"fbo_return_flow_trans_max_amount": 21.7,
"fbs_first_mile_min_amount": 0,
"fbs_first_mile_max_amount": 0,
"fbs_first_mile_max_amount": 25,
"fbs_direct_flow_trans_min_amount": 41,
"fbs_direct_flow_trans_max_amount": 61.5,
"fbs_deliv_to_customer_amount": 60,
@@ -2280,11 +2423,15 @@ func TestGetProductPriceInfo(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetProductPriceInfo(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetProductPriceInfo(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductPriceInfoResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -2342,9 +2489,11 @@ func TestGetMarkdownInfo(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().GetMarkdownInfo(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetMarkdownInfo(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
if resp.StatusCode != test.statusCode {
@@ -2352,6 +2501,8 @@ func TestGetMarkdownInfo(t *testing.T) {
}
if resp.StatusCode == http.StatusOK {
compareJsonResponse(t, test.response, &GetMarkdownInfoResponse{})
if len(resp.Items) > 0 {
if fmt.Sprint(resp.Items[0].DiscountedSKU) != test.params.DiscountedSKUs[0] {
t.Errorf("SKUs in reqest and resonse are not equal")
@@ -2361,7 +2512,7 @@ func TestGetMarkdownInfo(t *testing.T) {
}
}
func TestSetDiscountOnMarkdownProductParams(t *testing.T) {
func TestSetDiscountOnMarkdownProduct(t *testing.T) {
t.Parallel()
tests := []struct {
@@ -2397,13 +2548,219 @@ func TestSetDiscountOnMarkdownProductParams(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Products().SetDiscountOnMarkdownProduct(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().SetDiscountOnMarkdownProduct(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
compareJsonResponse(t, test.response, &SetDiscountOnMarkdownProductResponse{})
}
}
func TestNumberOfSubsToProductAvailability(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *NumberOfSubsToProductAvailabilityParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&NumberOfSubsToProductAvailabilityParams{
SKUS: []int64{1234},
},
`{
"result": [
{
"count": 2,
"sku": 1234
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&NumberOfSubsToProductAvailabilityParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().NumberOfSubsToProductAvailability(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
compareJsonResponse(t, test.response, &NumberOfSubsToProductAvailabilityResponse{})
if resp.StatusCode == http.StatusOK {
if len(resp.Result) != len(test.params.SKUS) {
t.Errorf("Length of SKUS in request and response are not equal")
}
if len(resp.Result) > 0 {
if resp.Result[0].SKU != test.params.SKUS[0] {
t.Errorf("SKU in request and response are not equal")
}
}
}
}
}
func TestUpdateCharacteristics(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *UpdateCharacteristicsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&UpdateCharacteristicsParams{
Items: []UpdateCharacteristicsItem{
{
Attributes: []UpdateCharacteristicsItemAttribute{
{
ComplexId: 0,
Id: 0,
Values: []UpdateCharacteristicsItemValue{
{
DictionaryValueId: 0,
Value: "string",
},
},
},
},
OfferId: "string",
},
},
},
`{
"task_id": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&UpdateCharacteristicsParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().UpdateCharacteristics(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
compareJsonResponse(t, test.response, &UpdateCharacteristicsResponse{})
}
}
func TestGetRelatedSKUs(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetRelatedSKUsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetRelatedSKUsParams{
SKUs: []string{"321", "322"},
},
`{
"items": [
{
"availability": "HIDDEN",
"deleted_at": "2019-08-24T14:15:22Z",
"delivery_schema": "fbs",
"product_id": 123,
"sku": 321
}
],
"errors": [
{
"code": "test_code",
"sku": 322,
"message": "test_message"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetRelatedSKUsParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Products().GetRelatedSKUs(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
compareJsonResponse(t, test.response, &GetRelatedSKUsResponse{})
if len(resp.Errors)+len(resp.Items) != len(test.params.SKUs) {
t.Errorf("expected equal length of skus in request and response")
}
}
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"time"
@@ -15,68 +16,70 @@ type GetAvailablePromotionsResponse struct {
core.CommonResponse
// Method result
Result []struct {
// Promotion identifier
Id float64 `json:"id"`
Result []GetAvailablePromotionsResult `json:"result"`
}
// Promotion name
Title string `json:"title"`
type GetAvailablePromotionsResult struct {
// Promotion identifier
Id float64 `json:"id"`
// Promotion type
ActionType string `json:"action_type"`
// Promotion name
Title string `json:"title"`
// Promotion description
Description string `json:"description"`
// Promotion type
ActionType string `json:"action_type"`
// Promotion start date
DateStart string `json:"date_start"`
// Promotion description
Description string `json:"description"`
// Promotion end date
DateEnd string `json:"date_end"`
// Promotion start date
DateStart string `json:"date_start"`
// Promotion freeze date.
//
// If the field is filled, the seller can't increase prices, change the list of products, or decrease the number of product units in the promotion.
//
// The seller can lower prices and increase the product units number in the promotion
FreezeDate string `json:"freeze_date"`
// Promotion end date
DateEnd string `json:"date_end"`
// Number of products that can participate in the promotion
PotentialProductsCount float64 `json:"potential_products_count"`
// Promotion freeze date.
//
// If the field is filled, the seller can't increase prices, change the list of products, or decrease the number of product units in the promotion.
//
// The seller can lower prices and increase the product units number in the promotion
FreezeDate string `json:"freeze_date"`
// Number of products that participate in the promotion
ParticipatingProductsCount float64 `json:"participating_products_count"`
// Number of products that can participate in the promotion
PotentialProductsCount float64 `json:"potential_products_count"`
// Whether or not you participate in the promotion
IsParticipating bool `json:"participating"`
// Number of products that participate in the promotion
ParticipatingProductsCount float64 `json:"participating_products_count"`
// Indication that customers need a promo code to participate in the promotion
IsVoucherAction bool `json:"is_voucher_action"`
// Whether or not you participate in the promotion
IsParticipating bool `json:"is_participating"`
// Number of blocked products
BannedProductsCount float64 `json:"banned_products_count"`
// Indication that customers need a promo code to participate in the promotion
IsVoucherAction bool `json:"is_voucher_action"`
// Indication of the promotion is with the target audience
WithTargeting bool `json:"with_targeting"`
// Number of blocked products
BannedProductsCount float64 `json:"banned_products_count"`
// Order amount
OrderAmount float64 `json:"order_amount"`
// Indication of the promotion is with the target audience
WithTargeting bool `json:"with_targeting"`
// Discount type
DiscountType string `json:"discount_type"`
// Order amount
OrderAmount float64 `json:"order_amount"`
// Discount size
DiscountValue float64 `json:"discount_value"`
} `json:"result"`
// Discount type
DiscountType string `json:"discount_type"`
// Discount size
DiscountValue float64 `json:"discount_value"`
}
// A method for getting a list of promotions that you can participate in
func (c Promotions) GetAvailablePromotions() (*GetAvailablePromotionsResponse, error) {
func (c Promotions) GetAvailablePromotions(ctx context.Context) (*GetAvailablePromotionsResponse, error) {
url := "/v1/actions"
resp := &GetAvailablePromotionsResponse{}
response, err := c.client.Request(http.MethodGet, url, nil, resp)
response, err := c.client.Request(ctx, http.MethodGet, url, nil, resp, nil)
if err != nil {
return nil, err
}
@@ -108,28 +111,32 @@ type AddProductToPromotionResponse struct {
core.CommonResponse
// Method result
Result struct {
// List of product identifiers that were added to the promotion
ProductIds []float64 `json:"product_ids"`
Result AddProductToPromotionResult `json:"result"`
}
// List of products that weren't added to the promotion
Rejected []struct {
// Product identifier
ProductId float64 `json:"product_id"`
type AddProductToPromotionResult struct {
// List of product identifiers that were added to the promotion
ProductIds []float64 `json:"product_ids"`
// Reason why the product wasn't added to the promotion
Reason string `json:"reason"`
} `json:"rejected"`
} `json:"result"`
// List of products that weren't added to the promotion
Rejected []AddProductToPromotionResultRejected `json:"rejected"`
}
type AddProductToPromotionResultRejected struct {
// Product identifier
ProductId float64 `json:"product_id"`
// Reason why the product wasn't added to the promotion
Reason string `json:"reason"`
}
// A method for adding products to an available promotion
func (c Promotions) AddToPromotion(params *AddProductToPromotionParams) (*AddProductToPromotionResponse, error) {
func (c Promotions) AddToPromotion(ctx context.Context, params *AddProductToPromotionParams) (*AddProductToPromotionResponse, error) {
url := "/v1/actions/products/activate"
resp := &AddProductToPromotionResponse{}
response, err := c.client.Request(http.MethodGet, url, params, resp)
response, err := c.client.Request(ctx, http.MethodGet, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -147,20 +154,22 @@ type ProductsAvailableForPromotionParams struct {
// Number of elements that will be skipped in the response.
// For example, if offset=10, the response will start with the 11th element found
Offset float64 `json:"offset"`
Offset float64 `json:"offset,omitempty"`
}
type ProductsAvailableForPromotionResponse struct {
core.CommonResponse
// Method result
Result struct {
// Products list
Products []PromotionProduct `json:"products"`
Result ProductsAvailableForPromotionResult `json:"result"`
}
// Total number of products that can participate in the promotion
Total float64 `json:"total"`
} `json:"result"`
type ProductsAvailableForPromotionResult struct {
// Products list
Products []PromotionProduct `json:"products"`
// Total number of products that can participate in the promotion
Total float64 `json:"total"`
}
type PromotionProduct struct {
@@ -174,7 +183,7 @@ type PromotionProduct struct {
ActionPrice float64 `json:"action_price"`
// Maximum possible promotional product price
MaxActionType float64 `json:"max_action_type"`
MaxActionPrice float64 `json:"max_action_price"`
// Type of adding a product to the promotion: automatically or manually by the seller
AddMode string `json:"add_mode"`
@@ -187,12 +196,12 @@ type PromotionProduct struct {
}
// A method for getting a list of products that can participate in the promotion by the promotion identifier
func (c Promotions) ProductsAvailableForPromotion(params *ProductsAvailableForPromotionParams) (*ProductsAvailableForPromotionResponse, error) {
func (c Promotions) ProductsAvailableForPromotion(ctx context.Context, params *ProductsAvailableForPromotionParams) (*ProductsAvailableForPromotionResponse, error) {
url := "/v1/actions/candidates"
resp := &ProductsAvailableForPromotionResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -209,29 +218,31 @@ type ProductsInPromotionParams struct {
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
Offset float64 `json:"offset"`
Offset float64 `json:"offset,omitempty"`
}
type ProductsInPromotionResponse struct {
core.CommonResponse
// Method result
Result struct {
// Products list
Products []PromotionProduct `json:"products"`
Result ProductsInPromotionResult `json:"result"`
}
// Total number of products that can participate in the promotion
Total float64 `json:"total"`
} `json:"reuslt"`
type ProductsInPromotionResult struct {
// Products list
Products []PromotionProduct `json:"products"`
// Total number of products that can participate in the promotion
Total float64 `json:"total"`
}
// A method for getting the list of products participating in the promotion by its identifier
func (c Promotions) ProductsInPromotion(params *ProductsInPromotionParams) (*ProductsInPromotionResponse, error) {
func (c Promotions) ProductsInPromotion(ctx context.Context, params *ProductsInPromotionParams) (*ProductsInPromotionResponse, error) {
url := "/v1/actions/products"
resp := &ProductsInPromotionResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -252,28 +263,32 @@ type RemoveProductFromPromotionResponse struct {
core.CommonResponse
// Method result
Result struct {
// List of product identifiers that were removed from the promotion
ProductIds []float64 `json:"product_ids"`
Result RemoveProductFromPromotionResult `json:"result"`
}
// List of product identifiers that weren't removed from the promotion
Rejected []struct {
// Product identifier
ProductId float64 `json:"product_id"`
type RemoveProductFromPromotionResult struct {
// List of product identifiers that were removed from the promotion
ProductIds []float64 `json:"product_ids"`
// Reason why the product wasn't removed from the promotion
Reason string `json:"reason"`
} `json:"rejected"`
} `json:"result"`
// List of product identifiers that weren't removed from the promotion
Rejected []RemoveProductFromPromotionResultRejected `json:"rejected"`
}
type RemoveProductFromPromotionResultRejected struct {
// Product identifier
ProductId float64 `json:"product_id"`
// Reason why the product wasn't removed from the promotion
Reason string `json:"reason"`
}
// A method for removing products from the promotion
func (c Promotions) RemoveProduct(params *RemoveProductFromPromotionParams) (*RemoveProductFromPromotionResponse, error) {
func (c Promotions) RemoveProduct(ctx context.Context, params *RemoveProductFromPromotionParams) (*RemoveProductFromPromotionResponse, error) {
url := "/v1/actions/products/deactivate"
resp := &RemoveProductFromPromotionResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -286,42 +301,44 @@ type ListHotSalePromotionsResponse struct {
core.CommonResponse
// Method result
Result []struct {
// Promotion end date
DateEnd string `json:"date_end"`
Result []ListHotSalePromotionsResult `json:"result"`
}
// Promotion start date
DateStart string `json:"date_start"`
type ListHotSalePromotionsResult struct {
// Promotion end date
DateEnd string `json:"date_end"`
// Promotion description
Description string `json:"description"`
// Promotion start date
DateStart string `json:"date_start"`
// Promotion freeze date.
//
// If the field is filled, the seller can't increase prices, change the list of products,
// or decrease the number of product units in the promotion.
//
// The seller can lower prices and increase the product units number in the promotion
FreezeDate string `json:"freeze_date"`
// Promotion description
Description string `json:"description"`
// Hot Sale promotion identifier
HotsaleId float64 `json:"hotsale_id"`
// Promotion freeze date.
//
// If the field is filled, the seller can't increase prices, change the list of products,
// or decrease the number of product units in the promotion.
//
// The seller can lower prices and increase the product units number in the promotion
FreezeDate string `json:"freeze_date"`
// Indication that you participate in this promotion
IsParticipating bool `json:"is_participating"`
// Hot Sale promotion identifier
HotsaleId float64 `json:"hotsale_id"`
// Promotion name
Title string `json:"title"`
} `json:"result"`
// Indication that you participate in this promotion
IsParticipating bool `json:"is_participating"`
// Promotion name
Title string `json:"title"`
}
// List of available Hot Sale promotions
func (c Promotions) ListHotSalePromotions() (*ListHotSalePromotionsResponse, error) {
func (c Promotions) ListHotSalePromotions(ctx context.Context) (*ListHotSalePromotionsResponse, error) {
url := "/v1/actions/hotsales/list"
resp := &ListHotSalePromotionsResponse{}
response, err := c.client.Request(http.MethodPost, url, nil, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, nil)
if err != nil {
return nil, err
}
@@ -338,50 +355,54 @@ type ProductsAvailableForHotSalePromotionParams struct {
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
Offset float64 `json:"offset"`
Offset float64 `json:"offset,omitempty"`
}
type ProductsAvailableForHotSalePromotionResponse struct {
core.CommonResponse
// Method result
Result struct {
// Products list
Products []struct {
// Promotional product price
ActionPrice float64 `json:"action_price"`
Result ProductsAvailableForHotSalePromotionResult `json:"result"`
}
// Date when the product participates in the promotion in the YYYY-MM-DD format
DateDayPromo string `json:"date_day_promo"`
type ProductsAvailableForHotSalePromotionResult struct {
// Products list
Products []ProductsAvailableForHotSalePromotionResultProduct `json:"products"`
// Product identifier
Id float64 `json:"id"`
// Total number of products that are available for the promotion
Total float64 `json:"total"`
}
// Indication that product participates in the promotion
IsActive bool `json:"is_active"`
type ProductsAvailableForHotSalePromotionResultProduct struct {
// Promotional product price
ActionPrice float64 `json:"action_price"`
// Maximum possible promotional price of the product
MaxActionPrice float64 `json:"max_action_type"`
// Date when the product participates in the promotion in the YYYY-MM-DD format
DateDayPromo string `json:"date_day_promo"`
// Minimum number of product units in a stock discount type promotion
MinStock float64 `json:"min_stock"`
// Product identifier
Id float64 `json:"id"`
// Number of product units in a stock discount type promotion
Stock float64 `json:"stock"`
} `json:"products"`
// Indication that product participates in the promotion
IsActive bool `json:"is_active"`
// Total number of products that are available for the promotion
Total float64 `json:"total"`
} `json:"result"`
// Maximum possible promotional price of the product
MaxActionPrice float64 `json:"max_action_price"`
// Minimum number of product units in a stock discount type promotion
MinStock float64 `json:"min_stock"`
// Number of product units in a stock discount type promotion
Stock float64 `json:"stock"`
}
// Method for getting a list of products that can participate or are already participating in the Hot Sale promotion
func (c Promotions) ProductsAvailableForHotSalePromotion(params *ProductsAvailableForHotSalePromotionParams) (*ProductsAvailableForHotSalePromotionResponse, error) {
func (c Promotions) ProductsAvailableForHotSalePromotion(ctx context.Context, params *ProductsAvailableForHotSalePromotionParams) (*ProductsAvailableForHotSalePromotionResponse, error) {
url := "/v1/actions/hotsales/products"
resp := &ProductsAvailableForHotSalePromotionResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -413,24 +434,28 @@ type ProductsToHotSaleResponse struct {
core.CommonResponse
// Method result
Result struct {
// List of products that haven't been added to the promotion
Rejected []struct {
//Product identifier
ProductId float64 `json:"product_id"`
// Reason why the product hasn't been added to the promotion
Reason string `json:"reason"`
} `json:"rejected"`
} `json:"result"`
Result ProductsToHotSaleResult `json:"result"`
}
func (c Promotions) AddProductsToHotSale(params *AddProductsToHotSaleParams) (*ProductsToHotSaleResponse, error) {
type ProductsToHotSaleResult struct {
// List of products that haven't been added to the promotion
Rejected []ProductsToHotSaleResultRejected `json:"rejected"`
}
type ProductsToHotSaleResultRejected struct {
//Product identifier
ProductId float64 `json:"product_id"`
// Reason why the product hasn't been added to the promotion
Reason string `json:"reason"`
}
func (c Promotions) AddProductsToHotSale(ctx context.Context, params *AddProductsToHotSaleParams) (*ProductsToHotSaleResponse, error) {
url := "/v1/actions/hotsales/activate"
resp := &ProductsToHotSaleResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -448,12 +473,12 @@ type RemoveProductsToHotSaleParams struct {
}
// Remove product from the Hot Sale promotion
func (c Promotions) RemoveProductsToHotSale(params *RemoveProductsToHotSaleParams) (*ProductsToHotSaleResponse, error) {
func (c Promotions) RemoveProductsToHotSale(ctx context.Context, params *RemoveProductsToHotSaleParams) (*ProductsToHotSaleResponse, error) {
url := "/v1/actions/hotsales/activate"
resp := &ProductsToHotSaleResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -464,7 +489,7 @@ func (c Promotions) RemoveProductsToHotSale(params *RemoveProductsToHotSaleParam
type ListDiscountRequestsParams struct {
// Discount request status
Status string `json:"status" default:"UNKNOWN"`
Status ListDiscountRequestsStatus `json:"status" default:"UNKNOWN"`
// Page number from which you want to download the list of discount requests
Page uint64 `json:"page"`
@@ -477,121 +502,123 @@ type ListDiscountRequestsResponse struct {
core.CommonResponse
// List of requests
Result []struct {
// Request ID
Id uint64 `json:"id"`
Result []ListDiscountRequestsResult `json:"result"`
}
// Request created date
CreatedAt time.Time `json:"created_at"`
type ListDiscountRequestsResult struct {
// Request ID
Id uint64 `json:"id"`
// End time of the request
EndAt time.Time `json:"end_at"`
// Request created date
CreatedAt time.Time `json:"created_at"`
// Time to change the decision
EditedTill time.Time `json:"edited_till"`
// End time of the request
EndAt time.Time `json:"end_at"`
// Request status
Status string `json:"status"`
// Time to change the decision
EditedTill time.Time `json:"edited_till"`
// Customer's name
CustomerName string `json:"customer_name"`
// Request status
Status string `json:"status"`
// Product identifier in the Ozon system, SKU
SKU uint64 `json:"sku"`
// Customer's name
CustomerName string `json:"customer_name"`
// Customer's comment on the request
UserComment string `json:"user_comment"`
// Product identifier in the Ozon system, SKU
SKU uint64 `json:"sku"`
// Seller's comment on the request
SellerComment string `json:"seller_comment"`
// Customer's comment on the request
UserComment string `json:"user_comment"`
// Requested price
RequestedPrice float64 `json:"requested_price"`
// Seller's comment on the request
SellerComment string `json:"seller_comment"`
// Approved price
ApprovedPrice float64 `json:"approved_price"`
// Requested price
RequestedPrice float64 `json:"requested_price"`
// Product price before all discounts
OriginalPrice float64 `json:"original_price"`
// Approved price
ApprovedPrice float64 `json:"approved_price"`
// Discount in rubles
Discount float64 `json:"discount"`
// Product price before all discounts
OriginalPrice float64 `json:"original_price"`
// Discount percentage
DiscountPercent float64 `json:"discount_percent"`
// Discount in rubles
Discount float64 `json:"discount"`
// Base price at which a product is selling on Ozon, if not eligible for a promotion
BasePrice float64 `json:"base_price"`
// Discount percentage
DiscountPercent float64 `json:"discount_percent"`
// The minimum price after auto-application of discounts and promotions
MinAutoPrice float64 `json:"min_auto_price"`
// Base price at which a product is selling on Ozon, if not eligible for a promotion
BasePrice float64 `json:"base_price"`
// ID of the previous customer request for this product
PrevTaskId uint64 `json:"prev_task_id"`
// The minimum price after auto-application of discounts and promotions
MinAutoPrice float64 `json:"min_auto_price"`
// If product is damaged — true
IsDamaged bool `json:"is_damaged"`
// ID of the previous customer request for this product
PrevTaskId uint64 `json:"prev_task_id"`
// Moderation date: review, approval or decline of the request
ModeratedAt time.Time `json:"moderated_at"`
// If product is damaged — true
IsDamaged bool `json:"is_damaged"`
// Discount in rubles approved by the seller. Pass the value 0 if the seller did not approve the request
ApprovedDiscount float64 `json:"approved_discount"`
// Moderation date: review, approval or decline of the request
ModeratedAt time.Time `json:"moderated_at"`
// Discount percentage approved by the seller. Pass the value 0 if the seller did not approve the request
ApprovedDiscountPercent float64 `json:"approved_discount_percent"`
// Discount in rubles approved by the seller. Pass the value 0 if the seller did not approve the request
ApprovedDiscount float64 `json:"approved_discount"`
// Whether the customer has purchased the product. true if purchased
IsPurchased bool `json:"is_purchased"`
// Discount percentage approved by the seller. Pass the value 0 if the seller did not approve the request
ApprovedDiscountPercent float64 `json:"approved_discount_percent"`
// Whether the request was moderated automatically. true if moderation was automatic
IsAutoModerated bool `json:"is_auto_moderated"`
// Whether the customer has purchased the product. true if purchased
IsPurchased bool `json:"is_purchased"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Whether the request was moderated automatically. true if moderation was automatic
IsAutoModerated bool `json:"is_auto_moderated"`
// Email of the user who processed the request
Email string `json:"email"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Last name of the user who processed the request
LastName string `json:"last_name"`
// Email of the user who processed the request
Email string `json:"email"`
// First name of the user who processed the request
FirstName string `json:"first_name"`
// Last name of the user who processed the request
LastName string `json:"last_name"`
// Patronymic of the user who processed the request
Patronymic string `json:"patronymic"`
// First name of the user who processed the request
FirstName string `json:"first_name"`
// Approved minimum quantity of products
ApprovedQuantityMin uint64 `json:"approved_quantity_min"`
// Patronymic of the user who processed the request
Patronymic string `json:"patronymic"`
// Approved maximum quantity of products
ApprovedQuantityMax uint64 `json:"approved_quantity_max"`
// Approved minimum quantity of products
ApprovedQuantityMin uint64 `json:"approved_quantity_min"`
// Requested minimum number of products
RequestedQuantityMin uint64 `json:"requested_quantity_min"`
// Approved maximum quantity of products
ApprovedQuantityMax uint64 `json:"approved_quantity_max"`
// Requested maximum number of products
RequestedQuantityMax uint64 `json:"requested_quantity_max"`
// Requested minimum number of products
RequestedQuantityMin uint64 `json:"requested_quantity_min"`
// Requested price with fee
RequestedPriceWithFee float64 `json:"requested_price_with_fee"`
// Requested maximum number of products
RequestedQuantityMax uint64 `json:"requested_quantity_max"`
// Approved price with fee
ApprovedPriceWithFee float64 `json:"approved_price_with_fee"`
// Requested price with fee
RequestedPriceWithFee float64 `json:"requested_price_with_fee"`
// Approved price fee percent
ApprovedPriceFeePercent float64 `json:"approved_price_fee_percent"`
} `json:"result"`
// Approved price with fee
ApprovedPriceWithFee float64 `json:"approved_price_with_fee"`
// Approved price fee percent
ApprovedPriceFeePercent float64 `json:"approved_price_fee_percent"`
}
// Method for getting a list of products that customers want to buy with discount
func (c Promotions) ListDiscountRequests(params *ListDiscountRequestsParams) (*ListDiscountRequestsResponse, error) {
func (c Promotions) ListDiscountRequests(ctx context.Context, params *ListDiscountRequestsParams) (*ListDiscountRequestsResponse, error) {
url := "/v1/actions/discounts-task/list"
resp := &ListDiscountRequestsResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -626,33 +653,37 @@ type DiscountRequestResponse struct {
core.CommonResponse
// Method result
Result struct {
// Errors when creating a request
FailDetails []struct {
// Request ID
TaskId uint64 `json:"task_id"`
Result DiscountRequestResult `json:"result"`
}
// Error message
ErrorForUser string `json:"error_for_user"`
} `json:"fail_details"`
type DiscountRequestResult struct {
// Errors when creating a request
FailDetails []DiscountRequestResultFailDetail `json:"fail_details"`
// The number of requests with a successful status change
SuccessCount int32 `json:"success_count"`
// The number of requests with a successful status change
SuccessCount int32 `json:"success_count"`
// The number of requests that failed to change their status
FailCount int32 `json:"fail_count"`
} `json:"result"`
// The number of requests that failed to change their status
FailCount int32 `json:"fail_count"`
}
type DiscountRequestResultFailDetail struct {
// Request ID
TaskId uint64 `json:"task_id"`
// Error message
ErrorForUser string `json:"error_for_user"`
}
// You can approve applications in statuses:
// - NEW — new
// - SEEN — viewed
func (c Promotions) ApproveDiscountRequest(params *DiscountRequestParams) (*DiscountRequestResponse, error) {
func (c Promotions) ApproveDiscountRequest(ctx context.Context, params *DiscountRequestParams) (*DiscountRequestResponse, error) {
url := "/v1/actions/discounts-task/approve"
resp := &DiscountRequestResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -664,12 +695,12 @@ func (c Promotions) ApproveDiscountRequest(params *DiscountRequestParams) (*Disc
// You can decline applications in statuses:
// - NEW—new
// - SEEN—viewed
func (c Promotions) DeclineDiscountRequest(params *DiscountRequestParams) (*DiscountRequestResponse, error) {
func (c Promotions) DeclineDiscountRequest(ctx context.Context, params *DiscountRequestParams) (*DiscountRequestResponse, error) {
url := "/v1/actions/discounts-task/decline"
resp := &DiscountRequestResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"testing"
@@ -56,11 +57,15 @@ func TestGetAvailablePromotions(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().GetAvailablePromotions()
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().GetAvailablePromotions(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetAvailablePromotionsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -125,11 +130,15 @@ func TestAddToPromotion(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().AddToPromotion(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().AddToPromotion(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &AddProductToPromotionResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -201,11 +210,15 @@ func TestProductsAvailableForPromotion(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().ProductsAvailableForPromotion(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().ProductsAvailableForPromotion(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ProductsAvailableForPromotionResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -262,11 +275,15 @@ func TestProductsInPromotion(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().ProductsInPromotion(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().ProductsInPromotion(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ProductsInPromotionResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -314,11 +331,15 @@ func TestRemoveProduct(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().RemoveProduct(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().RemoveProduct(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &RemoveProductFromPromotionResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -373,11 +394,15 @@ func TestListHotSalePromotions(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().ListHotSalePromotions()
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().ListHotSalePromotions(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListHotSalePromotionsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -434,11 +459,15 @@ func TestProductsAvailableForHotSalePromotion(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().ProductsAvailableForHotSalePromotion(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().ProductsAvailableForHotSalePromotion(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ProductsAvailableForHotSalePromotionResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -494,11 +523,15 @@ func TestAddProductsToHotSale(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().AddProductsToHotSale(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().AddProductsToHotSale(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ProductsToHotSaleResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -548,11 +581,15 @@ func TestRemoveProductsToHotSale(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().RemoveProductsToHotSale(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().RemoveProductsToHotSale(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ProductsToHotSaleResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -634,11 +671,15 @@ func TestListDiscountRequests(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().ListDiscountRequests(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().ListDiscountRequests(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListDiscountRequestsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -661,9 +702,9 @@ func TestApproveDiscountRequest(t *testing.T) {
&DiscountRequestParams{
Tasks: []DiscountRequestTask{
{
Id: 123,
ApprovedPrice: 11,
SellerComment: "string",
Id: 123,
ApprovedPrice: 11,
SellerComment: "string",
ApprovedQuantityMin: 1,
ApprovedQuantityMax: 2,
},
@@ -697,11 +738,15 @@ func TestApproveDiscountRequest(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().ApproveDiscountRequest(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().ApproveDiscountRequest(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DiscountRequestResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -724,9 +769,9 @@ func TestDeclineDiscountRequest(t *testing.T) {
&DiscountRequestParams{
Tasks: []DiscountRequestTask{
{
Id: 123,
ApprovedPrice: 11,
SellerComment: "string",
Id: 123,
ApprovedPrice: 11,
SellerComment: "string",
ApprovedQuantityMin: 1,
ApprovedQuantityMax: 2,
},
@@ -760,11 +805,15 @@ func TestDeclineDiscountRequest(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Promotions().DeclineDiscountRequest(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Promotions().DeclineDiscountRequest(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DiscountRequestResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"time"
@@ -15,81 +16,87 @@ type GetCurrentSellerRatingInfoResponse struct {
core.CommonResponse
// Rating groups list
Groups []struct {
// Ratings group name
GroupName string `json:"group_name"`
Groups []GetCurrentSellerRatingInfoGroup `json:"groups"`
// Ratings list
Items []struct {
// An indication that the penalty points balance is exceeded
PenaltyScoreExceeded bool `json:"penalty_score_exceeded"`
// Rating change: the ratio of the previous value to the current one
Change struct {
// How the rating value has changed:
// - DIRECTION_UNKNOWN — unknown.
// - DIRECTION_NONE — has not changed.
// - DIRECTION_RISE — has increased.
// - DIRECTION_FALL — has dropped.
Direction string `json:"direction"`
// What the change means:
// - MEANING_UNKNOWN — unknown.
// - MEANING_NONE — neutral.
// - MEANING_GOOD — the indicator is improving, everything is good.
// - MEANING_BAD — the indicator is dropping, you should do something.
Meaning string `json:"meaning"`
} `json:"change"`
// Current rating value
CurrentValue float64 `json:"current_value"`
// Rating name
Name string `json:"name"`
// Previous rating value
PastValue float64 `json:"past_value"`
// System rating name
Rating string `json:"rating"`
// What should be the rating value to be considered good:
// - UNKNOWN_DIRECTION — unknown.
// - NEUTRAL — doesn't matter.
// - HIGHER_IS_BETTER — the higher the better.
// - LOWER_IS_BETTER — the lower the better.
RatingDirection string `json:"rating_direction"`
// Rating status:
// - UNKNOWN_STATUS — unknown status.
// - OK — everything is OK.
// - WARNING — indicators require attention.
// - CRITICAL — critical rating
Status string `json:"status"`
// Value type:
// - UNKNOWN_VALUE — unknown,
// - INDEX,
// - PERCENT,
// - TIME,
// - RATIO — coefficient,
// - REVIEW_SCORE — score,
// - COUNT
ValueType string `json:"value_type"`
} `json:"items"`
// An indication that the penalty points balance is exceeded
PenaltyScoreExceeded bool `json:"penalty_score_exceeded"`
// An indication that you participate in the Premium program
Premium bool `json:"premium"`
} `json:"groups"`
// An indication that you participate in the Premium program
Premium bool `json:"premium"`
}
func (c Rating) GetCurrentSellerRatingInfo() (*GetCurrentSellerRatingInfoResponse, error) {
type GetCurrentSellerRatingInfoGroup struct {
// Ratings group name
GroupName string `json:"group_name"`
// Ratings list
Items []GetCurrentSellerRatingInfoGroupItem `json:"items"`
}
type GetCurrentSellerRatingInfoGroupItem struct {
// Rating change: the ratio of the previous value to the current one
Change GetCurrentSellerRatingInfoGroupItemChange `json:"change"`
// Current rating value
CurrentValue float64 `json:"current_value"`
// Rating name
Name string `json:"name"`
// Previous rating value
PastValue float64 `json:"past_value"`
// System rating name
Rating string `json:"rating"`
// What should be the rating value to be considered good:
// - UNKNOWN_DIRECTION — unknown.
// - NEUTRAL — doesn't matter.
// - HIGHER_IS_BETTER — the higher the better.
// - LOWER_IS_BETTER — the lower the better.
RatingDirection string `json:"rating_direction"`
// Rating status:
// - UNKNOWN_STATUS — unknown status.
// - OK — everything is OK.
// - WARNING — indicators require attention.
// - CRITICAL — critical rating
Status string `json:"status"`
// Value type:
// - UNKNOWN_VALUE — unknown,
// - INDEX,
// - PERCENT,
// - TIME,
// - RATIO — coefficient,
// - REVIEW_SCORE — score,
// - COUNT
ValueType string `json:"value_type"`
}
type GetCurrentSellerRatingInfoGroupItemChange struct {
// How the rating value has changed:
// - DIRECTION_UNKNOWN — unknown.
// - DIRECTION_NONE — has not changed.
// - DIRECTION_RISE — has increased.
// - DIRECTION_FALL — has dropped.
Direction string `json:"direction"`
// What the change means:
// - MEANING_UNKNOWN — unknown.
// - MEANING_NONE — neutral.
// - MEANING_GOOD — the indicator is improving, everything is good.
// - MEANING_BAD — the indicator is dropping, you should do something.
Meaning string `json:"meaning"`
}
func (c Rating) GetCurrentSellerRatingInfo(ctx context.Context) (*GetCurrentSellerRatingInfoResponse, error) {
url := "/v1/rating/summary"
resp := &GetCurrentSellerRatingInfoResponse{}
response, err := c.client.Request(http.MethodPost, url, nil, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, nil)
if err != nil {
return nil, err
}
@@ -116,69 +123,79 @@ type GetSellerRatingInfoPeriodResponse struct {
core.CommonResponse
// Information on the Premium program penalty points
PremiumScores []struct {
// Rating name
Rating string `json:"rating"`
// Information on penalty points
Scores []struct {
// Date when the penalty points were received
Date time.Time `json:"date"`
// Rating value for which the penalty points were received
RatingValue float64 `json:"rating_value"`
// Number of received penalty points
Value int32 `json:"value"`
} `json:"scores"`
} `json:"premium_scores"`
PremiumScores []GetSellerRatingInfoPeriodPremiumScores `json:"premium_scores"`
// Information on the seller ratings
Ratings []struct {
// Rating threshold, after which sales will be blocked
DangerThreshold float64 `json:"danger_threshold"`
// Rating threshold for participation in the Premium program
PremiumThreshold float64 `json:"premium_threshold"`
// Rating system name
Rating string `json:"rating"`
// Rating values list
Values []struct {
// Rating calculation start date
DateFrom time.Time `json:"date_from"`
// Rating calculation end date
DateTo time.Time `json:"date_to"`
// Rating status
Status struct {
// Indication if the rating threshold for blocking is exceeded
Danger bool `json:"danger"`
// Indication whether the threshold for participation in the Premium program has been reached
Premium bool `json:"premium"`
// Indication of a warning that the threshold for blocking may be exceeded
Warning bool `json:"warning"`
} `json:"status"`
// Rating value
Value float64 `json:"value"`
} `json:"values"`
// Rating threshold, after which a warning about possible blocking appears
WarningThreshold float64 `json:"warning_threshold"`
} `json:"ratings"`
Ratings []GetSellerRatingInfoPeriodRating `json:"ratings"`
}
func (c Rating) GetSellerRatingInfoForPeriod(params *GetSellerRatingInfoForPeriodParams) (*GetSellerRatingInfoPeriodResponse, error) {
type GetSellerRatingInfoPeriodPremiumScores struct {
// Rating name
Rating string `json:"rating"`
// Information on penalty points
Scores []GetSellerRatingInfoPeriodPremiumScore `json:"scores"`
}
type GetSellerRatingInfoPeriodPremiumScore struct {
// Date when the penalty points were received
Date time.Time `json:"date"`
// Rating value for which the penalty points were received
RatingValue float64 `json:"rating_value"`
// Number of received penalty points
Value int32 `json:"value"`
}
type GetSellerRatingInfoPeriodRating struct {
// Rating threshold, after which sales will be blocked
DangerThreshold float64 `json:"danger_threshold"`
// Rating threshold for participation in the Premium program
PremiumThreshold float64 `json:"premium_threshold"`
// Rating system name
Rating string `json:"rating"`
// Rating values list
Values []GetSellerRatingInfoPeriodRatingValue `json:"values"`
// Rating threshold, after which a warning about possible blocking appears
WarningThreshold float64 `json:"warning_threshold"`
}
type GetSellerRatingInfoPeriodRatingValue struct {
// Rating calculation start date
DateFrom time.Time `json:"date_from"`
// Rating calculation end date
DateTo time.Time `json:"date_to"`
// Rating status
Status GetSellerRatingInfoPeriodRatingValueStatus `json:"status"`
// Rating value
Value float64 `json:"value"`
}
type GetSellerRatingInfoPeriodRatingValueStatus struct {
// Indication if the rating threshold for blocking is exceeded
Danger bool `json:"danger"`
// Indication whether the threshold for participation in the Premium program has been reached
Premium bool `json:"premium"`
// Indication of a warning that the threshold for blocking may be exceeded
Warning bool `json:"warning"`
}
func (c Rating) GetSellerRatingInfoForPeriod(ctx context.Context, params *GetSellerRatingInfoForPeriodParams) (*GetSellerRatingInfoPeriodResponse, error) {
url := "/v1/rating/history"
resp := &GetSellerRatingInfoPeriodResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"testing"
@@ -58,11 +59,15 @@ func TestGetCurrentRatingInfo(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Rating().GetCurrentSellerRatingInfo()
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Rating().GetCurrentSellerRatingInfo(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetCurrentSellerRatingInfoResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -146,11 +151,15 @@ func TestGetRatingInfoForPeriod(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Rating().GetSellerRatingInfoForPeriod(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Rating().GetSellerRatingInfoForPeriod(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetSellerRatingInfoPeriodResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"time"
@@ -38,54 +39,60 @@ type GetReportsListResponse struct {
core.CommonResponse
// Method result
Result struct {
// Array with generated reports
Reports []struct {
// Unique report identifier
Code string `json:"code"`
Result GetReportsListResult `json:"result"`
}
// Report creation date
CreatedAt time.Time `json:"created_at"`
type GetReportsListResult struct {
// Array with generated reports
Reports []GetReportsListResultReport `json:"reports"`
// Error code when generating the report
Error string `json:"error"`
// Total number of reports
Total int32 `json:"total"`
}
// Link to CSV file
File string `json:"file"`
type GetReportsListResultReport struct {
// Unique report identifier
Code string `json:"code"`
// Array with the filters specified when the seller created the report
Params struct {
} `json:"params"`
// Report creation date
CreatedAt time.Time `json:"created_at"`
// Report type:
// - SELLER_PRODUCTS — products report,
// - SELLER_TRANSACTIONS — transactions report,
// - SELLER_PRODUCT_PRICES — product prices report,
// - SELLER_STOCK — stocks report,
// - SELLER_PRODUCT_MOVEMENT — products movement report,
// - SELLER_RETURNS — returns report,
// - SELLER_POSTINGS — shipments report,
// - SELLER_FINANCE — financial report
ReportType string `json:"report_type"`
// Error code when generating the report
Error string `json:"error"`
// Report generation status
// - `success`
// - `failed`
Status string `json:"status"`
} `json:"reports"`
// Link to CSV file
//
// For a report with the SELLER_RETURNS type,
// the link is available within 5 minutes after making a request.
File string `json:"file"`
// Total number of reports
Total int32 `json:"total"`
} `json:"result"`
// Array with the filters specified when the seller created the report
Params map[string]string `json:"params"`
// Report type:
// - SELLER_PRODUCTS — products report,
// - SELLER_TRANSACTIONS — transactions report,
// - SELLER_PRODUCT_PRICES — product prices report,
// - SELLER_STOCK — stocks report,
// - SELLER_PRODUCT_MOVEMENT — products movement report,
// - SELLER_RETURNS — returns report,
// - SELLER_POSTINGS — shipments report,
// - SELLER_FINANCE — financial report
ReportType string `json:"report_type"`
// Report generation status
// - `success`
// - `failed`
Status string `json:"status"`
}
// Returns the list of reports that have been generated before
func (c Reports) GetList(params *GetReportsListParams) (*GetReportsListResponse, error) {
func (c Reports) GetList(ctx context.Context, params *GetReportsListParams) (*GetReportsListResponse, error) {
url := "/v1/report/list"
resp := &GetReportsListResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -103,47 +110,39 @@ type GetReportDetailsResponse struct {
core.CommonResponse
// Report details
Result struct {
// Unique report identifier
Code string `json:"code"`
Result GetReportDetailResult `json:"result"`
}
// Report creation date
CreatedAt time.Time `json:"created_at"`
type GetReportDetailResult struct {
// Unique report identifier
Code string `json:"code"`
// Error code when generating the report
Error string `json:"error"`
// Report creation date
CreatedAt time.Time `json:"created_at"`
// Link to CSV file
File string `json:"file"`
// Error code when generating the report
Error string `json:"error"`
// Array with the filters specified when the seller created the report
Params map[string]string `json:"params"`
// Link to CSV file
File string `json:"file"`
// Report type:
// - SELLER_PRODUCTS — products report,
// - SELLER_TRANSACTIONS — transactions report,
// - SELLER_PRODUCT_PRICES — product prices report,
// - SELLER_STOCK — stocks report,
// - SELLER_PRODUCT_MOVEMENT — products movement report,
// - SELLER_RETURNS — returns report,
// - SELLER_POSTINGS — shipments report,
// - SELLER_FINANCE — financial report
ReportType string `json:"report_type"`
// Array with the filters specified when the seller created the report
Params map[string]string `json:"params"`
// Report generation status:
// - success
// - failed
Status string `json:"status"`
} `json:"result"`
// Report type
ReportType ReportType `json:"report_type"`
// Report generation status
Status ReportInfoStatus `json:"status"`
}
// Returns information about a created report by its identifier
func (c Reports) GetReportDetails(params *GetReportDetailsParams) (*GetReportDetailsResponse, error) {
func (c Reports) GetReportDetails(ctx context.Context, params *GetReportDetailsParams) (*GetReportDetailsResponse, error) {
url := "/v1/report/info"
resp := &GetReportDetailsResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -159,6 +158,9 @@ type GetFinancialReportParams struct {
// Number of the page returned in the request
Page int64 `json:"page"`
// true, если нужно добавить дополнительные параметры в ответ
WithDetails bool `json:"with_details"`
// Number of items on the page
PageSize int64 `json:"page_size"`
}
@@ -175,52 +177,221 @@ type GetFinancialReportResponse struct {
core.CommonResponse
// Method result
Result struct {
// Reports list
CashFlows []struct {
// Period data
Period struct {
// Period identifier
Id int64 `json:"id"`
Result GetFinancialResultResult `json:"result"`
}
// Period start
Begin time.Time `json:"begin"`
type GetFinancialResultResult struct {
// Reports list
CashFlows []GetFinancialResultResultCashflow `json:"cash_flows"`
// Period end
End time.Time `json:"end"`
} `json:"period"`
// Detailed info
Details GetFinancialResultResultDetail `json:"details"`
// Sum of sold products prices
OrdersAmount float64 `json:"order_amount"`
// Number of pages with reports
PageCount int64 `json:"page_count"`
}
// Sum of returned products prices
ReturnsAmount float64 `json:"returns_amount"`
type GetFinancialResultResultCashflow struct {
// Period data
Period GetFinancialResultResultCashflowPeriod `json:"period"`
// Ozon sales commission
CommissionAmount float64 `json:"commission_amount"`
// Sum of sold products prices
OrdersAmount float64 `json:"orders_amount"`
// Additional services cost
ServicesAmount float64 `json:"services_amount"`
// Sum of returned products prices
ReturnsAmount float64 `json:"returns_amount"`
// Logistic services cost
ItemDeliveryAndReturnAmount float64 `json:"item_delivery_and_return_amount"`
// Ozon sales commission
CommissionAmount float64 `json:"commission_amount"`
// Code of the currency used to calculate the commissions
CurrencyCode string `json:"currency_code"`
} `json:"cash_flows"`
// Additional services cost
ServicesAmount float64 `json:"services_amount"`
// Number of pages with reports
PageCount int64 `json:"page_count"`
} `json:"result"`
// Logistic services cost
ItemDeliveryAndReturnAmount float64 `json:"item_delivery_and_return_amount"`
// Code of the currency used to calculate the commissions
CurrencyCode string `json:"currency_code"`
}
type GetFinancialResultResultCashflowPeriod struct {
// Period identifier
Id int64 `json:"id"`
// Period start
Begin time.Time `json:"begin"`
// Period end
End time.Time `json:"end"`
}
type GetFinancialResultResultDetail struct {
// Balance on the beginning of period
BeginBalanceAmount float64 `json:"begin_balance_amount"`
// Orders
Delivery GetFinancialResultResultDetailDelivery `json:"delivery"`
// Amount to be paid for the period
InvoiceTransfer float64 `json:"invoice_transfer"`
// Transfer under loan agreements
Loan float64 `json:"loan"`
// Paid for the period
Payments []GetFinancialResultResultDetailPayment `json:"payments"`
// Period data
Period GetFinancialResultResultDetailPeriod `json:"period"`
// Returns and cancellations
Return GetFinancialResultResultDetailReturn `json:"return"`
// rFBS transfers
RFBS GetFinancialResultResultDetailRFBS `json:"rfbs"`
// Services
Services GetFinancialResultResultDetailService `json:"services"`
// Compensation and other accruals
Others GetFinancialResultResultDetailOthers `json:"others"`
// Balance at the end of the period
EndBalanceAmount float64 `json:"end_balance_amount"`
}
type GetFinancialResultResultDetailDelivery struct {
// Total amount
Total float64 `json:"total"`
// Amount for which products were purchased, including commission fees
Amount float64 `json:"amount"`
// Processing and delivery fees
DeliveryServices GetFinancialResultResultDetailDeliveryServices `json:"delivery_services"`
}
type GetFinancialResultResultDetailDeliveryServices struct {
// Total amount
Total float64 `json:"total"`
// Details
Items []GetFinancialResultResultDetailDeliveryServicesItem `json:"items"`
}
type GetFinancialResultResultDetailDeliveryServicesItem struct {
// Operation name
Name DetailsDeliveryItemName `json:"name"`
// Amount by operation
Price float64 `json:"price"`
}
type GetFinancialResultResultDetailPayment struct {
// Currency
CurrencyCode string `json:"currency_code"`
// Payment amount
Payment float64 `json:"payment"`
}
type GetFinancialResultResultDetailPeriod struct {
// Period start
Begin time.Time `json:"begin"`
// Period end
End time.Time `json:"end"`
// Period identifier
Id int64 `json:"id"`
}
type GetFinancialResultResultDetailReturn struct {
// Total amount
Total float64 `json:"total"`
// Amount of returns received, including commission fees
Amount float64 `json:"amount"`
// Returns and cancellation fees
ReturnServices GetFinancialResultResultDetailReturnServices `json:"return_services"`
}
type GetFinancialResultResultDetailReturnServices struct {
// Total amount
Total float64 `json:"total"`
// Details
Items []GetFinancialResultResultDetailReturnServicesItem `json:"items"`
}
type GetFinancialResultResultDetailReturnServicesItem struct {
// Operation name
Name DetailsReturnServiceName `json:"name"`
// Amount by operation
Price float64 `json:"price"`
}
type GetFinancialResultResultDetailRFBS struct {
// Total amount
Total float64 `json:"total"`
// Transfers from customers
TransferDelivery float64 `json:"transfer_delivery"`
// Return of transfers to customers
TransferDeliveryReturn float64 `json:"transfer_delivery_return"`
// Compensation of delivery fees
CompensationDeliveryReturn float64 `json:"compensation_delivery_return"`
// Transfers of partial refunds to customers
PartialCompensation float64 `json:"partial_compensation"`
// Compensation of partial refunds
PartialCompensationReturn float64 `json:"partial_compensation_return"`
}
type GetFinancialResultResultDetailService struct {
// Total amount
Total float64 `json:"total"`
// Details
Items []GetFinancialResultResultDetailServiceItem `json:"items"`
}
type GetFinancialResultResultDetailServiceItem struct {
// Operation name
Name DetailsServiceItemName `json:"name"`
// Amount by operation
Price float64 `json:"price"`
}
type GetFinancialResultResultDetailOthers struct {
// Total amount
Total float64 `json:"total"`
// Details
Items []GetFinancialResultResultDetailOthersItem `json:"items"`
}
type GetFinancialResultResultDetailOthersItem struct {
// Operation name
Name DetailsOtherItemName `json:"name"`
// Amount by operation
Price float64 `json:"price"`
}
// Returns information about a created report by its identifier
func (c Reports) GetFinancial(params *GetFinancialReportParams) (*GetFinancialReportResponse, error) {
func (c Reports) GetFinancial(ctx context.Context, params *GetFinancialReportParams) (*GetFinancialReportResponse, error) {
url := "/v1/finance/cash-flow-statement/list"
resp := &GetFinancialReportResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -254,95 +425,21 @@ type GetProductsReportResponse struct {
core.CommonResponse
// Method result
Result struct {
// Unique report identifier
Code string `json:"code"`
} `json:"result"`
Result GetProductsReportResult `json:"result"`
}
type GetProductsReportResult struct {
// Unique report identifier
Code string `json:"code"`
}
// Method for getting a report with products data. For example, Ozon ID, number of products, prices, status
func (c Reports) GetProducts(params *GetProductsReportParams) (*GetProductsReportResponse, error) {
func (c Reports) GetProducts(ctx context.Context, params *GetProductsReportParams) (*GetProductsReportResponse, error) {
url := "/v1/report/products/create"
resp := &GetProductsReportResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type GetStocksReportParams struct {
// Default: "DEFAULT"
// Response language:
// - RU — Russian
// - EN — English
Language string `json:"language" default:"DEFAULT"`
}
type GetStocksReportResponse struct {
core.CommonResponse
// Method result
Result struct {
// Unique report identifier
Code string `json:"code"`
} `json:"result"`
}
// Report with information about the number of available and reserved products in stock
func (c Reports) GetStocks(params *GetStocksReportParams) (*GetStocksReportResponse, error) {
url := "/v1/report/stock/create"
resp := &GetStocksReportResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type GetProductsMovementReportParams struct {
// Date from which the data will be in the report
DateFrom time.Time `json:"date_from"`
// Date up to which the data will be in the report
DateTo time.Time `json:"date_to"`
// Default: "DEFAULT"
// Response language:
// - RU — Russian
// - EN — English
Language string `json:"language" default:"DEFAULT"`
}
type GetProductsMovementReportResponse struct {
core.CommonResponse
// Method result
Result struct {
// Unique report identifier
Code string `json:"code"`
} `json:"result"`
}
// Report with complete information on products, as well as the number of products with statuses:
// - products with defects or in inventory,
// - products in transit between the fulfillment centers,
// - products in delivery,
// - products to be sold
func (c Reports) GetProductsMovement(params *GetProductsMovementReportParams) (*GetProductsMovementReportResponse, error) {
url := "/v1/report/products/movement/create"
resp := &GetProductsMovementReportResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -353,7 +450,7 @@ func (c Reports) GetProductsMovement(params *GetProductsMovementReportParams) (*
type GetReturnsReportParams struct {
// Filter
Filter GetReturnsReportsFilter `json:"filter"`
Filter *GetReturnsReportsFilter `json:"filter,omitempty"`
// Default: "DEFAULT"
// Response language:
@@ -377,21 +474,23 @@ type GetReturnsReportResponse struct {
core.CommonResponse
// Method result
Result struct {
// Unique report identifier
Code string `json:"code"`
} `json:"result"`
Result GetReturnReportResult `json:"result"`
}
type GetReturnReportResult struct {
// Unique report identifier. The report is available for downloading within 3 days after making a request.
Code string `json:"code"`
}
// The report contains information about returned products that were accepted from the customer, ready for pickup, or delivered to the seller.
//
// The method is only suitable for orders shipped from the seller's warehouse
func (c Reports) GetReturns(params *GetReturnsReportParams) (*GetReturnsReportResponse, error) {
func (c Reports) GetReturns(ctx context.Context, params *GetReturnsReportParams) (*GetReturnsReportResponse, error) {
url := "/v1/report/returns/create"
resp := &GetReturnsReportResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
@@ -402,7 +501,7 @@ func (c Reports) GetReturns(params *GetReturnsReportParams) (*GetReturnsReportRe
type GetShipmentReportParams struct {
// Filter
Filter GetShipmentReportFilter `json:"filter"`
Filter *GetShipmentReportFilter `json:"filter,omitempty"`
// Default: "DEFAULT"
// Response language:
@@ -415,9 +514,12 @@ type GetShipmentReportFilter struct {
// Cancellation reason identifier
CancelReasonId []int64 `json:"cancel_reason_id"`
// Work scheme: FBO or FBS.
// The scheme of operation is FBO or FBS.
//
// To get an FBO scheme report, pass fbo in this parameter. For an FBS scheme report pass fbs
// Only one of the parameters can be passed to the array per query:
//
// fbo - to get a report by FBO scheme,
// fbs - to get a report by FBS scheme
DeliverySchema []string `json:"delivery_schema"`
// Product identifier
@@ -446,10 +548,12 @@ type GetShipmentReportResponse struct {
core.CommonResponse
// Method result
Result struct {
// Unique report identifier
Code string `json:"code"`
} `json:"result"`
Result GetShipmentReportResult `json:"result"`
}
type GetShipmentReportResult struct {
// Unique report identifier
Code string `json:"code"`
}
// Shipment report with orders details:
@@ -459,12 +563,75 @@ type GetShipmentReportResponse struct {
// - shipment numbers
// - shipment costs
// - shipments contents
func (c Reports) GetShipment(params *GetShipmentReportParams) (*GetShipmentReportResponse, error) {
func (c Reports) GetShipment(ctx context.Context, params *GetShipmentReportParams) (*GetShipmentReportResponse, error) {
url := "/v1/report/postings/create"
resp := &GetShipmentReportResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp)
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 IssueOnDiscountedProductsResponse struct {
core.CommonResponse
// Unique report identifier
Code string `json:"code"`
}
// Generates a report on discounted products in Ozon warehouses.
// For example, Ozon can discount a product due to damage when delivering.
//
// Returns report identifier. To get the report, send the identifier in the request body of a method `/v1/report/discounted/info`
func (c Reports) IssueOnDiscountedProducts(ctx context.Context) (*IssueOnDiscountedProductsResponse, error) {
url := "/v1/report/discounted/create"
resp := &IssueOnDiscountedProductsResponse{}
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 GetFBSStocksParams struct {
// Response language
Language string `json:"language"`
// Warehouses identifiers
WarehouseIds []int64 `json:"warehouse_id"`
}
type GetFBSStocksResponse struct {
core.CommonResponse
// Method result
Result GetFBSStocksResult `json:"result"`
}
type GetFBSStocksResult struct {
// Unique report identifier
Code string `json:"code"`
}
// Report with information about the number of available and reserved products in stock.
//
// The method returns a report identifier.
// To get the report, send the identifier in the request of the `/v1/report/info` method.
func (c Reports) GetFBSStocks(ctx context.Context, params *GetFBSStocksParams) (*GetFBSStocksResponse, error) {
url := "/v1/report/warehouse/stock"
resp := &GetFBSStocksResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, nil)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"testing"
@@ -69,11 +70,15 @@ func TestGetList(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetList(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetList(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetReportsListResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -111,7 +116,7 @@ func TestGetReportDetails(t *testing.T) {
"file": "https://storage.yandexcloud.net/ozon.reports/95/c1/95c1ae93320294cb.csv",
"report_type": "seller_products",
"params": {},
"created_at": "2021-11-25T14:54:55.688260Z"
"created_at": "2021-11-25T14:54:55.68826Z"
}
}`,
},
@@ -130,11 +135,15 @@ func TestGetReportDetails(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetReportDetails(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetReportDetails(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetReportDetailsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -165,27 +174,96 @@ func TestGetFinancialReport(t *testing.T) {
From: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2022-01-01T00:00:00.000Z"),
To: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2022-12-31T00:00:00.000Z"),
},
Page: 1,
PageSize: 1,
WithDetails: true,
Page: 1,
PageSize: 1,
},
`{
"result": {
"cash_flows": [
{
"period": {
"id": 11567022278500,
"begin": "2022-08-01T00:00:00Z",
"end": "2022-08-15T00:00:00Z"
},
"orders_amount": 1000,
"returns_amount": -3000,
"commission_amount": 1437,
"services_amount": 8471.28,
"currency_code": "string",
"item_delivery_and_return_amount": 1991,
"currency_code": "RUB"
"orders_amount": 1000,
"period": {
"begin": "2023-04-03T09:12:10.239Z",
"end": "2023-04-03T09:12:10.239Z",
"id": 11567022278500
},
"returns_amount": -3000,
"services_amount": 8471.28
}
],
"page_count": 15
"details": {
"period": {
"begin": "2023-04-03T09:12:10.239Z",
"end": "2023-04-03T09:12:10.239Z",
"id": 11567022278500
},
"payments": [
{
"payment": 0,
"currency_code": "string"
}
],
"begin_balance_amount": 0,
"delivery": {
"total": 0,
"amount": 0,
"delivery_services": {
"total": 0,
"items": [
{
"name": "string",
"price": 0
}
]
}
},
"return": {
"total": 0,
"amount": 0,
"return_services": {
"total": 0,
"items": [
{
"name": "string",
"price": 0
}
]
}
},
"loan": 0,
"invoice_transfer": 0,
"rfbs": {
"total": 0,
"transfer_delivery": 0,
"transfer_delivery_return": 0,
"compensation_delivery_return": 0,
"partial_compensation": 0,
"partial_compensation_return": 0
},
"services": {
"total": 0,
"items": [
{
"name": "string",
"price": 0
}
]
},
"others": {
"total": 0,
"items": [
{
"name": "string",
"price": 0
}
]
},
"end_balance_amount": 0
}
}
}`,
},
@@ -204,11 +282,15 @@ func TestGetFinancialReport(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetFinancial(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetFinancial(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetFinancialReportResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -258,11 +340,15 @@ func TestGetProductsReport(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetProducts(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductsReportResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -275,113 +361,6 @@ func TestGetProductsReport(t *testing.T) {
}
}
func TestGetStocksReport(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetStocksReportParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetStocksReportParams{},
`{
"result": {
"code": "d55f4517-8347-4e24-9d93-d6e736c1c07c"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetStocksReportParams{},
`{
"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))
resp, err := c.Reports().GetStocks(test.params)
if err != nil {
t.Error(err)
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.Code == "" {
t.Errorf("Code cannot be empty")
}
}
}
}
func TestGetProductsMovementReport(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetProductsMovementReportParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetProductsMovementReportParams{
DateFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2020-08-01T14:15:22Z"),
DateTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2020-08-01T14:15:22Z"),
},
`{
"result": {
"code": "h56f4917-1346-4e64-9d90-с6e736c1e07c"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetProductsMovementReportParams{},
`{
"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))
resp, err := c.Reports().GetProductsMovement(test.params)
if err != nil {
t.Error(err)
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.Code == "" {
t.Errorf("Code cannot be empty")
}
}
}
}
func TestGetReturnsReport(t *testing.T) {
t.Parallel()
@@ -396,7 +375,7 @@ func TestGetReturnsReport(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetReturnsReportParams{
Filter: GetReturnsReportsFilter{
Filter: &GetReturnsReportsFilter{
DeliverySchema: "fbs",
},
},
@@ -421,15 +400,19 @@ func TestGetReturnsReport(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetReturns(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetReturns(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetReturnsReportResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.Code == "" {
t.Errorf("Code cannot be empty")
@@ -452,7 +435,7 @@ func TestGetShipmentReport(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetShipmentReportParams{
Filter: GetShipmentReportFilter{
Filter: &GetShipmentReportFilter{
DeliverySchema: []string{"fbs", "fbo", "crossborder"},
ProcessedAtFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-09-02T17:10:54.861Z"),
ProcessedAtTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-11-02T17:10:54.861Z"),
@@ -479,15 +462,19 @@ func TestGetShipmentReport(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetShipment(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetShipment(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetShipmentReportResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.Code == "" {
t.Errorf("Code cannot be empty")
@@ -495,3 +482,106 @@ func TestGetShipmentReport(t *testing.T) {
}
}
}
func TestIssueOnDiscountedProducts(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"},
`{
"code": "d55f4517-8347-4e24-9d93-d6e736c1c07c"
}`,
},
// 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.Reports().IssueOnDiscountedProducts(ctx)
if err != nil {
t.Error(err)
continue
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
compareJsonResponse(t, test.response, &IssueOnDiscountedProductsResponse{})
if resp.Code == "" {
t.Errorf("Code cannot be empty")
}
}
}
}
func TestGetFBSStocks(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetFBSStocksParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetFBSStocksParams{
Language: "EN",
WarehouseIds: []int64{123},
},
`{
"result": {
"code": "d55f4517-8347-4e24-9d93-d6e736c1c07c"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetFBSStocksParams{},
`{
"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.Reports().GetFBSStocks(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetFBSStocksResponse{})
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

472
ozon/strategies.go Normal file
View File

@@ -0,0 +1,472 @@
package ozon
import (
"context"
"net/http"
core "github.com/diphantxm/ozon-api-client"
)
type Strategies struct {
client *core.Client
}
type ListCompetitorsParams struct {
// Page number from which you want to download the list of competitors.
// The minimum value is 1
Page int64 `json:"page"`
// Maximum number of competitors on the page. Allowed values: 150
Limit int64 `json:"limit"`
}
type ListCompetitorsResponse struct {
core.CommonResponse
// List of competitors
Competitor []ListCompetitorsCompetitor `json:"competitor"`
// Total number of competitors
Total int32 `json:"total"`
}
type ListCompetitorsCompetitor struct {
// Competitor's name
Name string `json:"competitor_name"`
// Competitor identifier
Id int64 `json:"competitor_id"`
}
// Method for getting a list of competitors—sellers with similar products in other online stores and marketplaces
func (c Strategies) ListCompetitors(ctx context.Context, params *ListCompetitorsParams) (*ListCompetitorsResponse, error) {
url := "/v1/pricing-strategy/competitors/list"
resp := &ListCompetitorsResponse{}
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 ListStrategiesParams struct {
// Page number from which you want to download the list of competitors.
// The minimum value is 1
Page int64 `json:"page"`
// Maximum number of competitors on the page. Allowed values: 150
Limit int64 `json:"limit"`
}
type ListStrategiesResponse struct {
core.CommonResponse
// List of strategies
Strategies []ListStrategiesStrategy `json:"strategies"`
// Total number of strategies
Total int32 `json:"total"`
}
type ListStrategiesStrategy struct {
// Strategy identifier
Id string `json:"strategy_id"`
// Strategy name
Name string `json:"strategy_name"`
// Strategy type
Type StrategyType `json:"type"`
// Type of the last strategy change
UpdateType StrategyUpdateType `json:"update_type"`
// Date of last change
UpdatedAt string `json:"updated_at"`
// Number of products in the strategy
ProductsCount int64 `json:"products_count"`
// Number of selected competitors
CompetitorsCount int64 `json:"competitors_count"`
// Strategy status
Enabled bool `json:"enabled"`
}
func (c Strategies) List(ctx context.Context, params *ListStrategiesParams) (*ListStrategiesResponse, error) {
url := "/v1/pricing-strategy/list"
resp := &ListStrategiesResponse{}
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 CreateStrategyParams struct {
// List of competitors
Competitors []CreateStrategyCompetitor `json:"competitors"`
// Strategy name
StrategyName string `json:"strategy_name"`
}
type CreateStrategyCompetitor struct {
// Coefficient by which the minimum price among competitors will be multiplied.
// The allowed range is from 0.5 to 1.2
Coefficient float32 `json:"coefficient"`
// Competitor identifier
CompetitorId int64 `json:"competitor_id"`
}
type CreateStrategyResponse struct {
core.CommonResponse
// Method result
Result CreateStrategyResult `json:"result"`
}
type CreateStrategyResult struct {
// Strategy identifier
StrategyId string `json:"strategy_id"`
}
func (c Strategies) Create(ctx context.Context, params *CreateStrategyParams) (*CreateStrategyResponse, error) {
url := "/v1/pricing-strategy/create"
resp := &CreateStrategyResponse{}
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 InfoStrategyParams struct {
// Strategy identifier
StrategyId string `json:"strategy_id"`
}
type InfoStrategyResponse struct {
core.CommonResponse
// Method result
Result InfoStrategyResult `json:"result"`
}
type InfoStrategyResult struct {
// List of competitors
Competitors []CreateStrategyCompetitor `json:"competitors"`
// Strategy status
Enabled bool `json:"enabled"`
// Strategy name
Name string `json:"name"`
// Strategy type
Type StrategyType `json:"type"`
// Type of the last strategy change
UpdateType StrategyUpdateType `json:"update_type"`
}
func (c Strategies) Info(ctx context.Context, params *InfoStrategyParams) (*InfoStrategyResponse, error) {
url := "/v1/pricing-strategy/info"
resp := &InfoStrategyResponse{}
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 UpdateStrategyParams struct {
// List of competitors
Competitors []CreateStrategyCompetitor `json:"competitors"`
// Product identifier
StrategyId string `json:"strategy_id"`
// Strategy name
StrategyName string `json:"strategy_name"`
}
type UpdateStrategyResponse struct {
core.CommonResponse
}
func (c Strategies) Update(ctx context.Context, params *UpdateStrategyParams) (*UpdateStrategyResponse, error) {
url := "/v1/pricing-strategy/update"
resp := &UpdateStrategyResponse{}
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 AddProductsToStrategyParams struct {
// List of product identifiers. The maximum number is 50
ProductId []int64 `json:"product_id"`
// Product identifier
StrategyId string `json:"strategy_id"`
}
type AddProductsToStrategyResponse struct {
core.CommonResponse
// Method result
Result AddProductsToStrategyResult `json:"result"`
}
type AddProductsToStrategyResult struct {
// Products with errors
Errors []AddProductsToStrategyResultError `json:"errors"`
// Number of products with errors
FailedProductCount int32 `json:"failed_product_count"`
}
type AddProductsToStrategyResultError struct {
// Error code
Code string `json:"code"`
// Error message
Error string `json:"error"`
// Product identifier
ProductId int64 `json:"product_id"`
}
func (c Strategies) AddProducts(ctx context.Context, params *AddProductsToStrategyParams) (*AddProductsToStrategyResponse, error) {
url := "/v1/pricing-strategy/products/add"
resp := &AddProductsToStrategyResponse{}
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 GetStrategiesByProductIdsParams struct {
// List of product identifiers. The maximum number is 50
ProductId []int64 `json:"product_id"`
}
type GetStrategiesByProductIdsResponse struct {
core.CommonResponse
// Method result
Result GetStrategiesByProductIdsResult `json:"result"`
}
type GetStrategiesByProductIdsResult struct {
// Product information
ProductsInfo []GetStrategiesByProductIdsResultProductInfo `json:"products_info"`
}
type GetStrategiesByProductIdsResultProductInfo struct {
// Product identifier
ProductId int64 `json:"product_id"`
// Strategy identifier to which the product is bounded
StrategyId string `json:"strategy_id"`
}
func (c Strategies) GetByProductIds(ctx context.Context, params *GetStrategiesByProductIdsParams) (*GetStrategiesByProductIdsResponse, error) {
url := "/v1/pricing-strategy/strategy-ids-by-product-ids"
resp := &GetStrategiesByProductIdsResponse{}
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 ListProductsInStrategyParams struct {
// Strategy identifier
StrategyId string `json:"strategy_id"`
}
type ListProductsInStrategyResponse struct {
core.CommonResponse
// Method result
Result ListProductsInStrategyResult `json:"result"`
}
type ListProductsInStrategyResult struct {
// Product identifier
ProductId []string `json:"product_id"`
}
func (c Strategies) ListProducts(ctx context.Context, params *ListProductsInStrategyParams) (*ListProductsInStrategyResponse, error) {
url := "/v1/pricing-strategy/products/list"
resp := &ListProductsInStrategyResponse{}
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 GetCompetitorPriceParams struct {
// Product identifier
ProductId int64 `json:"product_id"`
}
type GetCompetitorPriceResponse struct {
core.CommonResponse
// Method result
Result GetCompetitorPriceResult `json:"result"`
}
type GetCompetitorPriceResult struct {
// Product identifier
StrategyId string `json:"strategy_id"`
// true if the product is in the pricing strategy
IsEnabled bool `json:"is_enabled"`
// Price of product in the strategy
StrategyProductPrice int32 `json:"strategy_product_price"`
// Price setting date
PriceDownloadedAt string `json:"price_downloaded_at"`
// Competitor identifier
StrategyCompetitorId int64 `json:"strategy_competitor_id"`
// Link to a competitor's product
StrategyCompetitorProductURL string `json:"strategy_competitor_product_url"`
}
func (c Strategies) GetCompetitorPrice(ctx context.Context, params *GetCompetitorPriceParams) (*GetCompetitorPriceResponse, error) {
url := "/v1/pricing-strategy/product/info"
resp := &GetCompetitorPriceResponse{}
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 RemoveProductsFromStrategyParams struct {
// List of product identifiers. The maximum number is 50
ProductId []int64 `json:"product_id"`
}
type RemoveProductsFromStrategyResponse struct {
core.CommonResponse
// Method result
Result RemoveProductsFromStrategyResult `json:"result"`
}
type RemoveProductsFromStrategyResult struct {
// Number of products with errors
FailedProductCount int32 `json:"failed_product_count"`
}
func (c Strategies) RemoveProducts(ctx context.Context, params *RemoveProductsFromStrategyParams) (*RemoveProductsFromStrategyResponse, error) {
url := "/v1/pricing-strategy/products/delete"
resp := &RemoveProductsFromStrategyResponse{}
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 ChangeStrategyStatusParams struct {
// Strategy status
Enabled bool `json:"enabled"`
// Product identifier
StrategyId string `json:"strategy_id"`
}
type ChangeStrategyStatusResponse struct {
core.CommonResponse
}
func (c Strategies) ChangeStatus(ctx context.Context, params *ChangeStrategyStatusParams) (*ChangeStrategyStatusResponse, error) {
url := "/v1/pricing-strategy/status"
resp := &ChangeStrategyStatusResponse{}
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 RemoveStrategyParams struct {
// Strategy identifier
StrategyId string `json:"strategy_id"`
}
type RemoveStrategyResponse struct {
core.CommonResponse
}
func (c Strategies) Remove(ctx context.Context, params *RemoveStrategyParams) (*RemoveStrategyResponse, error) {
url := "/v1/pricing-strategy/delete"
resp := &RemoveStrategyResponse{}
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
}

717
ozon/strategies_test.go Normal file
View File

@@ -0,0 +1,717 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestListCompetitors(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListCompetitorsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListCompetitorsParams{
Page: 1,
Limit: 20,
},
`{
"competitor": [
{
"competitor_name": "string",
"competitor_id": 0
}
],
"total": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListCompetitorsParams{},
`{
"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.Strategies().ListCompetitors(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListCompetitorsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestListStrategies(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListStrategiesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListStrategiesParams{
Page: 1,
Limit: 20,
},
`{
"strategies": [
{
"strategy_id": "string",
"strategy_name": "string",
"type": "string",
"update_type": "string",
"updated_at": "string",
"products_count": 0,
"competitors_count": 0,
"enabled": true
}
],
"total": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListStrategiesParams{},
`{
"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.Strategies().List(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListStrategiesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestCreateStrategy(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *CreateStrategyParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&CreateStrategyParams{
StrategyName: "New strategy",
Competitors: []CreateStrategyCompetitor{
{
CompetitorId: 1008426,
Coefficient: 1,
},
{
CompetitorId: 204,
Coefficient: 1,
},
{
CompetitorId: 91,
Coefficient: 1,
},
{
CompetitorId: 48,
Coefficient: 1,
},
},
},
`{
"result": {
"strategy_id": "4f3a1d4c-5833-4f04-b69b-495cbc1f6f1c"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&CreateStrategyParams{},
`{
"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.Strategies().Create(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &CreateStrategyResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestInfoStrategy(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *InfoStrategyParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&InfoStrategyParams{
StrategyId: "1",
},
`{
"result": {
"name": "test1",
"enabled": true,
"update_type": "strategyItemsListChanged",
"type": "COMP_PRICE",
"competitors": [
{
"competitor_id": 204,
"coefficient": 1
},
{
"competitor_id": 1008426,
"coefficient": 1
}
]
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&InfoStrategyParams{},
`{
"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.Strategies().Info(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &InfoStrategyResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestUpdateStrategy(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *UpdateStrategyParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&UpdateStrategyParams{
StrategyId: "a3de1826-9c54-40f1-bb6d-1a9e2638b058",
StrategyName: "New Strategy",
Competitors: []CreateStrategyCompetitor{
{
CompetitorId: 1008426,
Coefficient: 1,
},
{
CompetitorId: 204,
Coefficient: 1,
},
{
CompetitorId: 91,
Coefficient: 1,
},
{
CompetitorId: 48,
Coefficient: 1,
},
{
CompetitorId: 45,
Coefficient: 1,
},
},
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&UpdateStrategyParams{},
`{
"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.Strategies().Update(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &UpdateStrategyResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestAddProductsToStrategy(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *AddProductsToStrategyParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&AddProductsToStrategyParams{
ProductId: []int64{29209},
StrategyId: "e29114f0-177d-4160-8d06-2bc528470dda",
},
`{
"result": {
"failed_product_count": 0
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&AddProductsToStrategyParams{},
`{
"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.Strategies().AddProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &AddProductsToStrategyResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetStrategiesByProductIds(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetStrategiesByProductIdsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetStrategiesByProductIdsParams{
ProductId: []int64{29209},
},
`{
"result": {
"products_info": [
{
"product_id": 29209,
"strategy_id": "b7cd30e6-5667-424d-b105-fbec30a52477"
}
]
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetStrategiesByProductIdsParams{},
`{
"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.Strategies().GetByProductIds(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetStrategiesByProductIdsResponse{})
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.ProductsInfo) != len(test.params.ProductId) {
t.Errorf("Length of product ids in request and response are not equal")
}
if len(resp.Result.ProductsInfo) > 0 {
if resp.Result.ProductsInfo[0].ProductId != test.params.ProductId[0] {
t.Errorf("Product ids in request and response are not equal")
}
}
}
}
}
func TestListProductsInStrategy(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListProductsInStrategyParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListProductsInStrategyParams{
StrategyId: "string",
},
`{
"result": {
"product_id": [
"string"
]
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListProductsInStrategyParams{},
`{
"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.Strategies().ListProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListProductsInStrategyResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetCompetitorPrice(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetCompetitorPriceParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetCompetitorPriceParams{
ProductId: 0,
},
`{
"result": {
"strategy_id": "string",
"is_enabled": true,
"strategy_product_price": 0,
"price_downloaded_at": "2022-11-17T15:33:53.936Z",
"strategy_competitor_id": 0,
"strategy_competitor_product_url": "string"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetCompetitorPriceParams{},
`{
"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.Strategies().GetCompetitorPrice(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetCompetitorPriceResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestRemoveProductsFromStrategy(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *RemoveProductsFromStrategyParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&RemoveProductsFromStrategyParams{
ProductId: []int64{0},
},
`{
"result": {
"failed_product_count": 0
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&RemoveProductsFromStrategyParams{},
`{
"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.Strategies().RemoveProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &RemoveProductsFromStrategyResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestChangeStrategyStatus(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ChangeStrategyStatusParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ChangeStrategyStatusParams{
Enabled: true,
StrategyId: "c7516438-7124-4e2c-85d3-ccd92b6b9b65",
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ChangeStrategyStatusParams{},
`{
"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.Strategies().ChangeStatus(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ChangeStatusToResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestRemoveStrategy(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *RemoveStrategyParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&RemoveStrategyParams{
StrategyId: "strategy",
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&RemoveStrategyParams{},
`{
"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.Strategies().Remove(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &RemoveStrategyResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

83
ozon/utils.go Normal file
View File

@@ -0,0 +1,83 @@
package ozon
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
)
func compareJsonResponse(t *testing.T, expectedJSON string, response interface{}) {
err := json.Unmarshal([]byte(expectedJSON), response)
if err != nil {
t.Errorf("got error: %s", err)
return
}
after, err := json.Marshal(response)
if err != nil {
t.Errorf("got error: %s", err)
return
}
var j1, j2 map[string]interface{}
if err := json.NewDecoder(strings.NewReader(expectedJSON)).Decode(&j1); err != nil {
t.Errorf("got error: %s", err)
return
}
if err := json.NewDecoder(bytes.NewReader(after)).Decode(&j2); err != nil {
t.Errorf("got error: %s", err)
return
}
if err := compareJson(j1, j2, ""); err != nil {
t.Errorf("jsons are not equal: %s", err)
return
}
}
func compareJson(expected interface{}, actual interface{}, prefix string) error {
if expected == nil {
return nil
}
expectedType := reflect.TypeOf(expected).Kind()
actualType := reflect.TypeOf(actual).Kind()
if expectedType != actualType {
return fmt.Errorf("type for key %s is different: expected: %s, \ngot: %s", prefix, expectedType, actualType)
}
switch expected.(type) {
case map[string]interface{}:
expectedMap := expected.(map[string]interface{})
actualMap := actual.(map[string]interface{})
for k, v := range expectedMap {
key := fmt.Sprintf("%s.%s", prefix, k)
actualValue, ok := actualMap[k]
if !ok {
return fmt.Errorf("key %s is absent", key)
}
if err := compareJson(v, actualValue, key); err != nil {
return err
}
}
case []interface{}:
expectedSlice := expected.([]interface{})
actualSlice := actual.([]interface{})
for i := range expectedSlice {
key := fmt.Sprintf("%s.%d", prefix, i)
if err := compareJson(expectedSlice[i], actualSlice[i], key); err != nil {
return err
}
}
default:
if !reflect.DeepEqual(expected, actual) {
return fmt.Errorf("value for key %s is different: expected: %s, \ngot: %s", prefix, expected, actual)
}
}
return nil
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"time"
@@ -14,79 +15,83 @@ type Warehouses struct {
type GetListOfWarehousesResponse struct {
core.CommonResponse
Result []struct {
// Trusted acceptance attribute. `true` if trusted acceptance is enabled in the warehouse
HasEntrustedAcceptance bool `json:"has_entrusted_acceptance"`
Result []GetListOfWarehousesResult `json:"result"`
}
// Indication that the warehouse works under the rFBS scheme:
// - true — the warehouse works under the rFBS scheme;
// - false — the warehouse does not work under the rFBS scheme.
IsRFBS bool `json:"is_rfbs"`
type GetListOfWarehousesResult struct {
// Trusted acceptance attribute. `true` if trusted acceptance is enabled in the warehouse
HasEntrustedAcceptance bool `json:"has_entrusted_acceptance"`
// Warehouse name
Name string `json:"name"`
// Indication that the warehouse works under the rFBS scheme:
// - true — the warehouse works under the rFBS scheme;
// - false — the warehouse does not work under the rFBS scheme.
IsRFBS bool `json:"is_rfbs"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
// Warehouse name
Name string `json:"name"`
// Possibility to print an acceptance certificate in advance. `true` if printing in advance is possible
CanPrintActInAdvance bool `json:"can_print_act_in_advance"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
// FBS first mile
FirstMileType struct {
// DropOff point identifier
DropoffPointId string `json:"dropoff_point_id"`
// Possibility to print an acceptance certificate in advance. `true` if printing in advance is possible
CanPrintActInAdvance bool `json:"can_print_act_in_advance"`
// DropOff timeslot identifier
DropoffTimeslotId int64 `json:"dropoff_timeslot_id"`
// FBS first mile
FirstMileType GetListOfWarehousesResultFirstMile `json:"first_mile_type"`
// Indication that the warehouse settings are being updated
FirstMileIsChanging bool `json:"first_mile_is_changing"`
// Indication if there is a limit on the minimum number of orders. `true` if there is such a limit
HasPostingsLimit bool `json:"has_postings_limit"`
// First mile type:
//
// Enum: "DropOff" "Pickup"
// - DropOff
// - Pickup
FirstMileType string `json:"first_mile_type"`
} `json:"first_mile_type"`
// Indication that the warehouse is not working due to quarantine
IsKarantin bool `json:"is_karantin"`
// Indication if there is a limit on the minimum number of orders. `true` if there is such a limit
HasPostingsLimit bool `json:"has_postings_limit"`
// Indication that the warehouse accepts bulky products
IsKGT bool `json:"is_kgt"`
// Indication that the warehouse is not working due to quarantine
IsKarantin bool `json:"is_karantin"`
// Indication that warehouse schedule can be changed
IsTimetableEditable bool `json:"is_timetable_editable"`
// Indication that the warehouse accepts bulky products
IsKGT bool `json:"is_kgt"`
// Minimum limit value: the number of orders that can be brought in one shipment
MinPostingsLimit int32 `json:"min_postings_limit"`
// Indication that warehouse schedule can be changed
IsTimetableEditable bool `json:"is_timetable_editable"`
// Limit value. -1 if there is no limit
PostingsLimit int32 `json:"postings_limit"`
// Minimum limit value: the number of orders that can be brought in one shipment
MinPostingsLimit int32 `json:"min_postings_limit"`
// Number of warehouse working days
MinWorkingDays int64 `json:"min_working_days"`
// Limit value. -1 if there is no limit
PostingsLimit int32 `json:"postings_limit"`
// Warehouse status
Status string `json:"status"`
// Number of warehouse working days
MinWorkingDays int64 `json:"min_working_days"`
// Warehouse working days
WorkingDays []WorkingDay `json:"working_days"`
}
// Warehouse status
Status string `json:"status"`
type GetListOfWarehousesResultFirstMile struct {
// DropOff point identifier
DropoffPointId string `json:"dropoff_point_id"`
// Warehouse working days
WorkingDays []string `json:"working_days"`
} `json:"resulCommonResponse"`
// DropOff timeslot identifier
DropoffTimeslotId int64 `json:"dropoff_timeslot_id"`
// Indication that the warehouse settings are being updated
FirstMileIsChanging bool `json:"first_mile_is_changing"`
// First mile type:
//
// Enum: "DropOff" "Pickup"
// - DropOff
// - Pickup
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
func (c Warehouses) GetListOfWarehouses() (*GetListOfWarehousesResponse, error) {
func (c Warehouses) GetListOfWarehouses(ctx context.Context) (*GetListOfWarehousesResponse, error) {
url := "/v1/warehouse/list"
resp := &GetListOfWarehousesResponse{}
response, err := c.client.Request(http.MethodPost, url, nil, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, nil)
if err != nil {
return nil, err
}
@@ -97,7 +102,7 @@ func (c Warehouses) GetListOfWarehouses() (*GetListOfWarehousesResponse, error)
type GetListOfDeliveryMethodsParams struct {
// 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
Limit int64 `json:"limit"`
@@ -131,50 +136,52 @@ type GetListOfDeliveryMethodsResponse struct {
HasNext bool `json:"has_next"`
// Method result
Result []struct {
// Company identifier
CompanyId int64 `json:"company_id"`
Result []GetListOfDeliveryMethodsResult `json:"result"`
}
// Date and time of delivery method creation
CreatedAt time.Time `json:"created_at"`
type GetListOfDeliveryMethodsResult struct {
// Company identifier
CompanyId int64 `json:"company_id"`
// Time before an order must be packaged
Cutoff string `json:"cutoff"`
// Date and time of delivery method creation
CreatedAt time.Time `json:"created_at"`
// Delivery method identifier
Id int64 `json:"id"`
// Time before an order must be packaged
Cutoff string `json:"cutoff"`
// Delivery method name
Name string `json:"name"`
// Delivery method identifier
Id int64 `json:"id"`
// Delivery service identifier
ProviderId int64 `json:"provider_id"`
// Delivery method name
Name string `json:"name"`
// Delivery method status:
// - NEW—created,
// - EDITED—being edited,
// - ACTIVE—active,
// - DISABLED—inactive
Status string `json:"status"`
// Delivery service identifier
ProviderId int64 `json:"provider_id"`
// Order delivery service identifier
TemplateId int64 `json:"template_id"`
// Delivery method status:
// - NEW—created,
// - EDITED—being edited,
// - ACTIVE—active,
// - DISABLED—inactive
Status string `json:"status"`
// Date and time when the delivery method was last updated
UpdatedAt time.Time `json:"updated_at"`
// Order delivery service identifier
TemplateId int64 `json:"template_id"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
} `json:"result"`
// Date and time when the delivery method was last updated
UpdatedAt time.Time `json:"updated_at"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
// This methods allows you to get list of all delivery methods that can be applied for this warehouse
func (c Warehouses) GetListOfDeliveryMethods(params *GetListOfDeliveryMethodsParams) (*GetListOfDeliveryMethodsResponse, error) {
func (c Warehouses) GetListOfDeliveryMethods(ctx context.Context, params *GetListOfDeliveryMethodsParams) (*GetListOfDeliveryMethodsResponse, error) {
url := "/v1/delivery-method/list"
resp := &GetListOfDeliveryMethodsResponse{}
response, err := c.client.Request(http.MethodPost, url, nil, resp)
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package ozon
import (
"context"
"net/http"
"testing"
@@ -58,11 +59,15 @@ func TestGetListOfWarehouses(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Warehouses().GetListOfWarehouses()
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Warehouses().GetListOfWarehouses(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetListOfWarehousesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
@@ -94,7 +99,7 @@ func TestGetListOfDeliveryMethods(t *testing.T) {
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetListOfDeliveryMethodsParams{
Filter: GetListOfDeliveryMethodsFilter{
Filter: &GetListOfDeliveryMethodsFilter{
WarehouseId: 15588127982000,
},
Limit: 100,
@@ -133,11 +138,15 @@ func TestGetListOfDeliveryMethods(t *testing.T) {
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Warehouses().GetListOfDeliveryMethods(test.params)
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Warehouses().GetListOfDeliveryMethods(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetListOfDeliveryMethodsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}