77 Commits

Author SHA1 Message Date
Kirill
1c0c203097 Update February 14, 2025 (#144) 2025-03-02 01:34:57 +03:00
Kirill
b08f17f3f1 Update February 6, 2025 (#143) 2025-03-02 01:25:59 +03:00
Kirill
4831ad70d6 Update January 30, 2025 (#142) 2025-03-02 01:25:37 +03:00
Kirill
739f672caf Update January 17, 2025 (#141) 2025-03-02 01:18:53 +03:00
Kirill
0fa0986178 Update January 15, 2025 (#140) 2025-03-02 01:07:49 +03:00
Kirill
76e54922fa Update January 13, 2025 (#139) 2025-03-02 00:59:57 +03:00
Kirill
24cc2cbe93 Update December 28, 2024 (#138) 2025-03-02 00:58:09 +03:00
benice2me11
38e8446187 removed endpoint /v2/product/list update to /v3/product/list (#136)
Co-authored-by: ypoqou <benice2me11+ypoqou@gmail.com>
2025-02-14 20:43:09 +03:00
Kirill
77c3cf5462 Carriages: Update December 27, 2024 (#135) 2025-02-09 02:52:04 +03:00
Kirill
bc228dd6e1 Reviews: Update December 27, 2024 (#134) 2025-02-09 00:47:39 +03:00
Kirill
3a67391d71 Remove Endpoints file (#133) 2025-01-23 01:24:43 +03:00
Kirill
040bc23ebc Update December 26, 2024 (#132) 2025-01-23 01:22:28 +03:00
Kirill
804a4f3c2b Update December 24, 2024 (#131) 2025-01-23 01:07:04 +03:00
Kirill
c38e9f19a9 Update December 19, 2024 (#130) 2025-01-23 01:03:20 +03:00
Kirill
7654f5b7c5 Update December 17, 2024 (#129) 2025-01-10 21:21:03 +03:00
Kirill
7f705a4eb5 Update December 11, 2024 (#128) 2025-01-10 20:51:55 +03:00
Kirill
8173450413 Update December 9, 2024 (#127) 2025-01-10 18:07:03 +03:00
Kirill
32bd7748ec Update December 6, 2024 (#126) 2025-01-10 17:51:16 +03:00
Kirill
14986eb627 Update December 4, 2024 (#125) 2025-01-10 17:45:54 +03:00
diPhantxm
3c17a365a3 update 2024-12-27 23:12:33 +03:00
Kirill
7f71ed6545 Update November 29, 2024 (#124) 2024-12-27 22:59:45 +03:00
Kirill
2f4d207726 Update November 22, 2024 (#123) 2024-12-27 22:46:08 +03:00
Kirill
7b5f44ee44 Update November 20, 2024 (#122) 2024-12-27 22:01:51 +03:00
Kirill
bc5f0e52a5 Update November 18, 2024 (#121) 2024-12-27 21:50:21 +03:00
benice2me11
8b8b3bc974 Get description of products (#120) 2024-12-26 15:35:13 +03:00
Kirill
45b0dffe39 Update November 19, 2024 (#118) 2024-12-15 21:09:09 +03:00
Kirill
c7697863db Update November 14, 2024 (#117) 2024-12-15 21:05:21 +03:00
Kirill
8a585d086a Update November 13, 2024 (#116) 2024-12-15 21:03:19 +03:00
diPhantxm
d1fd698368 update 2024-12-15 20:52:23 +03:00
Kirill
6b8b22180a Update November 6, 2024 (#115) 2024-12-15 20:47:07 +03:00
Kirill
f4a09903c7 Update October 31, 2024 (#114) 2024-12-12 00:53:56 +03:00
Kirill
23ca98fedd Update October 24, 2024 (#111) 2024-10-31 16:14:01 +03:00
Kirill
eae6f54e71 Update October 23, 2024 (#110) 2024-10-31 15:59:47 +03:00
Kirill
8a6cd20b95 Update October 22, 2024 (#109)
New method for getting product turnovers
2024-10-31 15:56:04 +03:00
Kirill
1706575a34 Update October 17, 2024 (#108) 2024-10-31 15:47:35 +03:00
diPhantxm
3430ead143 method to list cancellation reasons for fbo scheme 2024-10-31 15:16:11 +03:00
Kirill
2164eff0a6 Update October 16, 2024 (#107) 2024-10-31 15:10:45 +03:00
Kirill
b6af642636 Update October 1, 2024 (#105) 2024-10-02 00:39:04 +03:00
Kirill
67898a4738 Update September 18, 2024 (#106) 2024-10-02 00:38:52 +03:00
Kirill
8c07540d28 Update September 6, 2024 (#104) 2024-10-02 00:38:01 +03:00
Kirill
7c0e18681b Updates August 26, 2024 and August 28, 2024 (#103) 2024-09-10 00:57:30 +03:00
Pireirik
895ef8be52 Change the data structure of chat response according to the updates from Ozon (#102) 2024-09-09 22:09:31 +03:00
Pireirik
ccd3610c76 Change variable type for list of chats (#101) 2024-09-08 19:39:20 +03:00
s1berc0de
090b2afb63 added /v1/description-category/attribute/values/search support (#100) 2024-08-29 17:53:31 +03:00
diPhantxm
823386edf2 remove states 2024-08-09 19:57:56 +03:00
Kirill
5ecf131061 Update August 2, 2024 (#99) 2024-08-09 19:55:36 +03:00
Kirill
dfbb93f438 Update June 27, 2024 (#98) 2024-07-29 04:26:59 +03:00
Zloy_Leshiy
b0c133ba40 replace method from GET to POST for /v1/actions/products/activate request (#97)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-07-22 14:31:28 +03:00
Kirill
680a155294 Update June 18, 2024 (#96) 2024-07-05 01:52:13 +03:00
Kirill
26e2c8b9a7 Update June 10, 2024 (#95) 2024-06-16 02:14:27 +03:00
Zloy_Leshiy
b26dd5bbee Archived fields for product details and fix filter for GetStockInfo (#94)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-06-03 13:40:55 +03:00
Kirill
9ea138003c Update May 24, 2024 (#93) 2024-05-24 15:59:51 +03:00
Kirill
5d62c8ec14 Update May 23, 2024 (#92) 2024-05-24 15:50:40 +03:00
Kirill
2e284d9667 Time format fixes and optional fields (#91) 2024-05-24 15:42:48 +03:00
Kirill
7ffcf3f235 Update 16, 2024 (#90) 2024-05-18 12:07:52 +03:00
Kirill
ad2eb19325 Update May 2, 2024 (#89) 2024-05-06 19:57:44 +03:00
Kirill
99b0a24d48 Tests for Time Format (#87) 2024-04-30 14:15:03 +03:00
Zloy_Leshiy
e5867813ab Custom data type for time representation (#86)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-04-30 13:03:08 +03:00
Kirill
b0e1210f34 Fix type in GetStocksInfoParams.GetStocksInfoFilter #84 (#85) 2024-04-27 13:22:18 +05:00
Kirill
049b02835b Fix response types in GetListOfWarehouses (#83) 2024-04-21 18:41:18 +03:00
Kirill
6cf4ae89e3 Fix empty body (#81) 2024-04-20 18:55:49 +03:00
Kirill
8a6ca3b2eb Update April 16, 2024 (#78) 2024-04-18 16:39:33 +03:00
Kirill
25051ee802 Updates April 8, 2024 and April 9, 2024 (#77) 2024-04-10 17:53:31 +03:00
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
48 changed files with 8649 additions and 2204 deletions

View File

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

View File

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

View File

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

View File

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

172
core.go
View File

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

View File

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

10
go.mod
View File

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

17
go.sum Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -88,6 +88,7 @@ func TestGetCategoryAttributes(t *testing.T) {
`{ `{
"result": [ "result": [
{ {
"category_dependent": true,
"description": "string", "description": "string",
"dictionary_id": 0, "dictionary_id": 0,
"group_id": 0, "group_id": 0,
@@ -202,3 +203,71 @@ func TestGetAttributeDictionary(t *testing.T) {
} }
} }
} }
func TestSearchAttributeDictionary(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SearchAttributeDictionaryParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SearchAttributeDictionaryParams{
AttributeId: 123456,
DescriptionCategoryId: 12,
Value: "34",
Limit: 5,
TypeId: 6,
},
`{
"has_next": true,
"result": [
{
"id": 0,
"info": "string",
"picture": "string",
"value": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SearchAttributeDictionaryParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Categories().SearchAttributesDictionary(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetAttributeDictionaryResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) > int(test.params.Limit) {
t.Errorf("Length of response result is bigger than limit")
}
}
}
}

View File

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

View File

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

View File

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

View File

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

74
ozon/clusters.go Normal file
View File

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

78
ozon/clusters_test.go Normal file
View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@ type FBS struct {
type ListUnprocessedShipmentsParams struct { type ListUnprocessedShipmentsParams struct {
// Sorting direction // Sorting direction
Direction Order `json:"dir"` Direction Order `json:"dir,omitempty"`
// Request filter // Request filter
Filter ListUnprocessedShipmentsFilter `json:"filter"` Filter ListUnprocessedShipmentsFilter `json:"filter"`
@@ -27,38 +27,43 @@ type ListUnprocessedShipmentsParams struct {
// Number of elements that will be skipped in the response. // Number of elements that will be skipped in the response.
// For example, if `offset` = 10, the response will start with the 11th element found // For example, if `offset` = 10, the response will start with the 11th element found
Offset int64 `json:"offset"` Offset int64 `json:"offset,omitempty"`
// Additional fields that should be added to the response // Additional fields that should be added to the response
With ListUnprocessedShipmentsWith `json:"with"` With *ListUnprocessedShipmentsWith `json:"with,omitempty"`
} }
type ListUnprocessedShipmentsFilter struct { type ListUnprocessedShipmentsFilter struct {
// Filter by the time by which the seller should pack the order. Period start. // Filter by the time by which the seller should pack the order. Period start.
// //
// Format: YYYY-MM-DDThh: mm:ss. mcsZ. Example: 2020-03-18T07:34:50.359 Z // Format: YYYY-MM-DDThh: mm:ss. mcsZ. Example: 2020-03-18T07:34:50.359 Z
CutoffFrom time.Time `json:"cutoff_from"` CutoffFrom *core.TimeFormat `json:"cutoff_from,omitempty"`
// Filter by the time by which the seller should pack the order. Period end. // Filter by the time by which the seller should pack the order. Period end.
// //
// Format: YYYY-MM-DDThh: mm:ss. mcsZ. Example: 2020-03-18T07:34:50.359 Z // Format: YYYY-MM-DDThh: mm:ss. mcsZ. Example: 2020-03-18T07:34:50.359 Z
CutoffTo time.Time `json:"cutoff_to"` CutoffTo *core.TimeFormat `json:"cutoff_to,omitempty"`
// Minimum date when shipment should be handed over for delivery // Minimum date when shipment should be handed over for delivery
DeliveringDateFrom time.Time `json:"delivering_date_from"` DeliveringDateFrom *core.TimeFormat `json:"delivering_date_from,omitempty"`
// Maximum date when shipment should be handed over for delivery // Maximum date when shipment should be handed over for delivery
DeliveringDateTo time.Time `json:"delivering_date_to"` DeliveringDateTo *core.TimeFormat `json:"delivering_date_to,omitempty"`
// Delivery method identifier // Delivery method identifier
DeliveryMethodId []int64 `json:"delivery_method_id"` DeliveryMethodId []int64 `json:"delivery_method_id"`
// Specify true to get only MOQ shipments.
//
// The default value is false, the response contains all shipments
IsQuantum bool `json:"is_quantum"`
// Filter for shipments delivered from partner warehouse (FBP). You can pass one of the following values: // Filter for shipments delivered from partner warehouse (FBP). You can pass one of the following values:
// //
// Default value is all. // Default value is all.
// //
// The FBP scheme is available only for sellers from China // The FBP scheme is available only for sellers from China
FBPFilter FBPFilter `json:"fbpFilter"` FBPFilter FBPFilter `json:"fbpFilter" default:"all"`
// Delivery service identifier // Delivery service identifier
ProviderId []int64 `json:"provider_id"` ProviderId []int64 `json:"provider_id"`
@@ -106,6 +111,9 @@ type FBSPosting struct {
// Analytics data // Analytics data
AnalyticsData FBSPostingAnalyticsData `json:"analytics_data"` AnalyticsData FBSPostingAnalyticsData `json:"analytics_data"`
// Available actions and shipment information
AvailableActions []string `json:"available_actions"`
// Shipment barcodes // Shipment barcodes
Barcodes FBSBarcode `json:"barcodes"` Barcodes FBSBarcode `json:"barcodes"`
@@ -146,6 +154,9 @@ type FBSPosting struct {
// Number of the parent shipment which split resulted in the current shipment // Number of the parent shipment which split resulted in the current shipment
ParentPostingNumber string `json:"parent_posting_number"` ParentPostingNumber string `json:"parent_posting_number"`
// Date and time of successful verification of the courier code
PickupCodeVerifiedAt time.Time `json:"pickup_code_verified_at"`
// Shipment number // Shipment number
PostingNumber string `json:"posting_number"` PostingNumber string `json:"posting_number"`
@@ -174,10 +185,48 @@ type FBSPosting struct {
Substatus string `json:"substatus"` Substatus string `json:"substatus"`
// Type of integration with the delivery service // Type of integration with the delivery service
TPLIntegrationType string `json:"tpl_integration_type"` TPLIntegrationType TPLIntegrationType `json:"tpl_integration_type"`
// Shipment tracking number // Shipment tracking number
TrackingNumber string `json:"tracking_number"` TrackingNumber string `json:"tracking_number"`
// Details on shipping rate
Tariffication []FBSPostingTariffication `json:"tariffication"`
// Economy product identifier
QuantumId int64 `json:"quantum_id"`
}
type FBSPostingTariffication struct {
// Current shipping rate as a percentage
CurrentTariffRate float64 `json:"current_tariff_rate"`
// Type of shipping rate adjustment: discount or surcharge
CurrentTariffType string `json:"current_tariff_type"`
// Current amount of discount or surcharge
CurrentTariffCharge string `json:"current_tariff_charge"`
// Currency of the amount
CurrencyTariffCurrencyCode string `json:"current_tariff_charge_currency_code"`
// Percentage by which the shipping rate is adjusted
// after the time specified in the next_tariff_starts_at parameter
NextTariffRate float64 `json:"next_tariff_rate"`
// The adjustment type applied to the shipping rate
// after the time specified in the next_tariff_starts_at parameter:
// discount or surcharge
NextTariffType string `json:"next_tariff_type"`
// Discount or surcharge amount applied during the next shipping rate adjustment step
NextTariffCharge string `json:"next_tariff_charge"`
// Date and time when the new shipping rate is applied
NextTariffStartsAt time.Time `json:"next_tariff_starts_at"`
// New shipping rate currency
NextTariffCurrencyCode string `json:"next_tariff_charge_currency_code"`
} }
type FBSPostingAddressee struct { type FBSPostingAddressee struct {
@@ -191,7 +240,7 @@ type FBSPostingAddressee struct {
} }
type FBSPostingAnalyticsData struct { type FBSPostingAnalyticsData struct {
// Delivery city // Delivery city. Only for rFBS shipments
City string `json:"city"` City string `json:"city"`
// Delivery start date and time // Delivery start date and time
@@ -213,9 +262,9 @@ type FBSPostingAnalyticsData struct {
IsPremium bool `json:"is_premium"` IsPremium bool `json:"is_premium"`
// Payment method // Payment method
PaymentTypeGroupName string `json:"payment_type_group_name"` PaymentTypeGroupName PaymentTypeGroupName `json:"payment_type_group_name"`
// Delivery region // Delivery region. Only for rFBS shipments
Region string `json:"region"` Region string `json:"region"`
// Delivery service // Delivery service
@@ -350,9 +399,6 @@ type FBSCustomer struct {
// Delivery address details // Delivery address details
Address FBSCustomerAddress `json:"address"` Address FBSCustomerAddress `json:"address"`
// Customer e-mail
CustomerEmail string `json:"customer_email"`
// Customer identifier // Customer identifier
CustomerId int64 `json:"customer_id"` CustomerId int64 `json:"customer_id"`
@@ -361,7 +407,7 @@ type FBSCustomer struct {
// Customer phone number. // Customer phone number.
// //
// Returns an empty string // Returns an empty string ""
Phone string `json:"phone"` Phone string `json:"phone"`
} }
@@ -452,6 +498,8 @@ type FinancialDataProduct struct {
CommissionsCurrencyCode string `json:"commissions_currency_code"` CommissionsCurrencyCode string `json:"commissions_currency_code"`
// Services // Services
//
// Deprecated: The parameter is outdated. To get information on accruals, use the `ListTransactions` method
ItemServices MarketplaceServices `json:"item_services"` ItemServices MarketplaceServices `json:"item_services"`
// Currency of your prices. It matches the currency set in the personal account settings // Currency of your prices. It matches the currency set in the personal account settings
@@ -511,9 +559,9 @@ func (c FBS) ListUnprocessedShipments(ctx context.Context, params *ListUnprocess
type GetFBSShipmentsListParams struct { type GetFBSShipmentsListParams struct {
// Sorting direction // Sorting direction
Direction string `json:"direction"` Direction Order `json:"dir,omitempty"`
//Filter // Filter
Filter GetFBSShipmentsListFilter `json:"filter"` Filter GetFBSShipmentsListFilter `json:"filter"`
// Number of shipments in the response: // Number of shipments in the response:
@@ -522,26 +570,31 @@ type GetFBSShipmentsListParams struct {
Limit int64 `json:"limit"` 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 // 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"`
// Additional fields that should be added to the response // Additional fields that should be added to the response
With GetFBSShipmentsListWith `json:"with"` With *GetFBSShipmentsListWith `json:"with,omitempty"`
} }
type GetFBSShipmentsListFilter struct { type GetFBSShipmentsListFilter struct {
// Delivery method identifier // Delivery method identifier
DeliveryMethodId []int64 `json:"delivery_method_id"` DeliveryMethodId []int64 `json:"delivery_method_id"`
// Filter for shipments delivered from partner warehouse (FBP). You can pass one of the following values: // Filter for shipments delivered from partner warehouse (FBP)
// //
// Default value is all. // Default value is all.
// //
// The FBP scheme is available only for sellers from China // The FBP scheme is available only for sellers from China
FBPFilter FBPFilter `json:"fbpFilter"` FBPFilter FBPFilter `json:"fbpFilter" default:"all"`
// Order identifier // Order identifier
OrderId int64 `json:"order_id"` OrderId int64 `json:"order_id"`
// Specify true to get only MOQ shipments.
//
// The default value is false, the response contains all shipments
IsQuantum bool `json:"is_quantum"`
// Delivery service identifier // Delivery service identifier
ProviderId []int64 `json:"provider_id"` ProviderId []int64 `json:"provider_id"`
@@ -564,6 +617,13 @@ type GetFBSShipmentsListFilter struct {
// Warehouse identifier // Warehouse identifier
WarehouseId []int64 `json:"warehouse_id"` WarehouseId []int64 `json:"warehouse_id"`
LastChangedStatusDate GetFBSShipmentsListFilterLastChangeDate `json:"last_changed_status_date"`
}
type GetFBSShipmentsListFilterLastChangeDate struct {
From time.Time `json:"from"`
To time.Time `json:"to"`
} }
type GetFBSShipmentsListWith struct { type GetFBSShipmentsListWith struct {
@@ -625,7 +685,7 @@ type PackOrderParams struct {
PostingNumber string `json:"posting_number"` PostingNumber string `json:"posting_number"`
// Additional information // Additional information
With PackOrderWith `json:"with"` With *PackOrderWith `json:"with,omitempty"`
} }
type PackOrderPackage struct { type PackOrderPackage struct {
@@ -865,7 +925,7 @@ type GetShipmentDataByIdentifierParams struct {
PostingNumber string `json:"posting_number"` PostingNumber string `json:"posting_number"`
// Additional fields that should be added to the response // Additional fields that should be added to the response
With GetShipmentDataByIdentifierWith `json:"with"` With *GetShipmentDataByIdentifierWith `json:"with,omitempty"`
} }
type GetShipmentDataByIdentifierWith struct { type GetShipmentDataByIdentifierWith struct {
@@ -906,6 +966,9 @@ type GetShipmentDataByIdentifierResult struct {
// Analytics data // Analytics data
AnalyticsData GetShipmentDataByIdentifierResultAnalyticsData `json:"analytics_data"` AnalyticsData GetShipmentDataByIdentifierResultAnalyticsData `json:"analytics_data"`
// Available actions and shipment information
AvailableActions []string `json:"available_actions"`
// Shipment barcodes // Shipment barcodes
Barcodes FBSBarcode `json:"barcodes"` Barcodes FBSBarcode `json:"barcodes"`
@@ -956,6 +1019,9 @@ type GetShipmentDataByIdentifierResult struct {
// Shipment number // Shipment number
PostingNumber string `json:"posting_number"` PostingNumber string `json:"posting_number"`
// Date and time of successful verification of the courier code
PickupCodeVerifiedAt time.Time `json:"pickup_code_verified_at"`
// Information on products and their instances. // Information on products and their instances.
// //
// The response contains the field product_exemplars, if the attribute with.product_exemplars = true is passed in the request // The response contains the field product_exemplars, if the attribute with.product_exemplars = true is passed in the request
@@ -967,6 +1033,9 @@ type GetShipmentDataByIdentifierResult struct {
// Delivery service status // Delivery service status
ProviderStatus string `json:"provider_status"` ProviderStatus string `json:"provider_status"`
// Previous sub-status of the shipment
PreviousSubstatus string `json:"previous_substatus"`
// Information on lifting service. Only relevant for bulky products // Information on lifting service. Only relevant for bulky products
// with a delivery by a third-party or integrated service // with a delivery by a third-party or integrated service
PRROption GetShipmentDataByIdentifierResultPRROption `json:"prr_option"` PRROption GetShipmentDataByIdentifierResultPRROption `json:"prr_option"`
@@ -993,6 +1062,9 @@ type GetShipmentDataByIdentifierResult struct {
// Shipment tracking number // Shipment tracking number
TrackingNumber string `json:"tracking_number"` TrackingNumber string `json:"tracking_number"`
// Details on shipping rate
Tariffication []FBSPostingTariffication `json:"tariffication"`
} }
type GetShipmentDataByIdentifierResultAdditionalData struct { type GetShipmentDataByIdentifierResultAdditionalData struct {
@@ -1007,12 +1079,14 @@ type GetShipmentDataByIdentifierResultAddressee struct {
// Recipient name // Recipient name
Name string `json:"name"` Name string `json:"name"`
// Recipient phone number // Recipient phone number.
//
// Returns an empty string ""
Phone string `json:"phone"` Phone string `json:"phone"`
} }
type GetShipmentDataByIdentifierResultAnalyticsData struct { type GetShipmentDataByIdentifierResultAnalyticsData struct {
// Delivery city // Delivery city. Only for rFBS shipments
City string `json:"city"` City string `json:"city"`
// Delivery start date and time // Delivery start date and time
@@ -1035,7 +1109,7 @@ type GetShipmentDataByIdentifierResultAnalyticsData struct {
// Payment method // Payment method
PaymentTypeGroupName string `json:"payment_type_group_name"` PaymentTypeGroupName string `json:"payment_type_group_name"`
// Delivery region // Delivery region. Only for rFBS shipments
Region string `json:"region"` Region string `json:"region"`
// Delivery service // Delivery service
@@ -1062,6 +1136,8 @@ type GetShipmentDataByIdentifierResultCourier struct {
Name string `json:"name"` Name string `json:"name"`
// Courier's phone number // Courier's phone number
//
// Returns an empty string ""
Phone string `json:"phone"` Phone string `json:"phone"`
} }
@@ -1338,38 +1414,6 @@ func (c FBS) ListOfShipmentCertificates(ctx context.Context, params *ListOfShipm
return resp, nil return resp, nil
} }
type SignShipmentCertificateParams struct {
// Certificate identifier
Id int64 `json:"id"`
// Type of shipment certificate:
// - act_of_mismatch — discrepancy certificate,
// - act_of_excess — surplus certificate
DocType string `json:"doc_type"`
}
type SignShipmentCertificateResponse struct {
core.CommonResponse
// Request processing
Result string `json:"result"`
}
// Signs shipment certificates electronically via the electronic documents (ED) system of Ozon logistics
func (c FBS) SignShipmentCertificate(ctx context.Context, params *SignShipmentCertificateParams) (*SignShipmentCertificateResponse, error) {
url := "/v2/posting/fbs/digital/act/document-sign"
resp := &SignShipmentCertificateResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type ChangeStatusToParams struct { type ChangeStatusToParams struct {
// Shipment identifier // Shipment identifier
PostingNumber []string `json:"posting_number"` PostingNumber []string `json:"posting_number"`
@@ -1454,7 +1498,7 @@ func (c FBS) ChangeStatusToSendBySeller(ctx context.Context, params *ChangeStatu
} }
type PassShipmentToShippingParams struct { type PassShipmentToShippingParams struct {
// Shipment identifier // Shipment identifier. The maximum number of values in one request is 100
PostingNumber []string `json:"posting_number"` PostingNumber []string `json:"posting_number"`
} }
@@ -1672,15 +1716,25 @@ type CreateTaskForGeneratingLabelResponse struct {
} }
type CreateTaskForGeneratingLabelResult struct { type CreateTaskForGeneratingLabelResult struct {
Tasks []CreateTaskForGeneratingLabelTask `json:"tasks"`
}
type CreateTaskForGeneratingLabelTask struct {
// Task identifier for labeling generation // Task identifier for labeling generation
TaskId int64 `json:"task_id"` TaskId int64 `json:"task_id"`
// Type of label generation task:
//
// big_label — for a regular label,
// small_label — for a small label
TaskType string `json:"task_type"`
} }
// Method for creating a task for asynchronous labeling generation. // Method for creating a task for asynchronous labeling generation.
// //
// To get labels created as a result of the method, use the /v1/posting/fbs/package-label/get method // To get labels created as a result of the method, use the /v1/posting/fbs/package-label/get method
func (c FBS) CreateTaskForGeneratingLabel(ctx context.Context, params *CreateTaskForGeneratingLabelParams) (*CreateTaskForGeneratingLabelResponse, error) { func (c FBS) CreateTaskForGeneratingLabel(ctx context.Context, params *CreateTaskForGeneratingLabelParams) (*CreateTaskForGeneratingLabelResponse, error) {
url := "/v1/posting/fbs/package-label/create" url := "/v2/posting/fbs/package-label/create"
resp := &CreateTaskForGeneratingLabelResponse{} resp := &CreateTaskForGeneratingLabelResponse{}
@@ -1831,24 +1885,37 @@ type CheckProductItemsDataResponse struct {
// Asynchronous method: // Asynchronous method:
// //
// for checking the availability of product items in the “Chestny ZNAK” labeling system; // for checking the availability of product items in the “Chestny ZNAK” labeling system;
// for saving product items data. // for saving product items data.
// To get the checks results, use the `/v4/fbs/posting/product/exemplar/status` method.
// To get data about created items, use the `/v5/fbs/fbs/posting/product/exemplar/create-or-get` method.
// //
// If necessary, specify the number of the cargo customs declaration in the gtd parameter. // To get the checks results,
// If it is missing, pass the value `is_gtd_absent` = true. // use the /v4/fbs/posting/product/exemplar/status method.
// To get data about created items,
// use the /v5/fbs/fbs/posting/product/exemplar/create-or-get method.
// //
// If you have multiple identical products in a shipment, specify one `product_id` and exemplars array for each product in the shipment. // If necessary, specify the number of the cargo customs declaration
// in the gtd parameter. If it is missing,
// pass the value is_gtd_absent = true.
//
// If you have multiple identical products in a shipment,
// specify one product_id and exemplars array for each product in the shipment.
// //
// Always pass a complete set of product items data. // Always pass a complete set of product items data.
// //
// For example, you have 10 product items in your system. // For example, you have 10 product items in your system.
// You've passed them for checking and saving. // You've passed them for checking and saving.
// Then you added another 60 product items to your system. // Then you added another 60 product items to your system.
// When you pass product items for checking and saving again, pass all of them: both old and newly added. // When you pass product items for checking and saving again,
// pass all of them: both old and newly added.
//
// Unlike /v4/fbs/posting/product/exemplar/set,
// you can pass more item information in the request.
//
// The 200 response code doesn't guarantee that instance data has been received.
// It indicates that a task for adding the information has been created.
// To check the task status, use the /v4/fbs/posting/product/exemplar/status method.
func (c FBS) CheckProductItemsData(ctx context.Context, params *CheckProductItemsDataParams) (*CheckProductItemsDataResponse, error) { func (c FBS) CheckProductItemsData(ctx context.Context, params *CheckProductItemsDataParams) (*CheckProductItemsDataResponse, error) {
url := "/v4/fbs/posting/product/exemplar/set" url := "/v5/fbs/posting/product/exemplar/set"
resp := &CheckProductItemsDataResponse{} resp := &CheckProductItemsDataResponse{}
@@ -2082,10 +2149,16 @@ type PartialPackOrderAdditionalData struct {
Products []PostingProduct `json:"products"` Products []PostingProduct `json:"products"`
} }
// If you pass to the request a part of the products from the shipment, the primary shipment will split into two parts. // If you pass some of the shipped products through the request,
// The primary unassembled shipment will contain some of the products that were not passed to the request. // the primary shipment will split into two parts.
// The primary unassembled shipment will contain some of the products
// that weren't passed to the request.
// //
// The status of the original shipment will only change when the split shipments status changes // Default status of created shipments is awaiting_packaging,
// which indicates that the shipment is awaiting assembly.
//
// The status of the original shipment will only change
// when the split shipments status changes
func (c FBS) PartialPackOrder(ctx context.Context, params *PartialPackOrderParams) (*PartialPackOrderResponse, error) { func (c FBS) PartialPackOrder(ctx context.Context, params *PartialPackOrderParams) (*PartialPackOrderResponse, error) {
url := "/v4/posting/fbs/ship/package" url := "/v4/posting/fbs/ship/package"
@@ -2149,6 +2222,12 @@ type AvailableFreightsListResult struct {
// Number of already packaged shipments // Number of already packaged shipments
MandatoryPackagedCount int32 `json:"mandatory_packaged_count"` MandatoryPackagedCount int32 `json:"mandatory_packaged_count"`
// Recommended local time of shipping to the pick-up point
RecommendedTimeLocal string `json:"recommended_time_local"`
// Time zone offset of the recommended shipping time from UTC-0 in minutes
RecommendedTimeUTCOffset int32 `json:"recommended_time_utc_offset_in_minutes"`
// Delivery service icon link // Delivery service icon link
TPLProviderIconURL string `json:"tpl_provider_icon_url"` TPLProviderIconURL string `json:"tpl_provider_icon_url"`
@@ -2486,20 +2565,11 @@ type CancelSendingResponse struct {
// Use this method if you cannot send some of the products from the shipment. // Use this method if you cannot send some of the products from the shipment.
// //
// If you are using the rFBS scheme, you have the following cancellation reason identifiers (`cancel_reason_id`) available: // To get the cancel_reason_id cancellation reason identifiers
// // when working with the FBS or rFBS schemes,
// 352—product is out of stock; // use the /v2/posting/fbs/cancel-reason/list method.
// 400—only defective products left;
// 401—cancellation from arbitration;
// 402—other reason;
// 665—the customer did not pick the order;
// 666—delivery is not available in the region;
// 667—order was lost by the delivery service.
// The last 4 reasons are available for shipments in the "Delivering" and "Courier on the way" statuses.
// //
// You can't cancel presumably delivered orders. // You can't cancel presumably delivered orders.
//
// If `cancel_reason_id` parameter value is 402, fill the `cancel_reason_message` field.
func (c FBS) CancelSending(ctx context.Context, params *CancelSendingParams) (*CancelSendingResponse, error) { func (c FBS) CancelSending(ctx context.Context, params *CancelSendingParams) (*CancelSendingResponse, error) {
url := "/v2/posting/fbs/product/cancel" url := "/v2/posting/fbs/product/cancel"
@@ -2869,6 +2939,8 @@ type CreateOrGetProductExemplarResponse struct {
} }
// Method returns the created items data passed in the `/v5/fbs/posting/product/exemplar/set` method. // Method returns the created items data passed in the `/v5/fbs/posting/product/exemplar/set` method.
//
// Use this method to get the `exemplar_id`
func (c FBS) CreateOrGetProductExemplar(ctx context.Context, params *CreateOrGetProductExemplarParams) (*CreateOrGetProductExemplarResponse, error) { func (c FBS) CreateOrGetProductExemplar(ctx context.Context, params *CreateOrGetProductExemplarParams) (*CreateOrGetProductExemplarResponse, error) {
url := "/v5/fbs/posting/product/exemplar/create-or-get" url := "/v5/fbs/posting/product/exemplar/create-or-get"
@@ -2882,3 +2954,359 @@ func (c FBS) CreateOrGetProductExemplar(ctx context.Context, params *CreateOrGet
return resp, nil return resp, nil
} }
type GetCarriageParams struct {
CarriageId int64 `json:"carriage_id"`
}
type GetCarriageResponse struct {
core.CommonResponse
// Acceptance certificate type for FBS sellers
ActType string `json:"act_type"`
// Pass identifiers for the freight
ArrivalPassIds []string `json:"arrival_pass_ids"`
// List of available actions on the freight
AvailableActions []string `json:"available_actions"`
// Cancel availability
CancelAvailability GetCarriageCancelAvailability `json:"cancel_availability"`
// Freight identifier
CarriageId int64 `json:"carriage_id"`
// Company identifier
CompanyId int64 `json:"company_id"`
// Number of package units
ContainersCount int32 `json:"containers_count"`
// Date and time of freight creation
CreatedAt time.Time `json:"created_at"`
// Delivery method identifier
DeliveryMethodId int64 `json:"delivery_method_id"`
// Shipping date
DepartureDate string `json:"departure_date"`
// First mile type
FirstMileType string `json:"first_mile_type"`
// true if there are shipments subject to shipping that are not in the current freight
HasPostingsForNextCarriage bool `json:"has_postings_for_next_carriage"`
// Delivery service integration type
IntegrationType string `json:"integration_type"`
// true if you already printed shipping labels
IsContainerLabelPrinted bool `json:"is_container_label_printed"`
// true if the freight is partial
IsPartial bool `json:"is_partial"`
// Serial number of the partial freight
PartialNum int64 `json:"partial_num"`
// The number of retries to create a freight
RetryCount int32 `json:"retry_count"`
// Freight status
Status GetCarriageStatus `json:"status"`
// Delivery method identifier
TPLProviderId int64 `json:"tpl_provider_id"`
// Date and time when the freight was last updated
UpdatedAt time.Time `json:"updated_at"`
// Warehouse identifier
WarehouseId int64 `json:"warehouse_id"`
}
type GetCarriageCancelAvailability struct {
// true if the freight can be cancelled
IsCancelAvailable bool `json:"is_cancel_available"`
// Reason why freight can't be cancelled
Reason string `json:"reason"`
}
func (c FBS) GetCarriage(ctx context.Context, params *GetCarriageParams) (*GetCarriageResponse, error) {
url := "/v1/carriage/get"
resp := &GetCarriageResponse{}
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 GetCancellationReasonsResponse struct {
core.CommonResponse
// Method result
Result []GetCancellationReasonsResult `json:"result"`
}
type GetCancellationReasonsResult struct {
// Cancellation reasons identifier
Id int64 `json:"id"`
// Shipment cancellation result. true if the request is available for cancellation
IsAvailableForCancellation bool `json:"is_available_for_cancellation"`
// Category name
Title string `json:"title"`
// Shipment cancellation initiator
TypeId string `json:"type_id"`
}
func (c FBS) GetCancellationReasons(ctx context.Context) (*GetCancellationReasonsResponse, error) {
url := "/v1/posting/fbo/cancel-reason/list"
resp := &GetCancellationReasonsResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type SetShippingDateParams struct {
// New shipping date
NewCutoffDate time.Time `json:"new_cutoff_date"`
// Shipment number
PostingNumber string `json:"posting_number"`
}
type SetShippingDateResponse struct {
core.CommonResponse
// true if the new date is set
Result bool `json:"result"`
}
func (c FBS) SetShippingDate(ctx context.Context, params *SetShippingDateParams) (*SetShippingDateResponse, error) {
url := "/v1/posting/cutoff/set"
resp := &SetShippingDateResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type SplitOrderParams struct {
// Shipment number
PostingNumber string `json:"posting_number"`
// Shipments list the order will be split into. You can split one order per one request
Postings []SplitOrderParamPosting `json:"postings"`
}
type SplitOrderParamPosting struct {
Products []SplitOrderPostingProduct `json:"products"`
}
type SplitOrderResponse struct {
core.CommonResponse
// Original shipment details
ParentPosting SplitOrderPosting `json:"parent_posting"`
// List of shipments the order was split into
Postings []SplitOrderPosting `json:"postings"`
}
type SplitOrderPosting struct {
// Shipment number
PostingNumber string `json:"posting_number"`
// List of products in the shipment
Products []SplitOrderPostingProduct `json:"products"`
}
type SplitOrderPostingProduct struct {
// FBS product identifier in the Ozon system, SKU
ProductId int64 `json:"product_id"`
// Product quantity
Quantity int64 `json:"quantity"`
}
func (c FBS) SplitOrder(ctx context.Context, params *SplitOrderParams) (*SplitOrderResponse, error) {
url := "/v1/posting/fbs/split"
resp := &SplitOrderResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type ListUnpaidProductsParams struct {
// Cursor for the next data sample
Cursor string `json:"cursor"`
// Number of values in the response
Limit int32 `json:"limit,omitempty"`
}
type ListUnpaidProductsResponse struct {
core.CommonResponse
Products []UnpaidProduct `json:"products"`
// Cursor for the next data sample
Cursor string `json:"cursor"`
}
type UnpaidProduct struct {
// Product identifier
ProductId int64 `json:"product_id"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product quantity, pcs
Quantity int32 `json:"quantity"`
// Product name
Name string `json:"name"`
// Link to product image
ImageURL string `json:"image_url"`
}
func (c FBS) ListUnpaidProducts(ctx context.Context, params *ListUnpaidProductsParams) (*ListUnpaidProductsResponse, error) {
url := "/v1/posting/unpaid-legal/product/list"
resp := &ListUnpaidProductsResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type ChangeShipmentCompositionParams struct {
// Freight identifier
CarriageId int64 `json:"carriage_id"`
// Current list of shipments
PostingNumbers []string `json:"posting_numbers"`
}
type ChangeShipmentCompositionResponse struct {
core.CommonResponse
Result []ChangeShipmentCompositionResult `json:"result"`
}
type ChangeShipmentCompositionResult struct {
// Error message
Error string `json:"error"`
// Shipment number
PostingNumber string `json:"posting_number"`
// Request processing result. true if the request was executed successfully
Result bool `json:"result"`
}
// Method overwrites the list of orders in the shipment. Pass only orders in the awaiting_deliver status and ones which are ready for shipping.
func (c FBS) ChangeShipmentComposition(ctx context.Context, params *ChangeShipmentCompositionParams) (*ChangeShipmentCompositionResponse, error) {
url := "/v1/carriage/set-postings"
resp := &ChangeShipmentCompositionResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type DeleteShipmentParams struct {
// Freight identifier
CarriageId int64 `json:"carriage_id"`
}
type DeleteShipmentResponse struct {
core.CommonResponse
// Error message
Error string `json:"error"`
// Carriage status
Status string `json:"carriage_status"`
}
func (c FBS) DeleteShipment(ctx context.Context, params *DeleteShipmentParams) (*DeleteShipmentResponse, error) {
url := "/v1/carriage/cancel"
resp := &DeleteShipmentResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type VerifyCourierCodeParams struct {
// Courier code
PickupCode string `json:"pickup_code"`
// Shipment number
PostingNumber string `json:"posting_number"`
}
type VerifyCourierCodeResponse struct {
core.CommonResponse
// true, if the code is correct
Valid bool `json:"valid"`
}
// Use this method to verify the courier code when handing over realFBS Express shipments
func (c FBS) VerifyCourierCode(ctx context.Context, params *VerifyCourierCodeParams) (*VerifyCourierCodeResponse, error) {
url := "/v1/posting/fbs/pick-up-code/verify"
resp := &VerifyCourierCodeResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}

View File

@@ -24,12 +24,12 @@ func TestListUnprocessedShipments(t *testing.T) {
&ListUnprocessedShipmentsParams{ &ListUnprocessedShipmentsParams{
Direction: "ASC", Direction: "ASC",
Filter: ListUnprocessedShipmentsFilter{ Filter: ListUnprocessedShipmentsFilter{
CutoffFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-08-24T14:15:22Z"), CutoffFrom: core.NewTimeFormat(core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-08-24T14:15:22Z"), "2006-01-02T15:04:05Z"),
CutoffTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-08-31T14:15:22Z"), CutoffTo: core.NewTimeFormat(core.TimeFromString(t, "2006-01-02T15:04:05Z", "2021-08-31T14:15:22Z"), "2006-01-02T15:04:05Z"),
Status: "awaiting_packaging", Status: "awaiting_packaging",
}, },
Limit: 100, Limit: 100,
With: ListUnprocessedShipmentsWith{ With: &ListUnprocessedShipmentsWith{
AnalyticsData: true, AnalyticsData: true,
Barcodes: true, Barcodes: true,
FinancialData: true, FinancialData: true,
@@ -43,6 +43,7 @@ func TestListUnprocessedShipments(t *testing.T) {
"posting_number": "23713478-0018-3", "posting_number": "23713478-0018-3",
"order_id": 559293114, "order_id": 559293114,
"order_number": "33713378-0051", "order_number": "33713378-0051",
"pickup_code_verified_at": "2025-01-17T11:03:00.124Z",
"status": "awaiting_packaging", "status": "awaiting_packaging",
"delivery_method": { "delivery_method": {
"id": 15110442724000, "id": 15110442724000,
@@ -66,6 +67,7 @@ func TestListUnprocessedShipments(t *testing.T) {
"cancellation_initiator": "" "cancellation_initiator": ""
}, },
"customer": null, "customer": null,
"quantum_id": 0,
"products": [ "products": [
{ {
"currency_code": "RUB", "currency_code": "RUB",
@@ -146,7 +148,20 @@ func TestListUnprocessedShipments(t *testing.T) {
"requirements": { "requirements": {
"products_requiring_gtd": [], "products_requiring_gtd": [],
"products_requiring_country": [] "products_requiring_country": []
} },
"tariffication": [
{
"current_tariff_rate": 0,
"current_tariff_type": "",
"current_tariff_charge": "",
"current_tariff_charge_currency_code": "",
"next_tariff_rate": 0,
"next_tariff_type": "",
"next_tariff_charge": "",
"next_tariff_starts_at": "2023-11-13T08:05:57.657Z",
"next_tariff_charge_currency_code": ""
}
]
} }
], ],
"count": 55 "count": 55
@@ -210,7 +225,7 @@ func TestGetFBSShipmentsList(t *testing.T) {
}, },
Limit: 0, Limit: 0,
Offset: 0, Offset: 0,
With: GetFBSShipmentsListWith{ With: &GetFBSShipmentsListWith{
AnalyticsData: true, AnalyticsData: true,
FinancialData: true, FinancialData: true,
Translit: true, Translit: true,
@@ -224,6 +239,7 @@ func TestGetFBSShipmentsList(t *testing.T) {
"posting_number": "05708065-0029-1", "posting_number": "05708065-0029-1",
"order_id": 680420041, "order_id": 680420041,
"order_number": "05708065-0029", "order_number": "05708065-0029",
"pickup_code_verified_at": "2025-01-17T10:59:26.614Z",
"status": "awaiting_deliver", "status": "awaiting_deliver",
"substatus": "posting_awaiting_passport_data", "substatus": "posting_awaiting_passport_data",
"delivery_method": { "delivery_method": {
@@ -264,16 +280,30 @@ func TestGetFBSShipmentsList(t *testing.T) {
"analytics_data": null, "analytics_data": null,
"financial_data": null, "financial_data": null,
"is_express": false, "is_express": false,
"quantum_id": 0,
"requirements": { "requirements": {
"products_requiring_gtd": [], "products_requiring_gtd": [],
"products_requiring_country": [], "products_requiring_country": [],
"products_requiring_mandatory_mark": [] "products_requiring_mandatory_mark": []
} },
"tariffication": [
{
"current_tariff_rate": 0,
"current_tariff_type": "",
"current_tariff_charge": "",
"current_tariff_charge_currency_code": "",
"next_tariff_rate": 0,
"next_tariff_type": "",
"next_tariff_charge": "",
"next_tariff_starts_at": "2023-11-13T08:05:57.657Z",
"next_tariff_charge_currency_code": ""
}
]
} }
], ],
"has_next": true "has_next": true
} }
}`, }`,
}, },
// Test No Client-Id or Api-Key // Test No Client-Id or Api-Key
{ {
@@ -330,7 +360,7 @@ func TestPackOrder(t *testing.T) {
}, },
}, },
PostingNumber: "89491381-0072-1", PostingNumber: "89491381-0072-1",
With: PackOrderWith{ With: &PackOrderWith{
AdditionalData: true, AdditionalData: true,
}, },
}, },
@@ -556,14 +586,17 @@ func TestGetShipmentDataByIdentifier(t *testing.T) {
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetShipmentDataByIdentifierParams{ &GetShipmentDataByIdentifierParams{
PostingNumber: "57195475-0050-3", PostingNumber: "57195475-0050-3",
With: GetShipmentDataByIdentifierWith{}, With: &GetShipmentDataByIdentifierWith{},
}, },
`{ `{
"result": { "result": {
"posting_number": "57195475-0050-3", "posting_number": "57195475-0050-3",
"order_id": 438764970, "order_id": 438764970,
"order_number": "57195475-0050", "order_number": "57195475-0050",
"pickup_code_verified_at": "2025-01-17T11:04:59.958Z",
"status": "awaiting_packaging", "status": "awaiting_packaging",
"substatus": "posting_awaiting_passport_data",
"previous_substatus": "posting_transferring_to_delivery",
"delivery_method": { "delivery_method": {
"id": 18114520187000, "id": 18114520187000,
"name": "Ozon Логистика самостоятельно, Москва", "name": "Ozon Логистика самостоятельно, Москва",
@@ -615,7 +648,20 @@ func TestGetShipmentDataByIdentifier(t *testing.T) {
"products_requiring_gtd": [], "products_requiring_gtd": [],
"products_requiring_country": [] "products_requiring_country": []
}, },
"product_exemplars": null "product_exemplars": null,
"tariffication": [
{
"current_tariff_rate": 0,
"current_tariff_type": "",
"current_tariff_charge": "",
"current_tariff_charge_currency_code": "",
"next_tariff_rate": 0,
"next_tariff_type": "",
"next_tariff_charge": "",
"next_tariff_starts_at": "2023-11-13T08:05:57.657Z",
"next_tariff_charge_currency_code": ""
}
]
} }
}`, }`,
}, },
@@ -836,62 +882,6 @@ func TestListOfShipmentCertificates(t *testing.T) {
} }
} }
func TestSignShipmentCertificate(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SignShipmentCertificateParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SignShipmentCertificateParams{
Id: 900000250859000,
DocType: "act_of_mismatch",
},
`{
"result": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SignShipmentCertificateParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().SignShipmentCertificate(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &SignShipmentCertificateResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result == "" {
t.Errorf("Result cannot be empty")
}
}
}
}
func TestChangeStatusTo(t *testing.T) { func TestChangeStatusTo(t *testing.T) {
t.Parallel() t.Parallel()
@@ -1278,7 +1268,16 @@ func TestCreateTaskForGeneratingLabel(t *testing.T) {
}, },
`{ `{
"result": { "result": {
"task_id": 5819327210249 "tasks": [
{
"task_id": 5819327210248,
"task_type": "big_label"
},
{
"task_id": 5819327210249,
"task_type": "small_label"
}
]
} }
}`, }`,
}, },
@@ -1310,7 +1309,7 @@ func TestCreateTaskForGeneratingLabel(t *testing.T) {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
} }
if resp.StatusCode == http.StatusOK { if resp.StatusCode == http.StatusOK {
if resp.Result.TaskId == 0 { if len(resp.Result.Tasks) != 0 && resp.Result.Tasks[0].TaskId == 0 {
t.Errorf("Task id cannot be 0") t.Errorf("Task id cannot be 0")
} }
} }
@@ -1873,6 +1872,8 @@ func TestAvailableFreightsList(t *testing.T) {
"has_entrusted_acceptance": true, "has_entrusted_acceptance": true,
"mandatory_postings_count": 0, "mandatory_postings_count": 0,
"mandatory_packaged_count": 0, "mandatory_packaged_count": 0,
"recommended_time_local": "string",
"recommended_time_utc_offset_in_minutes": 0,
"tpl_provider_icon_url": "string", "tpl_provider_icon_url": "string",
"tpl_provider_name": "string", "tpl_provider_name": "string",
"warehouse_city": "string", "warehouse_city": "string",
@@ -2919,3 +2920,509 @@ func TestCreateOrGetProductExemplar(t *testing.T) {
} }
} }
} }
func TestGetCarriage(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetCarriageParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetCarriageParams{
CarriageId: 15,
},
`{
"act_type": "string",
"arrival_pass_ids": [
"string"
],
"available_actions": [
"string"
],
"cancel_availability": {
"is_cancel_available": true,
"reason": "string"
},
"carriage_id": 15,
"company_id": 0,
"containers_count": 0,
"created_at": "2019-08-24T14:15:22Z",
"delivery_method_id": 0,
"departure_date": "string",
"first_mile_type": "string",
"has_postings_for_next_carriage": true,
"integration_type": "string",
"is_container_label_printed": true,
"is_partial": true,
"partial_num": 0,
"retry_count": 0,
"status": "string",
"tpl_provider_id": 0,
"updated_at": "2019-08-24T14:15:22Z",
"warehouse_id": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetCarriageParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().GetCarriage(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetCarriageResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.CarriageId != test.params.CarriageId {
t.Errorf("carriage id in request and response should be equal")
}
}
}
func TestGetCancellationReasons(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
`{
"result": [
{
"id": 352,
"title": "The products ran out at the seller's warehouse",
"type_id": "seller",
"is_available_for_cancellation": true
},
{
"id": 401,
"title": "Seller rejects arbitration",
"type_id": "seller",
"is_available_for_cancellation": false
},
{
"id": 402,
"title": "Other (seller's fault)",
"type_id": "seller",
"is_available_for_cancellation": true
},
{
"id": 666,
"title": "Return from the delivery service: there is no delivery to the specified region",
"type_id": "seller",
"is_available_for_cancellation": false
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().GetCancellationReasons(ctx)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetCancellationReasonsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestSetShippingDate(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SetShippingDateParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SetShippingDateParams{
NewCutoffDate: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2019-08-24T14:15:22Z"),
},
`{
"result": true
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SetShippingDateParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().SetShippingDate(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &SetShippingDateResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestSplitOrder(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *SplitOrderParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&SplitOrderParams{
PostingNumber: "string",
Postings: []SplitOrderParamPosting{
{
Products: []SplitOrderPostingProduct{
{
ProductId: 1,
Quantity: 1,
},
},
},
},
},
`{
"parent_posting": {
"posting_number": "string",
"products": [
{
"product_id": 0,
"quantity": 0
}
]
},
"postings": [
{
"posting_number": "string",
"products": [
{
"product_id": 0,
"quantity": 0
}
]
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&SplitOrderParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().SplitOrder(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &SplitOrderResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestListUnpaidProducts(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ListUnpaidProductsParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ListUnpaidProductsParams{
Cursor: "hCGiPPopcBFMgMErdzaCEpzQfinuPyEhUoSmBMADuoFAhBjXeA==",
Limit: 1000,
},
`{
"products": [
{
"product_id": 145123054,
"offer_id": "10032",
"quantity": 1,
"name": "Телевизор LG",
"image_url": "https://cdn1.ozon.ru/multimedia/10741275.jpg"
}
],
"cursor": "hCGiPPopcBFMgMErdzaCEpzQfinuPyEhUoSmBMADuoFAhBjXeA=="
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ListUnpaidProductsParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().ListUnpaidProducts(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ListUnpaidProductsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestChangeShipmentComposition(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *ChangeShipmentCompositionParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&ChangeShipmentCompositionParams{
CarriageId: 10,
PostingNumbers: []string{"something"},
},
`{
"result": [
{
"error": "string",
"posting_number": "something",
"result": true
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&ChangeShipmentCompositionParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().ChangeShipmentComposition(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &ChangeShipmentCompositionResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) > 0 {
if resp.Result[0].PostingNumber != test.params.PostingNumbers[0] {
t.Errorf("posting numbers are different. Expected: %s, got: %s", test.params.PostingNumbers[0], resp.Result[0].PostingNumber)
}
}
}
}
}
func TestDeleteShipment(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *DeleteShipmentParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&DeleteShipmentParams{
CarriageId: 10,
},
`{
"error": "string",
"carriage_status": "string"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&DeleteShipmentParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().DeleteShipment(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &DeleteShipmentResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestVerifyCourierCode(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *VerifyCourierCodeParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&VerifyCourierCodeParams{
PickupCode: "string",
PostingNumber: "string",
},
`{
"valid": true
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&VerifyCourierCodeParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.FBS().VerifyCourierCode(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &VerifyCourierCodeResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,6 +41,10 @@ type Client struct {
certificates *Certificates certificates *Certificates
strategies *Strategies strategies *Strategies
barcodes *Barcodes barcodes *Barcodes
passes *Passes
clusters *Clusters
quants *Quants
reviews *Reviews
} }
func (c Client) Analytics() *Analytics { func (c Client) Analytics() *Analytics {
@@ -119,6 +123,22 @@ func (c Client) Barcodes() *Barcodes {
return c.barcodes return c.barcodes
} }
func (c Client) Passes() *Passes {
return c.passes
}
func (c Client) Clusters() *Clusters {
return c.clusters
}
func (c Client) Quants() *Quants {
return c.quants
}
func (c Client) Reviews() *Reviews {
return c.reviews
}
type ClientOption func(c *ClientOptions) type ClientOption func(c *ClientOptions)
func WithHttpClient(httpClient core.HttpClient) ClientOption { func WithHttpClient(httpClient core.HttpClient) ClientOption {
@@ -182,6 +202,10 @@ func NewClient(opts ...ClientOption) *Client {
certificates: &Certificates{client: coreClient}, certificates: &Certificates{client: coreClient},
strategies: &Strategies{client: coreClient}, strategies: &Strategies{client: coreClient},
barcodes: &Barcodes{client: coreClient}, barcodes: &Barcodes{client: coreClient},
passes: &Passes{client: coreClient},
clusters: &Clusters{client: coreClient},
quants: &Quants{client: coreClient},
reviews: &Reviews{client: coreClient},
} }
} }
@@ -209,5 +233,9 @@ func NewMockClient(handler http.HandlerFunc) *Client {
certificates: &Certificates{client: coreClient}, certificates: &Certificates{client: coreClient},
strategies: &Strategies{client: coreClient}, strategies: &Strategies{client: coreClient},
barcodes: &Barcodes{client: coreClient}, barcodes: &Barcodes{client: coreClient},
passes: &Passes{client: coreClient},
clusters: &Clusters{client: coreClient},
quants: &Quants{client: coreClient},
reviews: &Reviews{client: coreClient},
} }
} }

302
ozon/pass.go Normal file
View File

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

431
ozon/pass_test.go Normal file
View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

356
ozon/quants.go Normal file
View File

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

303
ozon/quants_test.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

334
ozon/reviews.go Normal file
View File

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

416
ozon/reviews_test.go Normal file
View File

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

View File

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

View File

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

View File

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