129 Commits

Author SHA1 Message Date
diPhantxm
dcf366d7d4 update 2025-03-02 15:35:05 +03:00
diPhantxm
f5d2d0197b update 2025-03-02 15:28:41 +03:00
Kirill
bd280b54f4 Update February 17, 2025 (#146) 2025-03-02 15:11:00 +03:00
Kirill
1c0c203097 Update February 14, 2025 (#144) 2025-03-02 01:34:57 +03:00
Kirill
b08f17f3f1 Update February 6, 2025 (#143) 2025-03-02 01:25:59 +03:00
Kirill
4831ad70d6 Update January 30, 2025 (#142) 2025-03-02 01:25:37 +03:00
Kirill
739f672caf Update January 17, 2025 (#141) 2025-03-02 01:18:53 +03:00
Kirill
0fa0986178 Update January 15, 2025 (#140) 2025-03-02 01:07:49 +03:00
Kirill
76e54922fa Update January 13, 2025 (#139) 2025-03-02 00:59:57 +03:00
Kirill
24cc2cbe93 Update December 28, 2024 (#138) 2025-03-02 00:58:09 +03:00
benice2me11
38e8446187 removed endpoint /v2/product/list update to /v3/product/list (#136)
Co-authored-by: ypoqou <benice2me11+ypoqou@gmail.com>
2025-02-14 20:43:09 +03:00
Kirill
77c3cf5462 Carriages: Update December 27, 2024 (#135) 2025-02-09 02:52:04 +03:00
Kirill
bc228dd6e1 Reviews: Update December 27, 2024 (#134) 2025-02-09 00:47:39 +03:00
Kirill
3a67391d71 Remove Endpoints file (#133) 2025-01-23 01:24:43 +03:00
Kirill
040bc23ebc Update December 26, 2024 (#132) 2025-01-23 01:22:28 +03:00
Kirill
804a4f3c2b Update December 24, 2024 (#131) 2025-01-23 01:07:04 +03:00
Kirill
c38e9f19a9 Update December 19, 2024 (#130) 2025-01-23 01:03:20 +03:00
Kirill
7654f5b7c5 Update December 17, 2024 (#129) 2025-01-10 21:21:03 +03:00
Kirill
7f705a4eb5 Update December 11, 2024 (#128) 2025-01-10 20:51:55 +03:00
Kirill
8173450413 Update December 9, 2024 (#127) 2025-01-10 18:07:03 +03:00
Kirill
32bd7748ec Update December 6, 2024 (#126) 2025-01-10 17:51:16 +03:00
Kirill
14986eb627 Update December 4, 2024 (#125) 2025-01-10 17:45:54 +03:00
diPhantxm
3c17a365a3 update 2024-12-27 23:12:33 +03:00
Kirill
7f71ed6545 Update November 29, 2024 (#124) 2024-12-27 22:59:45 +03:00
Kirill
2f4d207726 Update November 22, 2024 (#123) 2024-12-27 22:46:08 +03:00
Kirill
7b5f44ee44 Update November 20, 2024 (#122) 2024-12-27 22:01:51 +03:00
Kirill
bc5f0e52a5 Update November 18, 2024 (#121) 2024-12-27 21:50:21 +03:00
benice2me11
8b8b3bc974 Get description of products (#120) 2024-12-26 15:35:13 +03:00
Kirill
45b0dffe39 Update November 19, 2024 (#118) 2024-12-15 21:09:09 +03:00
Kirill
c7697863db Update November 14, 2024 (#117) 2024-12-15 21:05:21 +03:00
Kirill
8a585d086a Update November 13, 2024 (#116) 2024-12-15 21:03:19 +03:00
diPhantxm
d1fd698368 update 2024-12-15 20:52:23 +03:00
Kirill
6b8b22180a Update November 6, 2024 (#115) 2024-12-15 20:47:07 +03:00
Kirill
f4a09903c7 Update October 31, 2024 (#114) 2024-12-12 00:53:56 +03:00
Kirill
23ca98fedd Update October 24, 2024 (#111) 2024-10-31 16:14:01 +03:00
Kirill
eae6f54e71 Update October 23, 2024 (#110) 2024-10-31 15:59:47 +03:00
Kirill
8a6cd20b95 Update October 22, 2024 (#109)
New method for getting product turnovers
2024-10-31 15:56:04 +03:00
Kirill
1706575a34 Update October 17, 2024 (#108) 2024-10-31 15:47:35 +03:00
diPhantxm
3430ead143 method to list cancellation reasons for fbo scheme 2024-10-31 15:16:11 +03:00
Kirill
2164eff0a6 Update October 16, 2024 (#107) 2024-10-31 15:10:45 +03:00
Kirill
b6af642636 Update October 1, 2024 (#105) 2024-10-02 00:39:04 +03:00
Kirill
67898a4738 Update September 18, 2024 (#106) 2024-10-02 00:38:52 +03:00
Kirill
8c07540d28 Update September 6, 2024 (#104) 2024-10-02 00:38:01 +03:00
Kirill
7c0e18681b Updates August 26, 2024 and August 28, 2024 (#103) 2024-09-10 00:57:30 +03:00
Pireirik
895ef8be52 Change the data structure of chat response according to the updates from Ozon (#102) 2024-09-09 22:09:31 +03:00
Pireirik
ccd3610c76 Change variable type for list of chats (#101) 2024-09-08 19:39:20 +03:00
s1berc0de
090b2afb63 added /v1/description-category/attribute/values/search support (#100) 2024-08-29 17:53:31 +03:00
diPhantxm
823386edf2 remove states 2024-08-09 19:57:56 +03:00
Kirill
5ecf131061 Update August 2, 2024 (#99) 2024-08-09 19:55:36 +03:00
Kirill
dfbb93f438 Update June 27, 2024 (#98) 2024-07-29 04:26:59 +03:00
Zloy_Leshiy
b0c133ba40 replace method from GET to POST for /v1/actions/products/activate request (#97)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-07-22 14:31:28 +03:00
Kirill
680a155294 Update June 18, 2024 (#96) 2024-07-05 01:52:13 +03:00
Kirill
26e2c8b9a7 Update June 10, 2024 (#95) 2024-06-16 02:14:27 +03:00
Zloy_Leshiy
b26dd5bbee Archived fields for product details and fix filter for GetStockInfo (#94)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-06-03 13:40:55 +03:00
Kirill
9ea138003c Update May 24, 2024 (#93) 2024-05-24 15:59:51 +03:00
Kirill
5d62c8ec14 Update May 23, 2024 (#92) 2024-05-24 15:50:40 +03:00
Kirill
2e284d9667 Time format fixes and optional fields (#91) 2024-05-24 15:42:48 +03:00
Kirill
7ffcf3f235 Update 16, 2024 (#90) 2024-05-18 12:07:52 +03:00
Kirill
ad2eb19325 Update May 2, 2024 (#89) 2024-05-06 19:57:44 +03:00
Kirill
99b0a24d48 Tests for Time Format (#87) 2024-04-30 14:15:03 +03:00
Zloy_Leshiy
e5867813ab Custom data type for time representation (#86)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-04-30 13:03:08 +03:00
Kirill
b0e1210f34 Fix type in GetStocksInfoParams.GetStocksInfoFilter #84 (#85) 2024-04-27 13:22:18 +05:00
Kirill
049b02835b Fix response types in GetListOfWarehouses (#83) 2024-04-21 18:41:18 +03:00
Kirill
6cf4ae89e3 Fix empty body (#81) 2024-04-20 18:55:49 +03:00
Kirill
8a6ca3b2eb Update April 16, 2024 (#78) 2024-04-18 16:39:33 +03:00
Kirill
25051ee802 Updates April 8, 2024 and April 9, 2024 (#77) 2024-04-10 17:53:31 +03:00
Kirill
61a78b1c4c Methods to manage Passes (Update April 1, 2024) (#76) 2024-04-02 22:50:52 +03:00
Kirill
97a9d2aba5 Update March 29, 2024 (#74) 2024-03-31 16:51:47 +03:00
Kirill
f02e71d17e Update March 12, 2024 (#73)
Add `hybryd` value to TPL Integration Type enum
2024-03-13 21:54:54 +03:00
Kirill
e6bfa30545 Update March 1, 2024 (#72)
Remove `accepted_from_customer_moment` from request and response in `/v3/returns/company/fbs`
2024-03-06 14:42:57 +03:00
Kirill
f6311fe59e Update February 16, 2024 (#71) 2024-02-17 03:47:20 +03:00
Kirill
8e73d136f2 Update February 13, 2024 (#70) 2024-02-17 03:24:14 +03:00
Kirill
7d8f43540f Update February 8, 2024 (#69) 2024-02-11 13:55:36 +03:00
diPhantxm
114a5b90b9 fix typo 2024-02-06 16:41:46 +03:00
Kirill
a0995a79e1 Update January 26, 2024 (#66)
add new status to fbo returns
2024-01-29 23:31:46 +03:00
Kirill
2f94b8c774 Reimplement default values (#65) 2024-01-29 23:22:57 +03:00
Kirill
35832e6269 remove default values temporarily (#64) 2024-01-29 18:51:12 +03:00
Kirill
965c83ba85 Make fields optional in method params (#62) 2024-01-25 15:41:30 +03:00
Zloy_Leshiy
a3c9d93adc Golang version to 19 and not required fields #60 (#61)
Co-authored-by: o.tyurin <o.tyurin@corp.mail.ru>
2024-01-23 03:14:18 +03:00
Kirill
549a2b9b41 Update readme example (#59) 2024-01-08 15:55:49 +03:00
Kirill
c36446bb59 Update December 25, 2023 (#57) 2023-12-27 00:04:03 +03:00
Kirill
ebafb17c9d Update December 15, 2023 (#55) 2023-12-16 15:52:35 +03:00
Kirill
178fd7086a Update December 11, 2023 (#54) 2023-12-12 23:29:03 +03:00
Kirill
de08ee28c0 Client options (#53) 2023-12-11 21:30:36 +03:00
Kirill
1494ff5905 Better tests (check that response match the structure) (#52)
Trailing zeros were removed from time in responses because of json marshaling features
2023-12-11 03:19:12 +03:00
Kirill
922e2de8b0 Update December 7, 2023 (#51) 2023-12-09 16:05:52 +03:00
Kirill
af7c167edf Update November 30, 2023 (#50)
Method for RFBS returns operations
2023-11-30 18:16:34 +03:00
Kirill
ba8f4ca1b2 Update November 9, 2023 (#49) 2023-11-11 16:05:36 +03:00
Kirill
52b18252b1 Update November 8, 2023 (#48) 2023-11-11 15:37:26 +03:00
Kirill
b496767c5b Update October 30, 2023 (#47) 2023-10-31 01:44:56 +03:00
Kirill
40dd5b86a8 Update October 26, 2023 (#46) 2023-10-27 20:06:33 +03:00
Antares
e5f2007a8e Fix fields in Finance methods (#45)
Fixed operation type field in getting transaction list
Fixed fields in params for getting finance realization report
2023-10-26 14:48:31 +03:00
Kirill
40d9fc32cb Update October 19, 2023 (#44) 2023-10-20 00:58:22 +03:00
Kirill
b07968d280 update/6-october-2023 - New Categories' methods version (#43) 2023-10-08 19:02:14 +03:00
Kirill
699d210296 update/4-october-2023 - Barcodes (#42) 2023-10-08 18:27:39 +03:00
Kirill
07d38a8456 remove deprecated method Delete Polygon (#41) 2023-09-05 23:11:43 +03:00
Kirill
0f1d0410bc update some methods description (#40) 2023-09-05 23:06:04 +03:00
Kirill
6d4d97e3c8 added comments on restrictions for seller without Premium subscription (#39) 2023-08-25 16:38:38 +03:00
Kirill
85543f45b0 update comments on price in Update Price params (#38) 2023-08-25 16:36:25 +03:00
Kirill
854d110ab1 Configure HttpClient, added context parameter to all methods (#37)
Context is needed to limit time of execution of a method. Previously, context was passed to structure and it was stored inside structure which is a bad practice. Now we need to pass context to function, which is a best practice
2023-08-05 13:50:34 +03:00
Kirill
018d40e641 Update August 2, 2023 (#34) 2023-08-03 21:02:08 +03:00
Kirill
72b25b673d fix tests (#33) 2023-08-02 01:57:08 +03:00
Kirill
cb24f19e83 fbs_sku and fbo_sku are now just sku (#32) 2023-08-02 01:19:21 +03:00
Kirill
588f4748a9 Update 28 July, 2023 (#31) 2023-07-31 00:20:07 +03:00
Kirill
651c39595f add tests for notification server errors (#30) 2023-07-29 02:59:03 +03:00
Kirill
6c1a5e35c0 enhance tests for notification server
enhance tests for notification server
2023-07-29 01:37:01 +03:00
diPhantxm
580a752012 enhance tests for notification server 2023-07-29 01:29:38 +03:00
diPhantxm
83fd8cf825 fix examlpe for notification server 2023-07-27 03:20:32 +03:00
Kirill
ebbc21b618 Notification Server
Push-Notifications
2023-07-27 02:51:36 +03:00
diPhantxm
f53b573d62 fix tests 2023-07-27 02:49:55 +03:00
diPhantxm
eb0ce6feb6 update readme 2023-07-27 00:44:36 +03:00
diPhantxm
9a41bb1196 add notification server, tests 2023-07-27 00:23:47 +03:00
diPhantxm
e76b9f3961 add notification server message types 2023-07-26 04:18:24 +03:00
diPhantxm
add4202b3e added new response fields to GetProductPriceInfo: result.items.acquiring, result.items.commissions.sales_percent_fbo/fbs 2023-07-25 17:41:27 +03:00
diPhantxm
7beee39eb2 Added the prices.price_strategy_enabled parameter to the method request 2023-07-20 02:48:09 +03:00
diPhantxm
baeeef9b46 fix json names for GetDigitalActResponse 2023-07-15 03:02:45 +03:00
diPhantxm
336c49baa4 Added the response example to TestGetLabeling 2023-07-15 03:02:45 +03:00
diPhantxm
f11ccb4714 updated the schemas and examples for method responses: methods return the result in binary format 2023-07-15 03:02:45 +03:00
diPhantxm
1958dfb94e Added a method for getting a barcode from the /v2/posting/fbs/act/get-barcode response in text format 2023-07-12 19:14:48 +03:00
diPhantxm
2ca20d9b12 Added a method for getting a barcode used during product shipment 2023-07-12 19:14:48 +03:00
Anton Salnikov
eea0f99066 Change address field tag 2023-07-09 19:54:28 +03:00
diPhantxm
7adaa92ad4 Specify enum values for status in GetFBOReturns method 2023-07-06 19:48:33 +03:00
diPhantxm
f54aa64b63 Update GetFBSReturns to v3. Add exemplar_id and return_barcode fields to method reponse 2023-07-06 19:39:54 +03:00
diPhantxm
e7c5dc320e fix params field in GetReportsListResultReport 2023-06-24 02:06:18 +03:00
diPhantxm
006ecdf877 commmented all data types and methods 2023-06-23 23:23:23 +03:00
diPhantxm
f81e4ed350 flat structures 2023-06-23 20:57:38 +03:00
Kirill
e7290069f5 Create LICENSE
Add MIT License
2023-06-20 23:33:02 +03:00
diphantxm
422f959b9f add prr option to some methods 2023-06-16 21:52:49 +03:00
diphantxm
ac99c5ba2f added new status to getting act list filter, updated descriptions, changed test according to examples 2023-06-16 21:35:33 +03:00
64 changed files with 17690 additions and 5560 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

21
LICENSE Normal file
View File

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

View File

@@ -8,9 +8,8 @@ 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
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)
Just add dependency to your project and you're ready to go. Just add dependency to your project and you're ready to go.
@@ -22,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"
@@ -32,10 +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
resp, err := client.Products().GetProductDetails(&ozon.GetProductDetailsParams{ resp, err := client.Products().GetProductDetails(context.Background(), &ozon.GetProductDetailsParams{
ProductId: 123456789, ProductId: 123456789,
}) })
if err != nil || resp.StatusCode != http.StatusOK { if err != nil || resp.StatusCode != http.StatusOK {
@@ -49,7 +53,39 @@ func main() {
} }
``` ```
## Contribution ### Notifications
If you need some endpoints ASAP, create an issue and list all the endpoints. I will add them to library soon. Ozon can send push-notifications to your REST server. There is an implementation of REST server that handles notifications in this library.
Or you can implement them and contribute to the project. Contribution to the project is welcome. [Official documentation](https://docs.ozon.ru/api/seller/en/#tag/push_intro)
How to use:
```Golang
package main
import (
"log"
"github.com/diphantxm/ozon-api-client/ozon/notifications"
)
func main() {
// Create server
port := 5000
server := notifications.NewNotificationServer(port)
// Register handlers passing message type and handler itself
server.Register(notifications.ChatClosedType, func(req interface{}) error {
notification := req.(*notifications.ChatClosed)
// Do something with the notification here...
log.Printf("chat %s has been closed\n", notification.ChatId)
return nil
})
// Run server
if err := server.Run(); err != nil {
log.Printf("error while running notification server: %s", err)
}
}
```

View File

@@ -7,6 +7,8 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"reflect"
) )
type HttpClient interface { type HttpClient interface {
@@ -15,36 +17,47 @@ type HttpClient interface {
type Client struct { type Client struct {
baseUrl string baseUrl string
ctx context.Context
Options map[string]string Options map[string]string
client HttpClient client HttpClient
} }
func NewClient(baseUrl string, opts map[string]string) *Client { func NewClient(client HttpClient, baseUrl string, opts map[string]string) *Client {
return &Client{ return &Client{
Options: opts, Options: opts,
ctx: context.Background(), client: client,
client: http.DefaultClient,
baseUrl: baseUrl, baseUrl: baseUrl,
} }
} }
func NewMockClient(handler http.HandlerFunc) *Client { func NewMockClient(handler http.HandlerFunc) *Client {
return &Client{ return &Client{
ctx: context.Background(),
client: NewMockHttpClient(handler), client: NewMockHttpClient(handler),
} }
} }
func (c Client) newRequest(method string, url string, body interface{}) (*http.Request, error) { func (c Client) newRequest(ctx context.Context, method string, uri string, body interface{}) (*http.Request, error) {
bodyJson, err := json.Marshal(body) var err error
var bodyJson []byte
// Set default values for empty fields if `default` tag is present
// And body is not nil
if body != nil {
if err := getDefaultValues(reflect.ValueOf(body)); err != nil {
return nil, err
}
bodyJson, err = json.Marshal(body)
if err != nil {
return nil, err
}
}
uri, err = url.JoinPath(c.baseUrl, uri)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequestWithContext(ctx, method, uri, bytes.NewBuffer(bodyJson))
url = c.baseUrl + url
req, err := http.NewRequestWithContext(c.ctx, method, url, bytes.NewBuffer(bodyJson))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -56,16 +69,11 @@ func (c Client) newRequest(method string, url string, body interface{}) (*http.R
return req, nil return req, nil
} }
func (c Client) Request(method string, path string, req, resp interface{}, options map[string]string) (*Response, error) { func (c Client) Request(ctx context.Context, method string, path string, req, resp interface{}, options map[string]string) (*Response, error) {
httpReq, err := c.newRequest(method, path, req) httpReq, err := c.newRequest(ctx, method, path, req)
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 {

View File

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

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

@@ -1,8 +1,8 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"time"
core "github.com/diphantxm/ozon-api-client" core "github.com/diphantxm/ozon-api-client"
) )
@@ -13,25 +13,31 @@ 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:
// - unknownDimension—unknown,
// - sku—product identifier,
// - spu—product identifier,
// - day—day,
// - week—week,
// - month—month.
// Data grouping available to sellers with Premium subscription:
// - year—year,
// - category1—first level category,
// - category2—second level category,
// - category3—third level category,
// - category4—fourth level category,
// - brand—brand,
// - modelID—model.
Dimension []GetAnalyticsDataDimension `json:"dimension"` Dimension []GetAnalyticsDataDimension `json:"dimension"`
// Filters // Filters
Filters []struct { Filters []GetAnalyticsDataFilter `json:"filters"`
// Sorting parameter. You can pass any attribute from the `dimension` and `metric` parameters except the `brand` attribute
Key string `json:"key"`
// Comparison operation
Operation GetAnalyticsDataFilterOperation `json:"operation"`
// Value for comparison
Value string `json:"value"`
} `json:"filters"`
// Number of items in the respones: // Number of items in the respones:
// - maximum is 1000, // - maximum is 1000,
@@ -39,6 +45,30 @@ type GetAnalyticsDataParams struct {
Limit int64 `json:"limit"` Limit int64 `json:"limit"`
// Specify up to 14 metrics. If there are more, you will get an error with the InvalidArgument code // Specify up to 14 metrics. If there are more, you will get an error with the InvalidArgument code
// The list of metrics for which the report will be generated.
//
// Metrics available to all sellers:
//
// - revenue—ordered amount,
// - ordered_units—ordered products.
// Metrics available to sellers with Premium subscription:
// - unknown_metric—unknown metric,
// - hits_view_search—impressions in search and category,
// - hits_view_pdp—impressions on the product description page,
// - hits_view—total impressions,
// - hits_tocart_search—added to cart from search or category,
// - hits_tocart_pdp—added to cart from the product description page,
// - hits_tocart—added to cart total,
// - session_view_search—sessions with impressions in search or category,
// - session_view_pdp—sessions with impressions on the product description page,
// - session_view—sessions total,
// - conv_tocart_search—conversion to cart from search or category,
// - conv_tocart_pdp—conversion to cart from a product description page,
// - conv_tocart—total conversion to cart,
// - returns—returned products,
// - cancellations—canceled products,
// - delivered_units—delivered products,
// - position_category—position in search and category.
Metrics []GetAnalyticsDataFilterMetric `json:"metrics"` Metrics []GetAnalyticsDataFilterMetric `json:"metrics"`
// Number of elements that will be skipped in the response. For example, if `offset=10`, the response will start with the 11th element found // Number of elements that will be skipped in the response. For example, if `offset=10`, the response will start with the 11th element found
@@ -48,6 +78,17 @@ type GetAnalyticsDataParams struct {
Sort []GetAnalyticsDataSort `json:"sort"` Sort []GetAnalyticsDataSort `json:"sort"`
} }
type GetAnalyticsDataFilter struct {
// Sorting parameter. You can pass any attribute from the `dimension` and `metric` parameters except the `brand` attribute
Key string `json:"key"`
// Comparison operation
Operation GetAnalyticsDataFilterOperation `json:"operation"`
// Value for comparison
Value string `json:"value"`
}
// Report sorting settings // Report sorting settings
type GetAnalyticsDataSort struct { type GetAnalyticsDataSort struct {
// Metric by which the method result will be sorted // Metric by which the method result will be sorted
@@ -61,37 +102,50 @@ type GetAnalyticsDataResponse struct {
core.CommonResponse core.CommonResponse
// Method result // Method result
Result struct { Result GetAnalyticsDataResult `json:"result"`
// Data array
Data []struct {
// Data grouping in the report
Dimensions []struct {
// Identifier
Id string `json:"id"`
// Name
Name string `json:"name"`
} `json:"dimensions"`
// Metric values list
Metrics []float64 `json:"metrics"`
} `json:"data"`
// Total and average metrics values
Totals []float64 `json:"totals"`
} `json:"result"`
// Report creation time // Report creation time
Timestamp string `json:"timestamp"` Timestamp string `json:"timestamp"`
} }
type GetAnalyticsDataResult struct {
// Data array
Data []GetAnalyticsDataResultData `json:"data"`
// Total and average metrics values
Totals []float64 `json:"totals"`
}
type GetAnalyticsDataResultData struct {
// Data grouping in the report
Dimensions []GetAnalyticsDataResultDimension `json:"dimensions"`
// Metric values list
Metrics []float64 `json:"metrics"`
}
type GetAnalyticsDataResultDimension struct {
// Product SKU
Id string `json:"id"`
// Name
Name string `json:"name"`
}
// Specify the period and metrics that are required. The response will contain analytical data grouped by the `dimensions` parameter. // Specify the period and metrics that are required. The response will contain analytical data grouped by the `dimensions` parameter.
func (c Analytics) GetAnalyticsData(params *GetAnalyticsDataParams) (*GetAnalyticsDataResponse, error) { //
// There are restrictions for sellers without Premium subscription:
//
// - data is available for the last 3 months,
// - some of the data grouping methods and metrics aren't available.
//
// There are no restrictions for sellers with Premium subscription
func (c Analytics) GetAnalyticsData(ctx context.Context, params *GetAnalyticsDataParams) (*GetAnalyticsDataResponse, error) {
url := "/v1/analytics/data" url := "/v1/analytics/data"
resp := &GetAnalyticsDataResponse{} resp := &GetAnalyticsDataResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -109,48 +163,284 @@ 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 {
core.CommonResponse core.CommonResponse
// Method result // Method result
Result struct { Result GetStocksOnWarehousesResult `json:"result"`
// Information about products and stocks
Rows []struct {
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Product identifier in the seller's system
ItemCode string `json:"item_code"`
// Product name in the Ozon system
ItemName string `json:"item_name"`
// Product amount available for sale on Ozon
FreeToSellAmount int64 `json:"free_to_sell_amount"`
// Product amount specified for confirmed future supplies
PromisedAmount int64 `json:"promised_amount"`
// Product amount reserved for purchase, returns, and transportation between warehouses
ReservedAmount int64 `json:"reserved_amount"`
// Name of the warehouse where the products are stored
WarehouseName string `json:"warehouse_name"`
} `json:"rows"`
} `json:"result"`
} }
// Report on stocks and products movement at Ozon warehouses type GetStocksOnWarehousesResult struct {
func (c Analytics) GetStocksOnWarehouses(params *GetStocksOnWarehousesParams) (*GetStocksOnWarehousesResponse, error) { // Information about products and stocks
Rows []GetStocksOnWarehousesResultRow `json:"rows"`
}
type GetStocksOnWarehousesResultRow struct {
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Product identifier in the seller's system
ItemCode string `json:"item_code"`
// Product name in the Ozon system
ItemName string `json:"item_name"`
// Product amount available for sale on Ozon
FreeToSellAmount int64 `json:"free_to_sell_amount"`
// Product amount specified for confirmed future supplies
PromisedAmount int64 `json:"promised_amount"`
// Product amount reserved for purchase, returns, and transportation between warehouses
ReservedAmount int64 `json:"reserved_amount"`
// Name of the warehouse where the products are stored
WarehouseName string `json:"warehouse_name"`
}
// Method for getting a report on leftover stocks and products movement at Ozon warehouses
func (c Analytics) GetStocksOnWarehouses(ctx context.Context, params *GetStocksOnWarehousesParams) (*GetStocksOnWarehousesResponse, error) {
url := "/v2/analytics/stock_on_warehouses" url := "/v2/analytics/stock_on_warehouses"
resp := &GetStocksOnWarehousesResponse{} resp := &GetStocksOnWarehousesResponse{}
response, err := c.client.Request(http.MethodPost, url, params, 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 GetProductTurnoverParams struct {
// Number of values in the response
Limit int64 `json:"limit"`
// Number of elements to skip in the response.
//
// For example, if offset = 10, the response starts with the 11th element found
Offset int32 `json:"offset"`
// Product identifiers in the Ozon system, SKU
SKU []string `json:"sku"`
}
type GetProductTurnoverResponse struct {
core.CommonResponse
// Products
Items []ProductTurnoverItem `json:"items"`
}
type ProductTurnoverItem struct {
// Average daily number of product items sold over the last 60 days
Ads float64 `json:"ads"`
// Product stock, pcs
CurrentStock int64 `json:"current_stock"`
// Number of days the stock will last based on your average daily sales
IDC float64 `json:"idc"`
// Product stock level
IDCGrade string `json:"idc_grade"`
}
// Use the method to get the product turnover rate and the number of days the current stock will last.
//
// If you request a list of products by sku, the limit and offset parameters are optional.
func (c Analytics) GetProductTurnover(ctx context.Context, params *GetProductTurnoverParams) (*GetProductTurnoverResponse, error) {
url := "/v1/analytics/turnover/stocks"
resp := &GetProductTurnoverResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type GetStockManagementParams struct {
// GetStockManagementFilter
Filter GetStockManagementFilter `json:"filter"`
// Number of values in the response
Limit int32 `json:"limit,omitempty"`
// Number of elements to skip in the response
Offset int32 `json:"offset,omitempty"`
}
type GetStockManagementFilter struct {
// Product identifiers in the Ozon system, SKU
SKUs []string `json:"skus"`
// The type of item in stock
StockTypes string `json:"stock_types"`
// Warehouse identifiers
WarehouseIds []string `json:"warehouse_ids"`
}
type GetStockManagementResponse struct {
core.CommonResponse
// Products
Items []StockItem `json:"items"`
}
type StockItem struct {
// Stock of defective products, pcs
DefectCount int64 `json:"defect_stock_count"`
// Stock of near-expired products, pcs
ExpiringCount int64 `json:"expiring_stock_count"`
// Product name
ProductName string `json:"name"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Stock of valid products
ValidCount int64 `json:"valid_stock_count"`
// Stock of products that waiting for documents
WaitingDocsCount int64 `json:"waitingdocs_stock_count"`
// Warehouse name
WarehouseName string `json:"warehouse_name"`
}
// Use the method to find out how many product items are left in stock
func (c Analytics) Stock(ctx context.Context, params *GetStockManagementParams) (*GetStockManagementResponse, error) {
url := "/v1/analytics/manage/stocks"
resp := &GetStockManagementResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type GetProductQueriesParams struct {
// Date when analytics generation starts
DateFrom string `json:"date_from"`
//Date when analytics generation ends
DateTo string `json:"date_to"`
// Number of page returned in the request
Page int32 `json:"page"`
// Number of items on the pag
PageSize int32 `json:"page_size"`
// List of SKUs—product identifiers in the Ozon system.
// Analytics on requests is returned for them.
// Maximum value is 1,000 SKUs
SKUs []string `json:"skus"`
// Parameter by which products are sorted
SortBy string `json:"sort_by"`
// Sorting direction
SortDir string `json:"sort_dir"`
}
type GetProductQueriesResponse struct {
core.CommonResponse
// Period for which the analytics is generated
AnalyticsPeriod AnalyticsPeriod `json:"analytics_period"`
// Product list
Items []GetProductQueriesItem `json:"items"`
// Number of pages
PageCount int64 `json:"page_count"`
// Total number of queries
Total int64 `json:"total"`
}
type AnalyticsPeriod struct {
// Date when analytics generation starts
DateFrom string `json:"date_from"`
// Date when analytics generation ends
DateTo string `json:"date_to"`
}
type GetProductQueriesItem struct {
// Category name
Category string `json:"category"`
// Currency
Currency string `json:"currency"`
// Sales by queries
GMV float64 `json:"gmv"`
// Product name
Name string `json:"name"`
// Product identifier in the seller's system
OfferId string `json:"offer_id"`
// Average product position. Available only with the Premium or Premium Plus subscription, otherwise the field returns empty
Position float64 `json:"position"`
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
// Number of customers who searched for your product on Ozon
UniqueSearchUsers int64 `json:"unique_search_users"`
// Number of customers who have seen your product on Ozon.
// Available only with the Premium or Premium Plus subscription,
// otherwise the field returns empty
UniqueViewUsers int64 `json:"unique_view_users"`
// Conversion from product views.
// Available only with the Premium or Premium Plus subscription,
// otherwise the field returns empty
ViewConversion float64 `json:"view_conversion"`
}
// Use the method to get data about your product queries.
// Full analytics is available with the Premium and Premium Plus subscription.
// Without subscription, you can see a part of the metrics.
// The method is similar to the Products in Search → Queries for my product tab in your personal account.
//
// You can view analytics by queries for certain dates.
// To do this, specify the interval in the date_from and date_to fields.
// Data for the last month are available in any interval except for
// three days from the current date because these days the calculation is performed.
// Analytics for dates later than a month ago is available only with
// the Premium and Premium Plus subscription, and only by weeks.
// Specify the date_from parameter in the request
func (c Analytics) GetProductQueries(ctx context.Context, params *GetProductQueriesParams) (*GetProductQueriesResponse, error) {
url := "/v1/analytics/product-queries"
resp := &GetProductQueriesResponse{}
response, err := c.client.Request(ctx, http.MethodPost, url, params, resp, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,8 +1,10 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
"time"
core "github.com/diphantxm/ozon-api-client" core "github.com/diphantxm/ozon-api-client"
) )
@@ -21,10 +23,10 @@ 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{AdvViewAll}, Metrics: []GetAnalyticsDataFilterMetric{HistViewPDP},
Sort: []GetAnalyticsDataSort{ Sort: []GetAnalyticsDataSort{
{ {
Key: HistViewPDP, Key: HistViewPDP,
@@ -59,11 +61,15 @@ func TestGetAnalyticsData(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Analytics().GetAnalyticsData(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Analytics().GetAnalyticsData(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetAnalyticsDataResponse{})
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)
} }
@@ -119,11 +125,15 @@ func TestGetStocksOnWarehouses(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Analytics().GetStocksOnWarehouses(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Analytics().GetStocksOnWarehouses(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetStocksOnWarehousesResponse{})
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)
} }
@@ -135,3 +145,206 @@ func TestGetStocksOnWarehouses(t *testing.T) {
} }
} }
} }
func TestGetProductTurnover(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetProductTurnoverParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetProductTurnoverParams{
Limit: 1,
SKU: []string{"string"},
},
`{
"items": [
{
"ads": 0,
"current_stock": 0,
"idc": 0,
"idc_grade": "GRADES_NONE"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetProductTurnoverParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Analytics().GetProductTurnover(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductTurnoverResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Items) > int(test.params.Limit) {
t.Errorf("Length of items is bigger than limit")
}
}
}
}
func TestGetStock(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetStockManagementParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetStockManagementParams{
Limit: 1,
Offset: 0,
Filter: GetStockManagementFilter{
StockTypes: "STOCK_TYPE_VALID",
SKUs: []string{
"string",
},
},
},
`{
"items": [
{
"defect_stock_count": 0,
"expiring_stock_count": 0,
"name": "string",
"offer_id": "string",
"sku": 0,
"valid_stock_count": 0,
"waitingdocs_stock_count": 0,
"warehouse_name": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetStockManagementParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Analytics().Stock(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetStockManagementResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestGetProductQueries(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetProductQueriesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetProductQueriesParams{
Page: 1,
PageSize: 10,
SKUs: []string{"string"},
},
`{
"analytics_period": {
"date_from": "string",
"date_to": "string"
},
"items": [
{
"category": "string",
"currency": "string",
"gmv": 0,
"name": "string",
"offer_id": "string",
"position": 0,
"sku": 0,
"unique_search_users": 0,
"unique_view_users": 0,
"view_conversion": 0
}
],
"page_count": 0,
"total": 0
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetProductQueriesParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Analytics().GetProductQueries(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GetProductQueriesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}

111
ozon/barcodes.go Normal file
View File

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

147
ozon/barcodes_test.go Normal file
View File

@@ -0,0 +1,147 @@
package ozon
import (
"context"
"net/http"
"testing"
core "github.com/diphantxm/ozon-api-client"
)
func TestGenerateBarcodes(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GenerateBarcodesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GenerateBarcodesParams{
ProductIds: []int64{123456789},
},
`{
"errors": [
{
"code": "code 200",
"error": "no error",
"barcode": "456",
"product_id": 123456789
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GenerateBarcodesParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Barcodes().Generate(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &GenerateBarcodesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Errors) != 0 {
if resp.Errors[0].ProductId != test.params.ProductIds[0] {
t.Errorf("Product ids are not equal")
}
}
}
}
}
func TestBindBarcodes(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *BindBarcodesParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&BindBarcodesParams{
Barcodes: []BindBarcode{
{
Barcode: "some barcode",
SKU: 123456789,
},
},
},
`{
"errors": [
{
"code": "code 200",
"error": "no error",
"barcode": "some barcode",
"sku": 123456789
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&BindBarcodesParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Barcodes().Bind(ctx, test.params)
if err != nil {
t.Error(err)
continue
}
compareJsonResponse(t, test.response, &BindBarcodesResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Errors) != 0 {
if resp.Errors[0].Barcode != test.params.Barcodes[0].Barcode {
t.Errorf("Barcodes are not equal")
}
if resp.Errors[0].SKU != test.params.Barcodes[0].SKU {
t.Errorf("Barcodes are not equal")
}
}
}
}
}

View File

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

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -28,7 +29,6 @@ func TestListCertifiedBrands(t *testing.T) {
"result": { "result": {
"brand_certification": [ "brand_certification": [
{ {
"brand_id": 135476863,
"brand_name": "Sea of Spa", "brand_name": "Sea of Spa",
"has_certificate": false "has_certificate": false
} }
@@ -52,11 +52,15 @@ func TestListCertifiedBrands(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Brands().List(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Brands().List(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListCertifiedBrandsResponse{})
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)
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"time" "time"
@@ -31,13 +32,7 @@ type CancellationInfo struct {
PostingNumber string `json:"posting_number"` PostingNumber string `json:"posting_number"`
// Cancellation reason // Cancellation reason
CancellationReason struct { CancellationReason CancellationInfoReason `json:"cancellation_reason"`
// Cancellation reason identifier
Id int64 `json:"id"`
// Cancellation reason name
Name string `json:"name"`
} `json:"cancellation_reason"`
// Cancellation request creation date // Cancellation request creation date
CancelledAt time.Time `json:"cancelled_at"` CancelledAt time.Time `json:"cancelled_at"`
@@ -46,19 +41,10 @@ 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 struct { State CancellationInfoState `json:"state"`
// Status identifier
Id int64 `json:"id"`
// Status name
Name string `json:"name"`
// Request status
State string `json:"state"`
} `json:"state"`
// Cancellation initiator // Cancellation initiator
CancellationInitiator string `json:"cancellation_initiator"` CancellationInitiator string `json:"cancellation_initiator"`
@@ -76,13 +62,32 @@ type CancellationInfo struct {
AutoApproveDate time.Time `json:"auto_approve_date"` AutoApproveDate time.Time `json:"auto_approve_date"`
} }
type CancellationInfoReason struct {
// Cancellation reason identifier
Id int64 `json:"id"`
// Cancellation reason name
Name string `json:"name"`
}
type CancellationInfoState struct {
// Status identifier
Id int64 `json:"id"`
// Status name
Name string `json:"name"`
// Request status
State string `json:"state"`
}
// Method for getting information about a rFBS cancellation request // Method for getting information about a rFBS cancellation request
func (c Cancellations) GetInfo(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{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -93,30 +98,30 @@ func (c Cancellations) GetInfo(params *GetCancellationInfoParams) (*GetCancellat
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 {
@@ -134,25 +139,27 @@ type ListCancellationsResponse struct {
Total int32 `json:"total"` Total int32 `json:"total"`
// Counter of requests in different statuses // Counter of requests in different statuses
Counters struct { Counters ListCancellationResponseCounters `json:"counters"`
// Number of requests for approval }
OnApproval int64 `json:"on_approval"`
// Number of approved requests type ListCancellationResponseCounters struct {
Approved int64 `json:"approved"` // Number of requests for approval
OnApproval int64 `json:"on_approval"`
// Number of rejected requests // Number of approved requests
Rejected int64 `json:"rejected"` Approved int64 `json:"approved"`
} `json:"counters"`
// Number of rejected requests
Rejected int64 `json:"rejected"`
} }
// Method for getting a list of rFBS cancellation requests // Method for getting a list of rFBS cancellation requests
func (c Cancellations) List(params *ListCancellationsParams) (*ListCancellationsResponse, error) { func (c Cancellations) List(ctx context.Context, params *ListCancellationsParams) (*ListCancellationsResponse, error) {
url := "/v1/conditional-cancellation/list" url := "/v1/conditional-cancellation/list"
resp := &ListCancellationsResponse{} resp := &ListCancellationsResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -166,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 {
@@ -175,12 +182,12 @@ type ApproveRejectCancellationsResponse struct {
// The method allows to approve an rFBS cancellation request in the ON_APPROVAL status. // The method allows to approve an rFBS cancellation request in the ON_APPROVAL status.
// The order will be canceled and the money will be returned to the customer // The order will be canceled and the money will be returned to the customer
func (c Cancellations) Approve(params *ApproveRejectCancellationsParams) (*ApproveRejectCancellationsResponse, error) { func (c Cancellations) Approve(ctx context.Context, params *ApproveRejectCancellationsParams) (*ApproveRejectCancellationsResponse, error) {
url := "/v1/conditional-cancellation/approve" url := "/v1/conditional-cancellation/approve"
resp := &ApproveRejectCancellationsResponse{} resp := &ApproveRejectCancellationsResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -190,14 +197,14 @@ func (c Cancellations) Approve(params *ApproveRejectCancellationsParams) (*Appro
} }
// The method allows to reject an rFBS cancellation request in the ON_APPROVAL status. Explain your decision in the comment parameter. // The method allows to reject an rFBS cancellation request in the ON_APPROVAL status. Explain your decision in the comment parameter.
// //
// The order will remain in the same status and must be delivered to the customer // The order will remain in the same status and must be delivered to the customer
func (c Cancellations) Reject(params *ApproveRejectCancellationsParams) (*ApproveRejectCancellationsResponse, error) { func (c Cancellations) Reject(ctx context.Context, params *ApproveRejectCancellationsParams) (*ApproveRejectCancellationsResponse, error) {
url := "/v1/conditional-cancellation/reject" url := "/v1/conditional-cancellation/reject"
resp := &ApproveRejectCancellationsResponse{} resp := &ApproveRejectCancellationsResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -62,11 +63,15 @@ func TestGetCancellationInfo(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Cancellations().GetInfo(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Cancellations().GetInfo(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetCancellationInfoResponse{})
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)
} }
@@ -93,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,
}, },
}, },
@@ -121,9 +126,9 @@ func TestListCancellations(t *testing.T) {
"state": "APPROVED" "state": "APPROVED"
}, },
"cancellation_initiator": "CLIENT", "cancellation_initiator": "CLIENT",
"order_date": "2021-09-03T07:04:53.220Z", "order_date": "2021-09-03T07:04:53.22Z",
"approve_comment": "", "approve_comment": "",
"approve_date": "2021-09-03T09:13:12.614200Z", "approve_date": "2021-09-03T09:13:12.6142Z",
"auto_approve_date": "2021-09-06T07:17:12.116114Z" "auto_approve_date": "2021-09-06T07:17:12.116114Z"
}, },
{ {
@@ -171,11 +176,15 @@ func TestListCancellations(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Cancellations().List(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Cancellations().List(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListCancellationsResponse{})
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)
} }
@@ -215,11 +224,15 @@ func TestApproveCancellations(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Cancellations().Approve(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Cancellations().Approve(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ApproveRejectCancellationsResponse{})
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)
} }
@@ -259,11 +272,15 @@ func TestRejectCancellations(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Cancellations().Reject(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Cancellations().Reject(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ApproveRejectCancellationsResponse{})
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)
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
core "github.com/diphantxm/ozon-api-client" core "github.com/diphantxm/ozon-api-client"
@@ -11,39 +12,48 @@ type Categories struct {
} }
type GetProductTreeParams struct { type GetProductTreeParams struct {
// Category identifier
CategoryId int64 `json:"category_id"`
// Response language // Response language
Language Language `json:"language" default:"DEFAULT"` Language Language `json:"language,omitempty"`
} }
type GetProductTreeResponse struct { type GetProductTreeResponse struct {
core.CommonResponse core.CommonResponse
// Category list // Categories list
Result []struct { Result []GetProductTreeResult `json:"result"`
// Category identifier }
CategoryId int64 `json:"category_id"`
// Subcategory tree type GetProductTreeResult struct {
Children []GetProductTreeResponse `json:"children"` // Category identifier
DescriptionCategoryId int64 `json:"description_category_id"`
// Category name // Category name
Title string `json:"title"` CategoryName string `json:"category_name"`
} `json:"result"`
// `true`, if you can't create products in the category. `false`, if you can
Disabled bool `json:"disabled"`
// Product type identifier
TypeId int64 `json:"type_id"`
// Product type name
TypeName string `json:"type_name"`
// Subcategory tree
Children []GetProductTreeResult `json:"children"`
} }
// Returns product categories in the tree view. // Returns product categories in the tree view.
//
// New products can be created in the last level categories only. // New products can be created in the last level categories only.
// This means that you need to match these particular categories with the categories of your site. // This means that you need to match these particular categories with the categories of your site.
// It is not possible to create categories by user request // We don't create new categories by user request.
func (c Categories) Tree(params *GetProductTreeParams) (*GetProductTreeResponse, error) { func (c *Categories) Tree(ctx context.Context, params *GetProductTreeParams) (*GetProductTreeResponse, error) {
url := "/v2/category/tree" url := "/v1/description-category/tree"
resp := &GetProductTreeResponse{} resp := &GetProductTreeResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -53,87 +63,91 @@ func (c Categories) Tree(params *GetProductTreeParams) (*GetProductTreeResponse,
} }
type GetCategoryAttributesParams struct { type GetCategoryAttributesParams struct {
// Filter by characteristics
AttributeType AttributeType `json:"attribute_type" default:"ALL"`
// Category identifier // Category identifier
CategoryId []int64 `json:"category_id"` DescriptionCategoryId int64 `json:"description_category_id"`
// Response language // Response language
Language Language `json:"language" default:"DEFAULT"` Language Language `json:"language,omitempty"`
// Product type identifier
TypeId int64 `json:"type_id"`
} }
type GetCategoryAttributesResponse struct { type GetCategoryAttributesResponse struct {
core.CommonResponse core.CommonResponse
// Method result // Method result
Result []struct { Result []GetCategoryAttributesResult `json:"result"`
// Array of product characteristics
Attributes []struct {
// Indication that the dictionary attribute values depend on the category:
// - true — the attribute has its own set of values for each category.
// - false — the attribute has the same set of values for all categories
CategoryDependent bool `json:"category_dependent"`
// Characteristic description
Description string `json:"description"`
// Directory identifier
DictionaryId int64 `json:"dictionary_id"`
// Characteristics group identifier
GroupId int64 `json:"group_id"`
// Characteristics group name
GroupName string `json:"group_name"`
// Document generation task number
Id int64 `json:"id"`
// Indicates that the attribute is aspect. An aspect attribute is a characteristic that distinguishes products of the same model.
//
// For example, clothes and shoes of the same model may have different colors and sizes. That is, color and size are aspect attributes.
//
// Values description:
// - true — the attribute is aspect and cannot be changed after the products are delivered to the warehouse or sold from the seller's warehouse.
// - false — the attribute is not aspect and can be changed at any time
IsAspect bool `json:"is_aspect"`
// Indicates that the characteristic is a set of values:
// - true — the characteristic is a set of values,
// - false — the characteristic consists of a single value
IsCollection bool `json:"is_collection"`
// Indicates that the characteristic is mandatory:
// - true — a mandatory characteristic,
// - false — you can leave the characteristic out
IsRequired bool `json:"is_required"`
// Name
Name string `json:"name"`
// Characteristic type
Type string `json:"type"`
} `json:"attributes"`
// Category identifier
CategoryId int64 `json:"category_id"`
} `json:"result"`
} }
// Getting characteristics for specified product category. type GetCategoryAttributesResult struct {
// Indication that the dictionary attribute values depend on the category:
//
// true — the attribute has its own set of values for each category.
// false — the attribute has the same set of values for all categories
CategoryDependent bool `json:"category_dependent"`
// Characteristic description
Description string `json:"description"`
// Directory identifier
DictionaryId int64 `json:"dictionary_id"`
// Characteristics group identifier
GroupId int64 `json:"group_id"`
// Characteristics group name
GroupName string `json:"group_name"`
// Number of document generation task
Id int64 `json:"id"`
// Indicates that the attribute is aspect. An aspect attribute is a characteristic that distinguishes products of the same model.
//
// For example, clothes or shoes of the same model may have different colors and sizes. That is, color and size are aspect attributes.
//
// Values description:
//
// - `true`—the attribute is aspect and can't be changed after the products are delivered to the warehouse or sold from the seller's warehouse.
// - `false`—the attribute is not aspect and can be changed at any time
IsAspect bool `json:"is_aspect"`
// Indicates that the characteristic is a set of values:
//
// - `true`—the characteristic is a set of values,
// - `false`—the characteristic consists of a single value
IsCollection bool `json:"is_collection"`
// Indicates that the characteristic is mandatory:
//
// - `true`—a mandatory characteristic,
// - `false`—an optional characteristic
IsRequired bool `json:"is_required"`
// Name
Name string `json:"name"`
// Characteristic type
Type string `json:"type"`
// Complex attribute identifier
AttributeComplexId int64 `json:"attribute_complex_id"`
// Maximum number of values for attribute
MaxValueCount int64 `json:"max_value_count"`
}
// Getting characteristics for specified product category and type.
// //
// Pass up to 20 category identifiers in the `category_id` list. // If the dictionary_id value is 0, there is no directory.
// // If the value is different, there are directories.
// You can check whether the attribute has a nested directory by the `dictionary_id` parameter. // Get them using the `/v1/description-category/attribute/values` method.
// The 0 value means there is no directory. If the value is different, then there are directories. func (c *Categories) Attributes(ctx context.Context, params *GetCategoryAttributesParams) (*GetCategoryAttributesResponse, error) {
// You can get them using the `/v2/category/attribute/values` method url := "/v1/description-category/attribute"
func (c Categories) Attributes(params *GetCategoryAttributesParams) (*GetCategoryAttributesResponse, error) {
url := "/v3/category/attribute"
resp := &GetCategoryAttributesResponse{} resp := &GetCategoryAttributesResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -147,44 +161,122 @@ type GetAttributeDictionaryParams struct {
AttributeId int64 `json:"attribute_id"` AttributeId int64 `json:"attribute_id"`
// Category identifier // Category identifier
CategoryId int64 `json:"category_id"` DescriptionCategoryId int64 `json:"description_category_id"`
// Response language // Response language
// The default language is Russian Language Language `json:"language,omitempty"`
Language Language `json:"language" default:"DEFAULT"`
// Identifier of the directory to start the response with.
// If `last_value_id` is 10, the response will contain directories starting from the 11th
LastValueId int64 `json:"last_value_id"` LastValueId int64 `json:"last_value_id"`
// Number of values in the response: // Number of values in the response:
// - maximum — 5000 //
// - minimum — 1 // - maximum—5000,
Limit int64 `json:"limit"` // - minimum—1.
Limit int64 `json:"limit,omitempty"`
// Product type identifier
TypeId int64 `json:"type_id"`
} }
type GetAttributeDictionaryResponse struct { type GetAttributeDictionaryResponse struct {
core.CommonResponse core.CommonResponse
// Indication that only part of characteristic values was returned in the response:
//
// - true—make a request with a new last_value_id parameter value for getting the rest of characteristic values;
// - false—all characteristic values were returned
HasNext bool `json:"has_next"` HasNext bool `json:"has_next"`
// Method result // Characteristic values
Result []struct { Result []GetAttributeDictionaryResult `json:"result"`
Id int64 `json:"id"`
Info string `json:"info"`
Picture string `json:"picture"`
// Product characteristic value
Value string `json:"value"`
} `json:"result"`
} }
// You can use the `/v3/category/attribute` method to check if an attribute has a nested directory. type GetAttributeDictionaryResult struct {
// If there are directories, get them using this method // Characteristic value identifier
func (c Categories) AttributesDictionary(params *GetAttributeDictionaryParams) (*GetAttributeDictionaryResponse, error) { Id int64 `json:"id"`
url := "/v2/category/attribute/values"
// Additional description
Info string `json:"info"`
// Image link
Picture string `json:"picture"`
// Product characteristic value
Value string `json:"value"`
}
// Returns characteristics value directory.
//
// To check if an attribute has a nested directory,
// use the `/v1/description-category/attribute` method.
func (c *Categories) AttributesDictionary(ctx context.Context, params *GetAttributeDictionaryParams) (*GetAttributeDictionaryResponse, error) {
url := "/v1/description-category/attribute/values"
resp := &GetAttributeDictionaryResponse{} resp := &GetAttributeDictionaryResponse{}
response, err := c.client.Request(http.MethodPost, url, params, 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 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 { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -21,14 +22,17 @@ func TestGetProductTree(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"},
&GetProductTreeParams{ &GetProductTreeParams{
CategoryId: 17034410, Language: English,
}, },
`{ `{
"result": [ "result": [
{ {
"category_id": 17034410, "description_category_id": 0,
"title": "Развивающие игрушки", "category_name": "string",
"children": [] "children": [],
"disabled": true,
"type_id": 0,
"type_name": "string"
} }
] ]
}`, }`,
@@ -48,22 +52,18 @@ func TestGetProductTree(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Categories().Tree(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Categories().Tree(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetProductTreeResponse{})
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 len(resp.Result) > 0 {
if resp.Result[0].CategoryId != test.params.CategoryId {
t.Errorf("First category ids in request and response are not equal")
}
}
}
} }
} }
@@ -81,25 +81,26 @@ func TestGetCategoryAttributes(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetCategoryAttributesParams{ &GetCategoryAttributesParams{
CategoryId: []int64{17034410}, DescriptionCategoryId: 12345,
Language: English,
TypeId: 2,
}, },
`{ `{
"result": [ "result": [
{ {
"category_id": 17034410, "category_dependent": true,
"attributes": [ "description": "string",
{ "dictionary_id": 0,
"id": 85, "group_id": 0,
"name": "Бренд", "group_name": "string",
"description": "Укажите наименование бренда, под которым произведен товар. Если товар не имеет бренда, используйте значение \"Нет бренда\"", "id": 0,
"type": "String", "is_aspect": true,
"is_collection": false, "is_collection": true,
"is_required": true, "is_required": true,
"group_id": 0, "name": "string",
"group_name": "", "type": "string",
"dictionary_id": 28732849 "attribute_complex_id": 0,
} "max_value_count": 0
]
} }
] ]
}`, }`,
@@ -119,25 +120,18 @@ func TestGetCategoryAttributes(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Categories().Attributes(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Categories().Attributes(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetCategoryAttributesResponse{})
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 len(resp.Result) != len(test.params.CategoryId) {
t.Errorf("Length of categories in request and response are not equal")
}
if len(resp.Result) > 0 {
if resp.Result[0].CategoryId != test.params.CategoryId[0] {
t.Errorf("Category ids in request and response are not equal")
}
}
}
} }
} }
@@ -155,33 +149,23 @@ func TestGetAttributeDictionary(t *testing.T) {
http.StatusOK, http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetAttributeDictionaryParams{ &GetAttributeDictionaryParams{
AttributeId: 10096, AttributeId: 123456,
CategoryId: 17028968, DescriptionCategoryId: 12,
LastValueId: 0, Language: English,
Limit: 3, LastValueId: 1,
Limit: 5,
TypeId: 6,
}, },
`{ `{
"has_next": true,
"result": [ "result": [
{ {
"id": 61571, "id": 0,
"value": "белый", "info": "string",
"info": "", "picture": "string",
"picture": "" "value": "string"
},
{
"id": 61572,
"value": "прозрачный",
"info": "",
"picture": ""
},
{
"id": 61573,
"value": "бежевый",
"info": "",
"picture": ""
} }
], ]
"has_next": true
}`, }`,
}, },
// Test No Client-Id or Api-Key // Test No Client-Id or Api-Key
@@ -199,11 +183,83 @@ func TestGetAttributeDictionary(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Categories().AttributesDictionary(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Categories().AttributesDictionary(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) 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")
}
}
}
}
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 { 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)
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"time" "time"
@@ -15,34 +16,40 @@ type ListOfAccordanceTypesResponse struct {
core.CommonResponse core.CommonResponse
// Accordance types // Accordance types
Result struct { Result ListOfAccordanceTypesResult `json:"result"`
// Main accordance types }
Base []struct {
// Accordance type code
Code string `json:"code"`
// Accordance type description type ListOfAccordanceTypesResult struct {
Title string `json:"title"` // Main accordance types
} `json:"base"` Base []ListOfAccordanceTypesResultBase `json:"base"`
// Main accordance types related to dangerous products // Main accordance types related to dangerous products
Hazard []struct { Hazard []ListOfAccordanceTypesResultHazard `json:"hazard"`
// Accordance type code }
Code string `json:"code"`
// Accordance type description type ListOfAccordanceTypesResultBase struct {
Title string `json:"title"` // Accordance type code
} `json:"hazard"` Code string `json:"code"`
} `json:"result"`
// Accordance type description
Title string `json:"title"`
}
type ListOfAccordanceTypesResultHazard struct {
// Accordance type code
Code string `json:"code"`
// Accordance type description
Title string `json:"title"`
} }
// List of accordance types (version 2) // List of accordance types (version 2)
func (c Certificates) ListOfAccordanceTypes() (*ListOfAccordanceTypesResponse, error) { func (c Certificates) ListOfAccordanceTypes(ctx context.Context) (*ListOfAccordanceTypesResponse, error) {
url := "/v2/product/certificate/accordance-types/list" url := "/v2/product/certificate/accordance-types/list"
resp := &ListOfAccordanceTypesResponse{} resp := &ListOfAccordanceTypesResponse{}
response, err := c.client.Request(http.MethodGet, url, nil, resp, nil) response, err := c.client.Request(ctx, http.MethodGet, url, nil, resp, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -55,22 +62,24 @@ type DirectoryOfDocumentTypesResponse struct {
core.CommonResponse core.CommonResponse
// List of certificate types and names // List of certificate types and names
Result []struct { Result []DirectoryOfDocumentTypesResult `json:"result"`
// Certificate name }
Name string `json:"name"`
// Certificate type type DirectoryOfDocumentTypesResult struct {
Value string `json:"value"` // Certificate name
} `json:"result"` Name string `json:"name"`
// Certificate type
Value string `json:"value"`
} }
// Directory of document types // Directory of document types
func (c Certificates) DirectoryOfDocumentTypes() (*DirectoryOfDocumentTypesResponse, error) { func (c Certificates) DirectoryOfDocumentTypes(ctx context.Context) (*DirectoryOfDocumentTypesResponse, error) {
url := "/v1/product/certificate/types" url := "/v1/product/certificate/types"
resp := &DirectoryOfDocumentTypesResponse{} resp := &DirectoryOfDocumentTypesResponse{}
response, err := c.client.Request(http.MethodGet, url, nil, resp, nil) response, err := c.client.Request(ctx, http.MethodGet, url, nil, resp, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -90,29 +99,37 @@ type ListOfCertifiedCategoriesParams struct {
type ListOfCertifiedCategoriesResponse struct { type ListOfCertifiedCategoriesResponse struct {
core.CommonResponse core.CommonResponse
// Method result // Certified categories details
Result struct { Certification []ListOfCertifiedCategoriesResultCert `json:"certification"`
// Certified categories details
Certification []struct {
// Category name
CategoryName string `json:"category_name"`
// Indication of a mandatory category // Total number of categories
IsRequired bool `json:"is_required"` Total int64 `json:"total"`
} `json:"certification"` }
// Total number of categories type ListOfCertifiedCategoriesResultCert struct {
Total int64 `json:"total"` // Identifier of the certified category
} `json:"reult"` CategoryId int64 `json:"category_id"`
// Category name
CategoryName string `json:"category_name"`
// Indication of a mandatory category
IsRequired bool `json:"is_required"`
// Type identifier of the certified category
TypeId int64 `json:"type_id"`
// Name of the type of certified category
TypeName string `json:"type_name"`
} }
// List of certified categories // List of certified categories
func (c Certificates) ListOfCertifiedCategories(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{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -137,12 +154,12 @@ type LinkCertificateToProductResponse struct {
} }
// Link the certificate to the product // Link the certificate to the product
func (c Certificates) LinkToProduct(params *LinkCertificateToProductParams) (*LinkCertificateToProductResponse, error) { func (c Certificates) LinkToProduct(ctx context.Context, params *LinkCertificateToProductParams) (*LinkCertificateToProductResponse, error) {
url := "/v1/product/certificate/bind" url := "/v1/product/certificate/bind"
resp := &LinkCertificateToProductResponse{} resp := &LinkCertificateToProductResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -160,24 +177,26 @@ type DeleteCertificateResponse struct {
core.CommonResponse core.CommonResponse
// Result of deleting the certificate // Result of deleting the certificate
Result struct { Result DeleteCertificateResult `json:"result"`
// Indication that a certificate has been deleted: }
// - true — deleted
// - false — not deleted
IsDelete bool `json:"is_delete"`
// Description of errors during certificate deletion type DeleteCertificateResult struct {
ErrorMessage string `json:"error_message"` // Indication that a certificate has been deleted:
} `json:"result"` // - true — deleted
// - false — not deleted
IsDelete bool `json:"is_delete"`
// Description of errors during certificate deletion
ErrorMessage string `json:"error_message"`
} }
// Delete certificate // Delete certificate
func (c Certificates) Delete(params *DeleteCertificateParams) (*DeleteCertificateResponse, error) { func (c Certificates) Delete(ctx context.Context, params *DeleteCertificateParams) (*DeleteCertificateResponse, error) {
url := "/v1/product/certificate/delete" url := "/v1/product/certificate/delete"
resp := &DeleteCertificateResponse{} resp := &DeleteCertificateResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -195,49 +214,51 @@ type GetCertificateInfoResponse struct {
core.CommonResponse core.CommonResponse
// Certificate information // Certificate information
Result struct { Result GetCertificateInfoResult `json:"result"`
// Identifier }
CertificateId int32 `json:"certificate_id"`
// Number type GetCertificateInfoResult struct {
CertificateNumber string `json:"certificate_number"` // Identifier
CertificateId int32 `json:"certificate_id"`
// Name // Number
CertificateName string `json:"certificate_name"` CertificateNumber string `json:"certificate_number"`
// Type // Name
TypeCode string `json:"type_code"` CertificateName string `json:"certificate_name"`
// Status // Type
StatusCode string `json:"status_code"` TypeCode string `json:"type_code"`
// Accordance type // Status
AccordanceTypeCode string `json:"accordance_type_code"` StatusCode string `json:"status_code"`
// Certificate rejection reason // Accordance type
RejectionReasonCode string `json:"rejectio_reason_code"` AccordanceTypeCode string `json:"accordance_type_code"`
// Moderator's comment // Certificate rejection reason
VerificationComment string `json:"verification_comment"` RejectionReasonCode string `json:"rejection_reason_code"`
// Issue date // Moderator's comment
IssueDate time.Time `json:"issue_date"` VerificationComment string `json:"verification_comment"`
// Expire date // Issue date
ExpireDate time.Time `json:"expire_date"` IssueDate time.Time `json:"issue_date"`
// Number of products associated with a certificate // Expire date
ProductsCount int32 `json:"products_count"` ExpireDate time.Time `json:"expire_date"`
} `json:"result"`
// Number of products associated with a certificate
ProductsCount int32 `json:"products_count"`
} }
// Certificate information // Certificate information
func (c Certificates) GetInfo(params *GetCertificateInfoParams) (*GetCertificateInfoResponse, error) { func (c Certificates) GetInfo(ctx context.Context, params *GetCertificateInfoParams) (*GetCertificateInfoResponse, error) {
url := "/v1/product/certificate/info" url := "/v1/product/certificate/info"
resp := &GetCertificateInfoResponse{} resp := &GetCertificateInfoResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -268,55 +289,59 @@ type ListCertificatesResponse struct {
core.CommonResponse core.CommonResponse
// Certificates // Certificates
Result struct { Result ListCertificatesResult `json:"result"`
// Сertificate information }
Certificates []struct {
// Identifier
CertificateId int32 `json:"certificate_id"`
// Number type ListCertificatesResult struct {
CertificateNumber string `json:"certificate_number"` // Сertificate information
Certificates []ListCertificatesResultCert `json:"certificates"`
// Name // Number of pages
CertificateName string `json:"certificate_name"` PageCount int32 `json:"page_count"`
}
// Type type ListCertificatesResultCert struct {
TypeCode string `json:"type"` // Identifier
CertificateId int32 `json:"certificate_id"`
// Status // Number
StatusCode string `json:"status_code"` CertificateNumber string `json:"certificate_number"`
// Accordance type // Name
AccordanceTypecode string `json:"accordance_type_code"` CertificateName string `json:"certificate_name"`
// Certificate rejection reason // Type
RejectionReasonCode string `json:"rejectio_reason_code"` TypeCode string `json:"type_code"`
// Moderator's comment // Status
VerificationComment string `json:"verification_comment"` StatusCode string `json:"status_code"`
// Issue date // Accordance type
IssueDate time.Time `json:"issue_data"` AccordanceTypecode string `json:"accordance_type_code"`
// Expire date // Certificate rejection reason
ExpireDate time.Time `json:"expire_date"` RejectionReasonCode string `json:"rejection_reason_code"`
// Number of products associated with a certificate // Moderator's comment
ProductsCount int32 `json:"products_count"` VerificationComment string `json:"verification_comment"`
} `json:"certificates"`
// Number of pages // Issue date
PageCount int32 `json:"page_count"` IssueDate time.Time `json:"issue_date"`
} `json:"result"`
// Expire date
ExpireDate time.Time `json:"expire_date"`
// Number of products associated with a certificate
ProductsCount int32 `json:"products_count"`
} }
// Certificates list // Certificates list
func (c Certificates) List(params *ListCertificatesParams) (*ListCertificatesResponse, error) { func (c Certificates) List(ctx context.Context, params *ListCertificatesParams) (*ListCertificatesResponse, error) {
url := "/v1/product/certificate/list" url := "/v1/product/certificate/list"
resp := &ListCertificatesResponse{} resp := &ListCertificatesResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -329,21 +354,23 @@ type ProductStatusesResponse struct {
core.CommonResponse core.CommonResponse
// Product statuses // Product statuses
Result []struct { Result []ProductStatusesResult `json:"result"`
// Product status code when linking it to the certificate
Code string `json:"code"`
// Status description
Name string `json:"name"`
} `json:"result"`
} }
func (c Certificates) ProductStatuses() (*ProductStatusesResponse, error) { type ProductStatusesResult struct {
// Product status code when linking it to the certificate
Code string `json:"code"`
// Status description
Name string `json:"name"`
}
func (c Certificates) ProductStatuses(ctx context.Context) (*ProductStatusesResponse, error) {
url := "/v1/product/certificate/list" url := "/v1/product/certificate/list"
resp := &ProductStatusesResponse{} resp := &ProductStatusesResponse{}
response, err := c.client.Request(http.MethodPost, url, nil, 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
} }
@@ -370,28 +397,30 @@ type ListProductsForCertificateResponse struct {
core.CommonResponse core.CommonResponse
// Method result // Method result
Result struct { Result ListProductsForCertificateResult `json:"result"`
// List of products }
Items []struct {
// Product identifier
ProductId int64 `json:"product_id"`
// Status of the product processing when binding to a certificate type ListProductsForCertificateResult struct {
ProductStatusCode string `json:"product_status_code"` // List of products
} `json:"items"` Items []struct {
// Product identifier
ProductId int64 `json:"product_id"`
// Number of products found // Status of the product processing when binding to a certificate
Count int64 `json:"count"` ProductStatusCode string `json:"product_status_code"`
} `json:"result"` } `json:"items"`
// Number of products found
Count int64 `json:"count"`
} }
// A method for getting a list of possible statuses of products when binding them to a certificate // A method for getting a list of possible statuses of products when binding them to a certificate
func (c Certificates) ListProductsForCertificate(params *ListProductsForCertificateParams) (*ListProductsForCertificateResponse, error) { func (c Certificates) ListProductsForCertificate(ctx context.Context, params *ListProductsForCertificateParams) (*ListProductsForCertificateResponse, error) {
url := "/v1/product/certificate/products/list" url := "/v1/product/certificate/products/list"
resp := &ListProductsForCertificateResponse{} resp := &ListProductsForCertificateResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -412,27 +441,29 @@ type UnlinkFromProductResponse struct {
core.CommonResponse core.CommonResponse
// Method result // Method result
Result []struct { Result []UnlinkFromProductResult `json:"result"`
// Error message when unbinding a product }
Error string `json:"error"`
// Product identifier type UnlinkFromProductResult struct {
ProductId int64 `json:"product_id"` // Error message when unbinding a product
Error string `json:"error"`
// Indication that the product was unbound from a certificate: // Product identifier
// - true — it was unbound, ProductId int64 `json:"product_id"`
// - false — it is still bound
Updated bool `json:"updated"` // Indication that the product was unbound from a certificate:
} `json:"result"` // - true — it was unbound,
// - false — it is still bound
Updated bool `json:"updated"`
} }
// Unbind products from a certificate // Unbind products from a certificate
func (c Certificates) UnlinkFromProduct(params *UnlinkFromProductParams) (*UnlinkFromProductResponse, error) { func (c Certificates) UnlinkFromProduct(ctx context.Context, params *UnlinkFromProductParams) (*UnlinkFromProductResponse, error) {
url := "/v1/product/certificate/unbind" url := "/v1/product/certificate/unbind"
resp := &UnlinkFromProductResponse{} resp := &UnlinkFromProductResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -445,22 +476,24 @@ type PossibleRejectReasonsResponse struct {
core.CommonResponse core.CommonResponse
// Certificate rejection reasons // Certificate rejection reasons
Result []struct { Result []PossibleRejectReasonsResult `json:"result"`
// Сode of a certificate rejection reason }
Code string `json:"code"`
// Description of a certificate rejection reason type PossibleRejectReasonsResult struct {
Name string `json:"name"` // Сode of a certificate rejection reason
} `json:"result"` Code string `json:"code"`
// Description of a certificate rejection reason
Name string `json:"name"`
} }
// Possible certificate rejection reasons // Possible certificate rejection reasons
func (c Certificates) PossibleRejectReasons() (*PossibleRejectReasonsResponse, error) { func (c Certificates) PossibleRejectReasons(ctx context.Context) (*PossibleRejectReasonsResponse, error) {
url := "/v1/product/certificate/rejection_reasons/list" url := "/v1/product/certificate/rejection_reasons/list"
resp := &PossibleRejectReasonsResponse{} resp := &PossibleRejectReasonsResponse{}
response, err := c.client.Request(http.MethodPost, url, nil, 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
} }
@@ -473,21 +506,23 @@ type PossibleStatusesResponse struct {
core.CommonResponse core.CommonResponse
// Possible certificate statuses // Possible certificate statuses
Result []struct { Result []PossibleStatusesResult `json:"result"`
// Certificate status code
Code string `json:"code"`
// Status description
Name string `json:"name"`
} `json:"result"`
} }
func (c Certificates) PossibleStatuses() (*PossibleStatusesResponse, error) { type PossibleStatusesResult struct {
// Certificate status code
Code string `json:"code"`
// Status description
Name string `json:"name"`
}
func (c Certificates) PossibleStatuses(ctx context.Context) (*PossibleStatusesResponse, error) {
url := "/v1/product/certificate/status/list" url := "/v1/product/certificate/status/list"
resp := &PossibleStatusesResponse{} resp := &PossibleStatusesResponse{}
response, err := c.client.Request(http.MethodPost, url, nil, 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
} }
@@ -526,12 +561,12 @@ type AddCertificatesForProductsResponse struct {
} }
// Adding certificates for products // Adding certificates for products
func (c Certificates) AddForProducts(params *AddCertificatesForProductsParams) (*AddCertificatesForProductsResponse, error) { func (c Certificates) AddForProducts(ctx context.Context, params *AddCertificatesForProductsParams) (*AddCertificatesForProductsResponse, error) {
url := "/v1/product/certificate/create" url := "/v1/product/certificate/create"
resp := &AddCertificatesForProductsResponse{} resp := &AddCertificatesForProductsResponse{}
response, err := c.client.Request(http.MethodPost, url, nil, resp, map[string]string{ response, err := c.client.Request(ctx, http.MethodPost, url, nil, resp, map[string]string{
"Content-Type": "multipart/form-data", "Content-Type": "multipart/form-data",
}) })
if err != nil { if err != nil {

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
"time" "time"
@@ -51,11 +52,15 @@ func TestListOfAccordanceTypes(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().ListOfAccordanceTypes() ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().ListOfAccordanceTypes(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListOfAccordanceTypesResponse{})
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)
} }
@@ -113,11 +118,15 @@ func TestDirectoryOfDocumentTypes(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().DirectoryOfDocumentTypes() ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().DirectoryOfDocumentTypes(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &DirectoryOfDocumentTypesResponse{})
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)
} }
@@ -142,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
@@ -168,11 +178,15 @@ func TestListOfCertifiedCategories(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().ListOfCertifiedCategories(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().ListOfCertifiedCategories(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListOfCertifiedCategoriesResponse{})
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)
} }
@@ -215,11 +229,15 @@ func TestLinkCertificateToProduct(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().LinkToProduct(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().LinkToProduct(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &LinkCertificateToProductResponse{})
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)
} }
@@ -264,11 +282,15 @@ func TestDeleteCertificate(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().Delete(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().Delete(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &DeleteCertificateResponse{})
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)
} }
@@ -322,11 +344,15 @@ func TestGetCertificateInfo(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().GetInfo(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().GetInfo(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetCertificateInfoResponse{})
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)
} }
@@ -395,11 +421,15 @@ func TestListCertificates(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().List(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().List(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListCertificatesResponse{})
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)
} }
@@ -441,11 +471,15 @@ func TestProductStatuses(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().ProductStatuses() ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().ProductStatuses(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ProductStatusesResponse{})
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)
} }
@@ -498,11 +532,15 @@ func TestListProductsForCertificate(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().ListProductsForCertificate(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().ListProductsForCertificate(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListProductsForCertificateResponse{})
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)
} }
@@ -551,11 +589,15 @@ func TestUnlinkFromProduct(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().UnlinkFromProduct(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().UnlinkFromProduct(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &UnlinkFromProductResponse{})
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)
} }
@@ -597,11 +639,15 @@ func TestPossibleRejectReasons(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().PossibleRejectReasons() ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().PossibleRejectReasons(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &PossibleRejectReasonsResponse{})
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)
} }
@@ -643,11 +689,15 @@ func TestPossibleStatuses(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().PossibleStatuses() ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().PossibleStatuses(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &PossibleStatusesResponse{})
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)
} }
@@ -695,11 +745,15 @@ func TestAddCertificatesForProducts(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Certificates().AddForProducts(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Certificates().AddForProducts(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &AddCertificatesForProductsResponse{})
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)
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"time" "time"
@@ -13,14 +14,14 @@ type Chats struct {
type ListChatsParams struct { type ListChatsParams struct {
// Chats filter // Chats filter
Filter ListChatsFilter `json:"filter"` Filter *ListChatsFilter `json:"filter,omitempty"`
// Number of values in the response. Default value is 1 // Number of values in the response. The default value is 30. The maximum value is 1000
Limit int64 `json:"limit" default:"1"` Limit int64 `json:"limit" default:"30"`
// Number of elements that will be skipped in the response. // Number of elements that will be skipped in the response.
// For example, if offset=10, the response will start with the 11th element found // For example, if offset=10, the response will start with the 11th element found
Offset int64 `json:"offset"` Offset int64 `json:"offset,omitempty"`
} }
type ListChatsFilter struct { type ListChatsFilter struct {
@@ -38,36 +39,7 @@ type ListChatsResponse struct {
core.CommonResponse core.CommonResponse
// Chats data // Chats data
Chats []struct { Chats []ListChatsChatData `json:"chats"`
// Chat data
Chat struct {
// Chat identifier
ChatId string `json:"chat_id"`
// Chat status:
// - All
// - Opened
// - Closed
ChatStatus string `json:"chat_status"`
// Chat type:
// - Seller_Support — support chat
// - Buyer_Seller — chat with a customer
ChatType string `json:"chat_type"`
// Chat creation date
CreatedAt time.Time `json:"created_at"`
} `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"`
} `json:"chats"`
// Total number of chats // Total number of chats
TotalChatsCount int64 `json:"total_chats_count"` TotalChatsCount int64 `json:"total_chats_count"`
@@ -76,13 +48,41 @@ type ListChatsResponse struct {
TotalUnreadCount int64 `json:"total_unread_count"` TotalUnreadCount int64 `json:"total_unread_count"`
} }
type ListChatsChatData struct {
// Chat identifier
ChatId string `json:"chat_id"`
// Chat status:
// - All
// - Opened
// - Closed
ChatStatus string `json:"chat_status"`
// Chat type:
// - Seller_Support — support chat
// - Buyer_Seller — chat with a customer
ChatType string `json:"chat_type"`
// Chat creation date
CreatedAt time.Time `json:"created_at"`
// 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
func (c Chats) List(params *ListChatsParams) (*ListChatsResponse, error) { func (c Chats) List(ctx context.Context, params *ListChatsParams) (*ListChatsResponse, error) {
url := "/v2/chat/list" url := "/v2/chat/list"
resp := &ListChatsResponse{} resp := &ListChatsResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -107,12 +107,12 @@ type SendMessageResponse struct {
} }
// Sends a message to an existing chat by its identifier // Sends a message to an existing chat by its identifier
func (c Chats) SendMessage(params *SendMessageParams) (*SendMessageResponse, error) { func (c Chats) SendMessage(ctx context.Context, params *SendMessageParams) (*SendMessageResponse, error) {
url := "/v1/chat/send/message" url := "/v1/chat/send/message"
resp := &SendMessageResponse{} resp := &SendMessageResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -140,12 +140,12 @@ type SendFileResponse struct {
} }
// Sends a file to an existing chat by its identifier // Sends a file to an existing chat by its identifier
func (c Chats) SendFile(params *SendFileParams) (*SendFileResponse, error) { func (c Chats) SendFile(ctx context.Context, params *SendFileParams) (*SendFileResponse, error) {
url := "/v1/chat/send/file" url := "/v1/chat/send/file"
resp := &SendFileResponse{} resp := &SendFileResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -168,7 +168,7 @@ type ChatHistoryParams struct {
// Default value is the last visible message // Default value is the last visible message
FromMessageId string `json:"from_message_id"` FromMessageId string `json:"from_message_id"`
// Number of messages in the response. The default value is 50 // Number of messages in the response. The default value is 50. The maximum value is 1000
Limit int64 `json:"limit" default:"50"` Limit int64 `json:"limit" default:"50"`
} }
@@ -179,42 +179,46 @@ type ChatHistoryResponse struct {
HasNext bool `json:"has_next"` HasNext bool `json:"has_next"`
// An array of messages sorted according to the direction parameter in the request body // An array of messages sorted according to the direction parameter in the request body
Messages []struct { Messages []ChatHistoryMessage `json:"messages"`
// Message creation date
CreatedAt time.Time `json:"created_at"`
// Array with message content in Markdown format
Data []string `json:"data"`
// Indication of the read message
IsRead bool `json:"is_read"`
// Message identifier
MessageId string `json:"message_id"`
// Chat participant identifier
User struct {
// Chat participant identifier
Id string `json:"id"`
// Chat participant type:
// - customer
// - seller
// - crm—system messages
// - courier
// - support
Type string `json:"type"`
} `json:"user"`
} `json:"messages"`
} }
// Chat history type ChatHistoryMessage struct {
func (c Chats) History(params *ChatHistoryParams) (*ChatHistoryResponse, error) { // Message creation date
CreatedAt time.Time `json:"created_at"`
// Array with message content in Markdown format
Data []string `json:"data"`
// Indication of the read message
IsRead bool `json:"is_read"`
// Message identifier
MessageId string `json:"message_id"`
// Chat participant identifier
User ChatHistoryMessageUser `json:"user"`
}
type ChatHistoryMessageUser struct {
// Chat participant identifier
Id string `json:"id"`
// Chat participant type:
// - customer
// - seller
// - crm—system messages
// - courier
// - support
Type string `json:"type"`
}
// Returns the history of chat messages. By default messages are shown from newest to oldest.
func (c Chats) History(ctx context.Context, params *ChatHistoryParams) (*ChatHistoryResponse, error) {
url := "/v2/chat/history" url := "/v2/chat/history"
resp := &ChatHistoryResponse{} resp := &ChatHistoryResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -231,96 +235,110 @@ 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 {
core.CommonResponse core.CommonResponse
// Method result // Method result
Result []struct { Result []UpdateChatResult `json:"result"`
// An order or a product user wrote about in the chat }
Context struct {
// Product inforamtion
Item struct {
// Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
} `json:"item"`
// Order information type UpdateChatResult struct {
Order struct { // An order or a product user wrote about in the chat
// Order number Context UpdateChatResultContext `json:"context"`
OrderNumber string `json:"order_number"`
// Shipment information // Creation date and time
Postings []struct { CreatedAt time.Time `json:"created_at"`
// Delivery scheme:
// - FBO
// - FBS
// - RFBS
// - Crossborder
DeliverySchema string `json:"delivery_schema"`
// Shipment number // Information about the file in the chat. Displayed only for `type = file`
PostingNumber string `json:"posting_number"` File UpdateChatResultFile `json:"file"`
// List of product identifiers in the shipment // File identifier
SKUList []int64 `json:"sku_list"` Id uint64 `json:"id"`
} `json:"postings"`
} `json:"order"`
} `json:"context"`
// Creation date and time // Message. Displayed only for `type = text`
CreatedAt time.Time `json:"created_at"` Text string `json:"text"`
// Information about the file in the chat. Displayed only for `type = file` // Message type:
File struct { // - text
// File type // - file
Mime string `json:"mime"` Type string `json:"type"`
// File name // Chat participant information
Name string `json:"name"` User UpdateChatResultUser `json:"user"`
}
// File size in bytes type UpdateChatResultContext struct {
Size int64 `json:"size"` // Product inforamtion
Item UpdateChatResultContextItem `json:"item"`
// File URL // Order information
URL string `json:"url"` Order UpdateChatResultContextOrder `json:"order"`
} `json:"file"` }
// File identifier type UpdateChatResultContextItem struct {
Id uint64 `json:"id"` // Product identifier in the Ozon system, SKU
SKU int64 `json:"sku"`
}
// Message. Displayed only for `type = text` type UpdateChatResultContextOrder struct {
Text string `json:"text"` // Order number
OrderNumber string `json:"order_number"`
// Message type: // Shipment information
// - text Postings []UpdateChatResultContextOrderPosting `json:"postings"`
// - file }
Type string `json:"type"`
// Chat participant information type UpdateChatResultContextOrderPosting struct {
User struct { // Delivery scheme:
// Chat participant identifier // - FBO
Id string `json:"id"` // - FBS
// - RFBS
// - Crossborder
DeliverySchema string `json:"delivery_schema"`
// Chat participant chat: // Shipment number
// - customer PostingNumber string `json:"posting_number"`
// - seller
// - crm—system messages // List of product identifiers in the shipment
// - courier SKUList []int64 `json:"sku_list"`
Type string `json:"type"` }
} `json:"user"`
} `json:"result"` type UpdateChatResultFile struct {
// File type
Mime string `json:"mime"`
// File name
Name string `json:"name"`
// File size in bytes
Size int64 `json:"size"`
// File URL
URL string `json:"url"`
}
type UpdateChatResultUser struct {
// Chat participant identifier
Id string `json:"id"`
// Chat participant chat:
// - customer
// - seller
// - crm—system messages
// - courier
Type string `json:"type"`
} }
// Update chat // Update chat
func (c Chats) Update(params *UpdateChatParams) (*UpdateChatResponse, error) { func (c Chats) Update(ctx context.Context, params *UpdateChatParams) (*UpdateChatResponse, error) {
url := "/v1/chat/updates" url := "/v1/chat/updates"
resp := &UpdateChatResponse{} resp := &UpdateChatResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -338,19 +356,21 @@ type CreateNewChatResponse struct {
core.CommonResponse core.CommonResponse
//Method result //Method result
Result struct { Result CreateNewChatResult `json:"result"`
// Chat identifier }
ChatId string `json:"chat_id"`
} `json:"result"` type CreateNewChatResult struct {
// Chat identifier
ChatId string `json:"chat_id"`
} }
// Creates a new chat on the shipment with the customer. For example, to clarify the address or the product model // Creates a new chat on the shipment with the customer. For example, to clarify the address or the product model
func (c Chats) Create(params *CreateNewChatParams) (*CreateNewChatResponse, error) { func (c Chats) Create(ctx context.Context, params *CreateNewChatParams) (*CreateNewChatResponse, error) {
url := "/v1/chat/start" url := "/v1/chat/start"
resp := &CreateNewChatResponse{} resp := &CreateNewChatResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -361,7 +381,7 @@ func (c Chats) Create(params *CreateNewChatParams) (*CreateNewChatResponse, erro
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"`
@@ -375,12 +395,12 @@ type MarkAsReadResponse struct {
} }
// A method for marking the selected message and messages before it as read // A method for marking the selected message and messages before it as read
func (c Chats) MarkAsRead(params *MarkAsReadParams) (*MarkAsReadResponse, error) { func (c Chats) MarkAsRead(ctx context.Context, params *MarkAsReadParams) (*MarkAsReadResponse, error) {
url := "/v2/chat/read" url := "/v2/chat/read"
resp := &MarkAsReadResponse{} resp := &MarkAsReadResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -21,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,
}, },
@@ -31,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,
@@ -61,21 +60,25 @@ func TestListChats(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Chats().List(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Chats().List(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListChatsResponse{})
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 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")
} }
} }
@@ -119,11 +122,15 @@ func TestSendMessage(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Chats().SendMessage(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Chats().SendMessage(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &SendMessageResponse{})
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)
} }
@@ -167,11 +174,15 @@ func TestSendFile(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Chats().SendFile(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Chats().SendFile(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &SendFileResponse{})
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)
} }
@@ -230,11 +241,15 @@ func TestChatHistory(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Chats().History(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Chats().History(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ChatHistoryResponse{})
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)
} }
@@ -296,11 +311,15 @@ func TestUpdateChat(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Chats().Update(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Chats().Update(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &UpdateChatResponse{})
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)
} }
@@ -344,11 +363,15 @@ func TestCreateNewChat(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Chats().Create(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Chats().Create(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &CreateNewChatResponse{})
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)
} }
@@ -375,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,
}, },
`{ `{
@@ -397,11 +420,15 @@ func TestMarkAsRead(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Chats().MarkAsRead(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Chats().MarkAsRead(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &MarkAsReadResponse{})
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)
} }

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,5 +1,13 @@
package ozon package ozon
import (
"time"
)
const (
testTimeout = 5 * time.Second
)
type Order string type Order string
const ( const (
@@ -20,30 +28,24 @@ const (
type GetAnalyticsDataFilterMetric string type GetAnalyticsDataFilterMetric string
const ( const (
UnknownMetric GetAnalyticsDataFilterMetric = "unknown_metric" UnknownMetric GetAnalyticsDataFilterMetric = "unknown_metric"
HitsViewSearch GetAnalyticsDataFilterMetric = "hits_view_search" HitsViewSearch GetAnalyticsDataFilterMetric = "hits_view_search"
HistViewPDP GetAnalyticsDataFilterMetric = "hits_view_pdp" HistViewPDP GetAnalyticsDataFilterMetric = "hits_view_pdp"
HitsView GetAnalyticsDataFilterMetric = "hist_view" HitsView GetAnalyticsDataFilterMetric = "hist_view"
HitsToCartSearch GetAnalyticsDataFilterMetric = "hits_tocart_search" HitsToCartSearch GetAnalyticsDataFilterMetric = "hits_tocart_search"
HitsToCartPDP GetAnalyticsDataFilterMetric = "hits_tocart_pdp" HitsToCartPDP GetAnalyticsDataFilterMetric = "hits_tocart_pdp"
SessionViewSearch GetAnalyticsDataFilterMetric = "session_view_search" SessionViewSearch GetAnalyticsDataFilterMetric = "session_view_search"
SessionViewPDP GetAnalyticsDataFilterMetric = "session_view_pdp" SessionViewPDP GetAnalyticsDataFilterMetric = "session_view_pdp"
SessionView GetAnalyticsDataFilterMetric = "session_view" SessionView GetAnalyticsDataFilterMetric = "session_view"
ConvToCartSearch GetAnalyticsDataFilterMetric = "conv_tocart_search" ConvToCartSearch GetAnalyticsDataFilterMetric = "conv_tocart_search"
ConvToCartPDP GetAnalyticsDataFilterMetric = "conv_tocart_pdp" ConvToCartPDP GetAnalyticsDataFilterMetric = "conv_tocart_pdp"
ConvToCart GetAnalyticsDataFilterMetric = "conv_tocart" ConvToCart GetAnalyticsDataFilterMetric = "conv_tocart"
Revenue GetAnalyticsDataFilterMetric = "revenue" Revenue GetAnalyticsDataFilterMetric = "revenue"
ReturnsMetric GetAnalyticsDataFilterMetric = "returns" ReturnsMetric GetAnalyticsDataFilterMetric = "returns"
CancellationsMetric GetAnalyticsDataFilterMetric = "cancellations" CancellationsMetric GetAnalyticsDataFilterMetric = "cancellations"
OrderedUnits GetAnalyticsDataFilterMetric = "ordered_units" OrderedUnits GetAnalyticsDataFilterMetric = "ordered_units"
DeliveredUnits GetAnalyticsDataFilterMetric = "delivered_units" DeliveredUnits GetAnalyticsDataFilterMetric = "delivered_units"
AdvViewPDP GetAnalyticsDataFilterMetric = "adv_view_pdp" PositionCategory GetAnalyticsDataFilterMetric = "position_category"
AdvViewSearchCategory GetAnalyticsDataFilterMetric = "adv_view_search_category"
AdvViewAll GetAnalyticsDataFilterMetric = "adv_view_all"
AdvSumAll GetAnalyticsDataFilterMetric = "adv_sum_all"
PositionCategory GetAnalyticsDataFilterMetric = "position_category"
PostingsMetric GetAnalyticsDataFilterMetric = "postings"
PostingsPremium GetAnalyticsDataFilterMetric = "postings_premium"
) )
type WarehouseType string type WarehouseType string
@@ -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"
) )
@@ -377,3 +364,572 @@ const (
StrategyCreated StrategyUpdateType = "strategyCreated" StrategyCreated StrategyUpdateType = "strategyCreated"
StrategyItemsListChanged StrategyUpdateType = "strategyItemsListChanged" StrategyItemsListChanged StrategyUpdateType = "strategyItemsListChanged"
) )
type ShipmentCertificateFilterStatus string
const (
// new
ShitmentCertificateFilterNew ShipmentCertificateFilterStatus = "new"
// retry creation
ShitmentCertificateFilterAwaitingRetry ShipmentCertificateFilterStatus = "awaiting-retry"
// is being packaged
ShitmentCertificateFilterInProcess ShipmentCertificateFilterStatus = "in_process"
// created
ShitmentCertificateFilterSuccess ShipmentCertificateFilterStatus = "success"
// creation error
ShitmentCertificateFilterError ShipmentCertificateFilterStatus = "error"
// sent
ShitmentCertificateFilterSend ShipmentCertificateFilterStatus = "sent"
// received
ShitmentCertificateFilterReceived ShipmentCertificateFilterStatus = "received"
// packaged
ShitmentCertificateFilterFormed ShipmentCertificateFilterStatus = "formed"
// canceled
ShitmentCertificateFilterCancelled ShipmentCertificateFilterStatus = "cancelled"
// in the queue for packaging
ShitmentCertificateFilterPending ShipmentCertificateFilterStatus = "pending"
// in the queue for completion
ShitmentCertificateFilterCompletionEnqueued ShipmentCertificateFilterStatus = "completion_enqueued"
// in the process of completion
ShitmentCertificateFilterCompletionProcessing ShipmentCertificateFilterStatus = "completion_processing"
// completion error
ShitmentCertificateFilterCompletionFailed ShipmentCertificateFilterStatus = "completion_failed"
// in the queue for cancellation
ShitmentCertificateFilterCancelationEnqueued ShipmentCertificateFilterStatus = "cancelation_enqueued"
// in the process of cancellation
ShitmentCertificateFilterCancelationProcessing ShipmentCertificateFilterStatus = "cancelation_processing"
// cancellation error
ShitmentCertificateFilterCancelationFailed ShipmentCertificateFilterStatus = "cancelation_failed"
// completed
ShitmentCertificateFilterCompleted ShipmentCertificateFilterStatus = "completed"
// closed
ShitmentCertificateFilterClosed ShipmentCertificateFilterStatus = "closed"
)
type PRROptionStatus string
const (
// carrying the bulky product using the elevator
PRROptionLift PRROptionStatus = "lift"
// carrying the bulky product upstairs
PRROptionStairs PRROptionStatus = "stairs"
// the customer canceled the service,
// you don't need to lift the shipment
PRROptionNone PRROptionStatus = "none"
// delivery is included in the price.
// According to the offer you need to
// deliver products to the floor
PRROptionDeliveryDefault PRROptionStatus = "delivery_default"
)
type GetFBSReturnsFilterStatus string
const (
Moving GetFBSReturnsFilterStatus = "moving"
ReturnedToSeller GetFBSReturnsFilterStatus = "returned_to_seller"
WaitingForSeller GetFBSReturnsFilterStatus = "waiting_for_seller"
AcceptedFromCustomer GetFBSReturnsFilterStatus = "accepted_from_customer"
CancelledWithCompensation GetFBSReturnsFilterStatus = "cancelled_with_compensation"
ReadyForShipment GetFBSReturnsFilterStatus = "ready_for_shipment"
Disposing GetFBSReturnsFilterStatus = "disposing"
Disposed GetFBSReturnsFilterStatus = "disposed"
ArrivedForResale GetFBSReturnsFilterStatus = "arrived_for_resale"
MovingToResale GetFBSReturnsFilterStatus = "moving_to_resale"
)
type GetFBOReturnsFilterStatus string
const (
GetFBOReturnsFilterStatusCreated GetFBOReturnsFilterStatus = "Created"
GetFBOReturnsFilterStatusReturnedToOzon GetFBOReturnsFilterStatus = "ReturnedToOzon"
GetFBOReturnsFilterStatusCancelled GetFBOReturnsFilterStatus = "Cancelled"
GetFBOReturnsFilterStatusCancelledWithCompensation GetFBOReturnsFilterStatus = "CancelledWithCompensation"
)
type GetFBOReturnsReturnStatus string
const (
GetFBOReturnsReturnStatusCancelled GetFBOReturnsReturnStatus = "Возврат отменен"
GetFBOReturnsReturnStatusAcceptedFromCustomer GetFBOReturnsReturnStatus = "Принят от покупателя"
GetFBOReturnsReturnStatusReceivedAtOzon GetFBOReturnsReturnStatus = "Получен в Ozon"
)
type DigitalActType string
const (
// acceptance certificate
DigitalActTypeOfAcceptance DigitalActType = "act_of_acceptance"
// discrepancy certificate
DigitalActTypeOfMismatch DigitalActType = "act_of_mismatch"
// surplus certificate
DigitalActTypeOfExcess DigitalActType = "act_of_excess"
)
type PriceStrategy string
const (
// enable
PriceStrategyEnabled PriceStrategy = "ENABLED"
// disable
PriceStrategyDisabled PriceStrategy = "DISABLED"
// don't change anything. Default value
PriceStrategyUnknown PriceStrategy = "UNKNOWN"
)
type FBPFilter string
const (
// all shipments matching other filters will be returned in the response
FBPFilterAll FBPFilter = "all"
// only FBP shipments will be returned
FBPFilterOnly FBPFilter = "only"
// all shipments except FBP will be returned
FBPFilterWithout FBPFilter = "without"
)
type InvoiceCurrency string
const (
// dollar
InvoiceCurrencyUSD InvoiceCurrency = "USD"
// euro
InvoiceCurrencyEUR InvoiceCurrency = "EUR"
// Turkish lira
InvoiceCurrencyTRY InvoiceCurrency = "TRY"
// yuan
InvoiceCurrencyCNY InvoiceCurrency = "CNY"
// ruble
InvoiceCurrencyRUB InvoiceCurrency = "RUB"
// pound sterling
InvoiceCurrencyGBP InvoiceCurrency = "GBP"
)
type ReportType string
const (
// products report
ReportTypeSellerProducts ReportType = "SELLER_PRODUCTS"
// transactions report
ReportTypeSellerTransactions ReportType = "SELLER_TRANSACTIONS"
// product prices report
ReportTypeSellerProductPrices ReportType = "SELLER_PRODUCT_PRICES"
// stocks report
ReportTypeSellerStock ReportType = "SELLER_STOCK"
// products movement report
ReportTypeSellerProductMovement ReportType = "SELLER_PRODUCT_MOVEMENT"
// returns report
ReportTypeSellerReturns ReportType = "SELLER_RETURNS"
// shipments report
ReportTypeSellerPostings ReportType = "SELLER_POSTINGS"
// financial report
ReportTypeSellerFinance ReportType = "SELLER_FINANCE"
// report on sales to legal entities
ReportTypeDocB2BSales ReportType = "DOCUMENT_B2B_SALES"
// settlement report
ReportTypeMutualSettlement ReportType = "MUTUAL_SETTLEMENT"
)
type ReportInfoStatus string
const (
ReportInfoWaiting ReportInfoStatus = "waiting"
ReportInfoProcessing ReportInfoStatus = "processing"
ReportInfoSuccess ReportInfoStatus = "success"
ReportInfoFailed ReportInfoStatus = "failed"
)
type SKUAvailability string
const (
SKUAvailabilityHidden = "HIDDEN"
SKUAvailabilityAvailable = "AVAILABLE"
// SKU is deleted
SKUAvailabilityUnavailable = "UNAVAILABLE"
)
type RFBSReturnsGroupState string
const (
// All requests
RFBSReturnsGroupStateAll RFBSReturnsGroupState = "All"
// New
RFBSReturnsGroupStateNew RFBSReturnsGroupState = "New"
// Returned product is on the way for check
RFBSReturnsGroupStateDelivering RFBSReturnsGroupState = "Delivering"
// Returned product is being checked
RFBSReturnsGroupStateCheckout RFBSReturnsGroupState = "Checkout"
// Disputed
RFBSReturnsGroupStateArbitration RFBSReturnsGroupState = "Arbitration"
// Approved
RFBSReturnsGroupStateApproved RFBSReturnsGroupState = "Approved"
// Rejected
RFBSReturnsGroupStateRejected RFBSReturnsGroupState = "Rejected"
)
type GetRFBSReturnsCurrency string
const (
// Russian ruble
GetRFBSReturnsCurrencyRUB GetRFBSReturnsCurrency = "RUB"
// Belarusian ruble
GetRFBSReturnsCurrencyBYN GetRFBSReturnsCurrency = "BYN"
// Tenge
GetRFBSReturnsCurrencyKZT GetRFBSReturnsCurrency = "KZT"
// Euro
GetRFBSReturnsCurrencyEUR GetRFBSReturnsCurrency = "EUR"
// US dollar
GetRFBSReturnsCurrencyUSD GetRFBSReturnsCurrency = "USD"
// Yuan
GetRFBSReturnsCurrencyCNY GetRFBSReturnsCurrency = "CNY"
)
type GiveoutStatus string
const (
// Undefined, contact support team
GiveoutStatusUnspecified GiveoutStatus = "GIVEOUT_STATUS_UNSPECIFIED"
// Created
GiveoutStatusCreated GiveoutStatus = "GIVEOUT_STATUS_CREATED"
// Approved
GiveoutStatusApproved GiveoutStatus = "GIVEOUT_STATUS_APPROVED"
// Completed
GiveoutStatusCompleted GiveoutStatus = "GIVEOUT_STATUS_COMPLETED"
// Cancelled
GiveoutStatusCancelled GiveoutStatus = "GIVEOUT_STATUS_CANCELLED"
)
type GiveoutDeliverySchema string
const (
// Undefined, contact support team
GiveoutDeliverySchemaUnspecified GiveoutDeliverySchema = "GIVEOUT_DELIVERY_SCHEMA_UNSPECIFIED"
// FBO
GiveoutDeliverySchemaFBO GiveoutDeliverySchema = "GIVEOUT_DELIVERY_SCHEMA_FBO"
// FBS
GiveoutDeliverySchemaFBS GiveoutDeliverySchema = "GIVEOUT_DELIVERY_SCHEMA_FBS"
)
type MandatoryMarkStatus string
const (
// Labeling is processed
MandatoryMarkStatusProcessing MandatoryMarkStatus = "processing"
// Check is passed
MandatoryMarkStatusPassed MandatoryMarkStatus = "passed"
// Check is failed
MandatoryMarkStatusFailed MandatoryMarkStatus = "failed"
)
type GetCarriageStatus string
const (
// acceptance in progress
GetCarriageStatusReceived GetCarriageStatus = "received"
// closed after acceptance
GetCarriageStatusClosed GetCarriageStatus = "closed"
GetCarriageStatusSended GetCarriageStatus = "sended"
GetCarriageStatusCancelled GetCarriageStatus = "cancelled"
)
type TransactionOperationService string
const (
// return of unclaimed products from the customer to the warehouse
TransactionNotDelivered TransactionOperationService = "MarketplaceNotDeliveredCostItem"
// return from the customer to the warehouse after delivery
TransactionReturnAfterDelivery TransactionOperationService = "TransactionOperationServiceNotDelivered"
// product delivery to the customer
TransactionDelivery TransactionOperationService = "MarketplaceDeliveryCostItem"
// purchasing reviews on the platform
TransactionSaleReviews TransactionOperationService = "MarketplaceSaleReviewsItem"
// products delivery to the Ozon warehouse (cross docking)
TransactionItemAdForSupplierLogistic TransactionOperationService = "ItemAdvertisementForSupplierLogistic"
// product placement service
TransactionServiceStorageItem TransactionOperationService = "OperationMarketplaceServiceStorage"
// products promotion
TransactionMarketingActionCost TransactionOperationService = "MarketplaceMarketingActionCostItem"
// promotion and selling on an instalment plan
TransactionServiceItemInstallment TransactionOperationService = "MarketplaceServiceItemInstallment"
// mandatory products labeling
TransactionServiceMarkingItems TransactionOperationService = "MarketplaceServiceItemMarkingItems"
// flexible payment schedule
TransactionServiceFlexiblePaymentSchedule TransactionOperationService = "MarketplaceServiceItemFlexiblePaymentSchedule"
// picking up products for removal by the seller
TransactionServiceReturnFromStock TransactionOperationService = "MarketplaceServiceItemReturnFromStock"
TransactionServiceStarsMembership TransactionOperationService = "ItemAgentServiceStarsMembership"
// forwarding trade
TransactionItemAdForSupplierLogisticSeller TransactionOperationService = "ItemAdvertisementForSupplierLogisticSeller"
// last mile
TransactionServiceDeliveryToCustomer TransactionOperationService = "MarketplaceServiceItemDelivToCustomer"
// pipeline
TransactionServiceDirectFlowTrans TransactionOperationService = "MarketplaceServiceItemDirectFlowTrans"
// shipment processing
TransactionServiceDropoffFF TransactionOperationService = "MarketplaceServiceItemDropoffFF"
// shipment processing
TransactionServiceDropoffPVZ TransactionOperationService = "MarketplaceServiceItemDropoffPVZ"
// shipment processing
TransactionServiceDropoffSC TransactionOperationService = "MarketplaceServiceItemDropoffSC"
// order packaging
TransactionServiceFulfillment TransactionOperationService = "MarketplaceServiceItemFulfillment"
// picking products up by car from the seller's address (Pick-up)
TransactionServicePickup TransactionOperationService = "MarketplaceServiceItemPickup"
// return processing
TransactionServiceReturnAfterDelivToCustomer TransactionOperationService = "MarketplaceServiceItemReturnAfterDelivToCustomer"
// reverse pipeline
TransactionServiceReturnFlowTrans TransactionOperationService = "MarketplaceServiceItemReturnFlowTrans"
// cancellation processing
TransactionServiceReturnNotDelivToCustomer TransactionOperationService = "MarketplaceServiceItemReturnNotDelivToCustomer"
// unredeemed order processing
TransactionServiceReturnPartGoodsCustomer TransactionOperationService = "MarketplaceServiceItemReturnPartGoodsCustomer"
// acquiring payment
TransactionRedistributionOfAcquiringOperation TransactionOperationService = "MarketplaceRedistributionOfAcquiringOperation"
// FBS return short-term placement
TransactionServiceAtPickupPointFBS TransactionOperationService = "MarketplaceReturnStorageServiceAtThePickupPointFbsItem"
// FBS return long-term placement
TransactionServiceInWarehouseFBS TransactionOperationService = "MarketplaceReturnStorageServiceInTheWarehouseFbsItem"
// bulky products delivery
TransactionServiceDeliveryKGT TransactionOperationService = "MarketplaceServiceItemDeliveryKGT"
// logistics
TransactionServiceDirectFlowLogistic TransactionOperationService = "MarketplaceServiceItemDirectFlowLogistic"
// reverse logistics
TransactionServiceReturnFlowLogistic TransactionOperationService = "MarketplaceServiceItemReturnFlowLogistic"
// "Seller's Bonus" promotion service
TransactionServicePremiumCashbackIndPoints TransactionOperationService = "MarketplaceServicePremiumCashbackIndividualPoints"
// Premium promotion service, fixed commission
TransactionServicePremiumPromotion TransactionOperationService = "MarketplaceServicePremiumPromotion"
// withholding for product shortage
TransactionServiceWithHoldingForUndeliverableGoods TransactionOperationService = "OperationMarketplaceWithHoldingForUndeliverableGoods"
// drop-off service at the pick-up point
TransactionServiceDropoffPPZ TransactionOperationService = "MarketplaceServiceItemDropoffPPZ"
// reissue of returns at the pick-up point
TransactionServiceRedistributionReturnsPVZ TransactionOperationService = "MarketplaceServiceItemRedistributionReturnsPVZ"
// Agregator 3PL Globalagency service tariffication
TransactionServiceAgencyFeeAggregator3PLGlobal TransactionOperationService = "OperationMarketplaceAgencyFeeAggregator3PLGlobal"
)
type PaymentTypeGroupName string
const (
PaymentTypeGroupByCardOnline PaymentTypeGroupName = "by card online"
PaymentTypeGroupOzonCard PaymentTypeGroupName = "Ozon Card"
PaymentTypeGroupOzonCardAtCheckout PaymentTypeGroupName = "Ozon Card at checkout"
PaymentTypeGroupBySavedBankCardUponPickup PaymentTypeGroupName = "by saved bank card upon pick-up"
PaymentTypeGroupFasterPaymentSystem PaymentTypeGroupName = "Faster payment system"
PaymentTypeGroupOzonInstallment PaymentTypeGroupName = "Ozon Installment"
PaymentTypeGroupPaymentToCurrentAccount PaymentTypeGroupName = "payment to current account"
PaymentTypeGroupSberpay PaymentTypeGroupName = "Sberpay"
)
type VisualStatus string
const (
// dispute with the customer has been opened
VisualStatusDisputeOpened VisualStatus = "DisputeOpened"
// pending with the seller
VisualStatusOnSellerApproval VisualStatus = "OnSellerApproval"
// at the pick-up point
VisualStatusArrivedAtReturnPlace VisualStatus = "ArrivedAtReturnPlace"
// pending clarification by the seller
VisualStatusOnSellerClarification VisualStatus = "OnSellerClarification"
// pending clarification by the seller after partial compensation
VisualStatusOnSellerClarificationPartial VisualStatus = "OnSellerClarificationAfterPartialCompensation"
// partial compensation offered
VisualStatusOfferedPartial VisualStatus = "OfferedPartialCompensation"
// refund approved
VisualStatusReturnMoneyApproved VisualStatus = "ReturnMoneyApproved"
// partial compensation provided
VisualStatusPartialReturned VisualStatus = "PartialCompensationReturned"
// refund rejected, dispute isn't opened
VisualStatusCancelledDisputeNotOpen VisualStatus = "CancelledDisputeNotOpen"
// request rejected
VisualStatusRejected VisualStatus = "Rejected"
// request rejected by Ozon
VisualStatusCrmRejected VisualStatus = "CrmRejected"
// request canceled
VisualStatusCancelled VisualStatus = "Cancelled"
// request approved by the seller
VisualStatusApproved VisualStatus = "Approved"
// request approved by Ozon
VisualStatusApprovedByOzon VisualStatus = "ApprovedByOzon"
// seller received the return
VisualStatusReceivedBySeller VisualStatus = "ReceivedBySeller"
// return is on its way to the seller
VisualStatusMovingToSeller VisualStatus = "MovingToSeller"
// seller received the refund
VisualStatusReturnCompensated VisualStatus = "ReturnCompensated"
// courier is taking the return to the seller
VisualStatusReturningByCourier VisualStatus = "ReturningByCourier"
// on disposal
VisualStatusUtilizing VisualStatus = "Utilizing"
// disposed of
VisualStatusUtilized VisualStatus = "Utilized"
// customer received full refund
VisualStatusMoneyReturned VisualStatus = "MoneyReturned"
// partial refund has been approved
VisualStatusPartialInProcess VisualStatus = "PartialCompensationInProcess"
// seller opened a dispute
VisualStatusDisputeYouOpened VisualStatus = "DisputeYouOpened"
// compensation rejected
VisualStatusCompensationRejected VisualStatus = "CompensationRejected"
// support request sent
VisualStatusDisputeOpening VisualStatus = "DisputeOpening"
// awaiting your decision on compensation
VisualStatusCompensationOffered VisualStatus = "CompensationOffered"
// awaiting compensation
VisualStatusWaitingCompensation VisualStatus = "WaitingCompensation"
// an error occurred when sending the support request
VisualStatusSendingError VisualStatus = "SendingError"
// decision period has expired
VisualStatusCompensationRejectedBySla VisualStatus = "CompensationRejectedBySla"
// seller has refused compensation
VisualStatusCompensationRejectedBySeller VisualStatus = "CompensationRejectedBySeller"
// on the way to the Ozon warehouse
VisualStatusMovingToOzon VisualStatus = "MovingToOzon"
// arrived at the Ozon warehouse
VisualStatusReturnedToOzon VisualStatus = "ReturnedToOzon"
// quick refund
VisualStatusMoneyReturnedBySystem VisualStatus = "MoneyReturnedBySystem"
// awaiting shipping
VisualStatusWaitingShipment VisualStatus = "WaitingShipment"
)
type VAT string
const (
VAT0 VAT = "0"
VAT005 VAT = "0.05"
VAT007 VAT = "0.07"
VAT01 VAT = "0.1"
VAT02 VAT = "0.2"
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -22,55 +23,66 @@ 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": {
"contract_date": "string",
"contract_number": "string",
"currency_sys_name": "string",
"doc_amount": 0,
"doc_date": "string",
"number": "string",
"payer_inn": "string",
"payer_kpp": "string",
"payer_name": "string",
"receiver_inn": "string",
"receiver_kpp": "string",
"receiver_name": "string",
"start_date": "string",
"stop_date": "string",
"vat_amount": 0
},
"rows": [
{ {
"header": [ "commission_ratio": 0,
{ "delivery_commission": {
"doc_date": "2022-09-22", "amount": 0,
"num": "string", "bonus": 0,
"start_date": "2022-09-02", "commission": 0,
"stop_date": "2022-09-22", "compensation": 0,
"contract_date": "2022-09-02", "price_per_instance": 0,
"contract_num": "string", "quantity": 0,
"payer_name": "string", "standard_fee": 0,
"payer_inn": "string", "bank_coinvestment": 0,
"payer_kpp": "string", "stars": 0,
"rcv_name": "string", "total": 0
"rcv_inn": "string", },
"rcv_kpp": "string", "item": {
"doc_amount": 1, "barcode": "string",
"vat_amount": 1, "name": "string",
"currency_code": "string" "offer_id": "string",
} "sku": 0
], },
"rows": [ "return_commission": {
{ "amount": 0,
"row_number": 0, "bonus": 0,
"product_id": 0, "commission": 0,
"product_name": "string", "compensation": 0,
"offer_id": "string", "price_per_instance": 0,
"barcode": "string", "quantity": 0,
"price": 0, "standard_fee": 0,
"commission_percent": 0, "bank_coinvestment": 0,
"price_sale": 0, "stars": 0,
"sale_qty": 0, "total": 0
"sale_amount": 0, },
"sale_discount": 0, "rowNumber": 0,
"sale_commission": 0, "seller_price_per_instance": 0
"sale_price_seller": 0,
"return_sale": 0,
"return_qty": 0,
"return_amount": 0,
"return_discount": 0,
"return_commission": 0,
"return_price_seller": 0
}
]
} }
] ]
}
}`, }`,
"", "",
}, },
@@ -90,11 +102,15 @@ func TestReportOnSoldProducts(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Finance().ReportOnSoldProducts(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Finance().ReportOnSoldProducts(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ReportOnSoldProductsResponse{})
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)
} }
@@ -158,11 +174,15 @@ func TestGetTotalTransactionsSum(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Finance().GetTotalTransactionsSum(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Finance().GetTotalTransactionsSum(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetTotalTransactionsSumResponse{})
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)
} }
@@ -246,11 +266,139 @@ func TestListTransactions(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Finance().ListTransactions(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Finance().ListTransactions(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListTransactionsResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
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 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 { 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)
} }

View File

@@ -1,7 +1,9 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"time"
core "github.com/diphantxm/ozon-api-client" core "github.com/diphantxm/ozon-api-client"
) )
@@ -14,8 +16,31 @@ 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"`
// Product HS-codes
HSCodes []CreateUpdateProformaLinkHSCode `json:"hs_codes"`
// Invoice date
Date time.Time `json:"date"`
// Invoice number. The number can contain letters and digits, maximum length is 50 characters
Number string `json:"number"`
// Cost stated in the invoice. The fractional part is separated by decimal point, up to two digits after the decimal point
Price float64 `json:"price"`
// Invoice currency
PriceCurrency InvoiceCurrency `json:"price_currency" default:"USD"`
}
type CreateUpdateProformaLinkHSCode struct {
// Product HS code
Code string `json:"code"`
// Product identifier in the Ozon system, SKU
SKU string `json:"sku"`
} }
type CreateUpdateProformaLinkResponse struct { type CreateUpdateProformaLinkResponse struct {
@@ -25,13 +50,13 @@ 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(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{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -49,19 +74,40 @@ type GetProformaLinkResponse struct {
core.CommonResponse core.CommonResponse
// Method result // Method result
Result struct { Result GetProformaLinkResult `json:"result"`
// Proforma invoice link }
FileURL string `json:"file_url"`
} `json:"result"` type GetProformaLinkResult struct {
// Invoice uploading date
Date time.Time `json:"date"`
// Invoice link
FileURL string `json:"file_url"`
// Product HS-codes
HSCodes []CreateUpdateProformaLinkHSCode `json:"hs_codes"`
// Invoice number
Number string `json:"number"`
// Cost stated in the invoice.
// The fractional part is separated by decimal point,
// up to two digits after the decimal point.
//
// Example: 199.99
Price float64 `json:"price"`
// Invoice currency
PriceCurrency InvoiceCurrency `json:"price_currency"`
} }
// Get a proforma invoice link // Get a proforma invoice link
func (c Invoices) Get(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{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -82,12 +128,42 @@ type DeleteProformaLinkResponse struct {
Result bool `json:"result"` Result bool `json:"result"`
} }
func (c Invoices) Delete(params *DeleteProformaLinkParams) (*DeleteProformaLinkResponse, error) { func (c Invoices) Delete(ctx context.Context, params *DeleteProformaLinkParams) (*DeleteProformaLinkResponse, error) {
url := "/v1/invoice/delete" url := "/v1/invoice/delete"
resp := &DeleteProformaLinkResponse{} resp := &DeleteProformaLinkResponse{}
response, err := c.client.Request(http.MethodPost, url, params, 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 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 { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -21,8 +22,22 @@ func TestCreateUpdateProformaLink(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"},
&CreateUpdateProformaLinkParams{ &CreateUpdateProformaLinkParams{
PostingNumber: "posting number", PostingNumber: "33920146-0252-1",
URL: "link", URL: "https://cdn.ozone.ru/s3/ozon-disk-api/techdoc/seller-api/earsivfatura_1690960445.pdf",
HSCodes: []CreateUpdateProformaLinkHSCode{
{
Code: "534758761999",
SKU: "SKU123",
},
{
Code: "534758761000",
SKU: "SKU456",
},
},
Date: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2023-08-01T12:08:44.342Z"),
Number: "424fdsf234",
Price: 234.34,
PriceCurrency: InvoiceCurrencyRUB,
}, },
`{ `{
"result": true "result": true
@@ -43,11 +58,15 @@ func TestCreateUpdateProformaLink(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Invoices().CreateUpdate(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Invoices().CreateUpdate(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &CreateUpdateProformaLinkResponse{})
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)
} }
@@ -72,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"
} }
}`, }`,
}, },
@@ -91,11 +120,15 @@ func TestGetProformaLink(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Invoices().Get(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Invoices().Get(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetProformaLinkResponse{})
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)
} }
@@ -137,11 +170,66 @@ func TestDeleteProformaLink(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Invoices().Delete(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Invoices().Delete(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &DeleteProformaLinkResponse{})
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
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 { 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)
} }

View File

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

View File

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

View File

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

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

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

View File

@@ -10,6 +10,15 @@ const (
DefaultAPIBaseUrl = "https://api-seller.ozon.ru" DefaultAPIBaseUrl = "https://api-seller.ozon.ru"
) )
type ClientOptions struct {
client core.HttpClient
baseUri string
apiKey string
clientId string
}
type Client struct { type Client struct {
client *core.Client client *core.Client
@@ -31,6 +40,11 @@ type Client struct {
chats *Chats chats *Chats
certificates *Certificates certificates *Certificates
strategies *Strategies strategies *Strategies
barcodes *Barcodes
passes *Passes
clusters *Clusters
quants *Quants
reviews *Reviews
} }
func (c Client) Analytics() *Analytics { func (c Client) Analytics() *Analytics {
@@ -105,10 +119,66 @@ func (c Client) Strategies() *Strategies {
return c.strategies return c.strategies
} }
func NewClient(clientId, apiKey string) *Client { func (c Client) Barcodes() *Barcodes {
coreClient := core.NewClient(DefaultAPIBaseUrl, map[string]string{ return c.barcodes
"Client-Id": clientId, }
"Api-Key": apiKey,
func (c Client) Passes() *Passes {
return c.passes
}
func (c Client) Clusters() *Clusters {
return c.clusters
}
func (c Client) Quants() *Quants {
return c.quants
}
func (c Client) Reviews() *Reviews {
return c.reviews
}
type ClientOption func(c *ClientOptions)
func WithHttpClient(httpClient core.HttpClient) ClientOption {
return func(c *ClientOptions) {
c.client = httpClient
}
}
func WithURI(uri string) ClientOption {
return func(c *ClientOptions) {
c.baseUri = uri
}
}
func WithClientId(clientId string) ClientOption {
return func(c *ClientOptions) {
c.clientId = clientId
}
}
func WithAPIKey(apiKey string) ClientOption {
return func(c *ClientOptions) {
c.apiKey = apiKey
}
}
func NewClient(opts ...ClientOption) *Client {
// default values
options := &ClientOptions{
client: http.DefaultClient,
baseUri: DefaultAPIBaseUrl,
}
for _, opt := range opts {
opt(options)
}
coreClient := core.NewClient(options.client, options.baseUri, map[string]string{
"Client-Id": options.clientId,
"Api-Key": options.apiKey,
}) })
return &Client{ return &Client{
@@ -131,6 +201,11 @@ func NewClient(clientId, apiKey string) *Client {
chats: &Chats{client: coreClient}, chats: &Chats{client: coreClient},
certificates: &Certificates{client: coreClient}, certificates: &Certificates{client: coreClient},
strategies: &Strategies{client: coreClient}, strategies: &Strategies{client: coreClient},
barcodes: &Barcodes{client: coreClient},
passes: &Passes{client: coreClient},
clusters: &Clusters{client: coreClient},
quants: &Quants{client: coreClient},
reviews: &Reviews{client: coreClient},
} }
} }
@@ -157,5 +232,10 @@ func NewMockClient(handler http.HandlerFunc) *Client {
chats: &Chats{client: coreClient}, chats: &Chats{client: coreClient},
certificates: &Certificates{client: coreClient}, certificates: &Certificates{client: coreClient},
strategies: &Strategies{client: coreClient}, strategies: &Strategies{client: coreClient},
barcodes: &Barcodes{client: coreClient},
passes: &Passes{client: coreClient},
clusters: &Clusters{client: coreClient},
quants: &Quants{client: coreClient},
reviews: &Reviews{client: coreClient},
} }
} }

27
ozon/ozon_test.go Normal file
View File

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

302
ozon/pass.go Normal file
View File

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

431
ozon/pass_test.go Normal file
View File

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

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
core "github.com/diphantxm/ozon-api-client" core "github.com/diphantxm/ozon-api-client"
@@ -25,12 +26,12 @@ type CreateDeliveryPolygonResponse struct {
// You can link a polygon to the delivery method. // You can link a polygon to the delivery method.
// //
// Create a polygon getting its coordinates on https://geojson.io: mark at least 3 points on the map and connect them // Create a polygon getting its coordinates on https://geojson.io: mark at least 3 points on the map and connect them
func (c Polygons) CreateDelivery(params *CreateDeliveryPolygonParams) (*CreateDeliveryPolygonResponse, error) { func (c Polygons) CreateDelivery(ctx context.Context, params *CreateDeliveryPolygonParams) (*CreateDeliveryPolygonResponse, error) {
url := "/v1/polygon/create" url := "/v1/polygon/create"
resp := &CreateDeliveryPolygonResponse{} resp := &CreateDeliveryPolygonResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -71,36 +72,12 @@ type LinkDeliveryMethodToPolygonResponse struct {
} }
// Link delivery method to a delivery polygon // Link delivery method to a delivery polygon
func (c Polygons) Link(params *LinkDeliveryMethodToPolygonParams) (*LinkDeliveryMethodToPolygonResponse, error) { func (c Polygons) Link(ctx context.Context, params *LinkDeliveryMethodToPolygonParams) (*LinkDeliveryMethodToPolygonResponse, error) {
url := "/v1/polygon/bind" url := "/v1/polygon/bind"
resp := &LinkDeliveryMethodToPolygonResponse{} resp := &LinkDeliveryMethodToPolygonResponse{}
response, err := c.client.Request(http.MethodPost, url, params, 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 DeletePolygonParams struct {
// Polygons identifiers list
PolygonIds []int64 `json:"polygon_ids"`
}
type DeletePolygonResponse struct {
core.CommonResponse
}
// Delete polygon
func (c Polygons) Delete(params *DeletePolygonParams) (*DeletePolygonResponse, error) {
url := "/v1/polygon/delete"
resp := &DeletePolygonResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -24,7 +25,7 @@ func TestCreateDeliveryPolygon(t *testing.T) {
Coordinates: "[[[30.149574279785153,59.86550435303646],[30.21205902099609,59.846884387977326],[30.255661010742184,59.86240174913176],[30.149574279785153,59.86550435303646]]]", Coordinates: "[[[30.149574279785153,59.86550435303646],[30.21205902099609,59.846884387977326],[30.255661010742184,59.86240174913176],[30.149574279785153,59.86550435303646]]]",
}, },
`{ `{
"polygonId": "1323" "polygon_id": 1323
}`, }`,
}, },
// Test No Client-Id or Api-Key // Test No Client-Id or Api-Key
@@ -42,11 +43,15 @@ func TestCreateDeliveryPolygon(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Polygons().CreateDelivery(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Polygons().CreateDelivery(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &CreateDeliveryPolygonResponse{})
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)
} }
@@ -96,55 +101,15 @@ func TestLinkDeliveryMethodToPolygon(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Polygons().Link(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
if err != nil { resp, err := c.Polygons().Link(ctx, test.params)
t.Error(err)
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestDeletePolygon(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *DeletePolygonParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&DeletePolygonParams{
PolygonIds: []int64{1323},
},
`{}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&DeletePolygonParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Polygons().Delete(test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &LinkDeliveryMethodToPolygonResponse{})
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)
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

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

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

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -40,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
}`, }`,
@@ -58,11 +65,15 @@ func TestGetCurrentRatingInfo(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Rating().GetCurrentSellerRatingInfo() ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Rating().GetCurrentSellerRatingInfo(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetCurrentSellerRatingInfoResponse{})
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)
} }
@@ -146,11 +157,15 @@ func TestGetRatingInfoForPeriod(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Rating().GetSellerRatingInfoForPeriod(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Rating().GetSellerRatingInfoForPeriod(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetSellerRatingInfoPeriodResponse{})
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)
} }

View File

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

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -69,11 +70,15 @@ func TestGetList(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetList(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetList(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetReportsListResponse{})
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)
} }
@@ -111,7 +116,7 @@ func TestGetReportDetails(t *testing.T) {
"file": "https://storage.yandexcloud.net/ozon.reports/95/c1/95c1ae93320294cb.csv", "file": "https://storage.yandexcloud.net/ozon.reports/95/c1/95c1ae93320294cb.csv",
"report_type": "seller_products", "report_type": "seller_products",
"params": {}, "params": {},
"created_at": "2021-11-25T14:54:55.688260Z" "created_at": "2021-11-25T14:54:55.68826Z"
} }
}`, }`,
}, },
@@ -130,11 +135,15 @@ func TestGetReportDetails(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetReportDetails(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetReportDetails(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetReportDetailsResponse{})
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)
} }
@@ -255,8 +264,7 @@ func TestGetFinancialReport(t *testing.T) {
}, },
"end_balance_amount": 0 "end_balance_amount": 0
} }
}, }
"page_count": 15
}`, }`,
}, },
// Test No Client-Id or Api-Key // Test No Client-Id or Api-Key
@@ -274,11 +282,15 @@ func TestGetFinancialReport(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetFinancial(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetFinancial(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetFinancialReportResponse{})
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)
} }
@@ -328,117 +340,14 @@ func TestGetProductsReport(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetProducts(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetProducts(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
if resp.StatusCode != test.statusCode { compareJsonResponse(t, test.response, &GetProductsReportResponse{})
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.Code == "" {
t.Errorf("Code cannot be empty")
}
}
}
}
func TestGetStocksReport(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetStocksReportParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetStocksReportParams{},
`{
"result": {
"code": "d55f4517-8347-4e24-9d93-d6e736c1c07c"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetStocksReportParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetStocks(test.params)
if err != nil {
t.Error(err)
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if resp.Result.Code == "" {
t.Errorf("Code cannot be empty")
}
}
}
}
func TestGetProductsMovementReport(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetProductsMovementReportParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetProductsMovementReportParams{
DateFrom: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2020-08-01T14:15:22Z"),
DateTo: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2020-08-01T14:15:22Z"),
},
`{
"result": {
"code": "h56f4917-1346-4e64-9d90-с6e736c1e07c"
}
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetProductsMovementReportParams{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetProductsMovement(test.params)
if err != nil {
t.Error(err)
}
if resp.StatusCode != test.statusCode { 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)
@@ -466,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
@@ -491,9 +398,11 @@ func TestGetReturnsReport(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetReturns(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetReturns(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
if resp.StatusCode != test.statusCode { if resp.StatusCode != test.statusCode {
@@ -501,9 +410,7 @@ func TestGetReturnsReport(t *testing.T) {
} }
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")
}
} }
} }
} }
@@ -522,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"),
}, },
}, },
`{ `{
@@ -549,11 +456,15 @@ func TestGetShipmentReport(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().GetShipment(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().GetShipment(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetShipmentReportResponse{})
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)
} }
@@ -596,9 +507,11 @@ func TestIssueOnDiscountedProducts(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().IssueOnDiscountedProducts() ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Reports().IssueOnDiscountedProducts(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
if resp.StatusCode != test.statusCode { if resp.StatusCode != test.statusCode {
@@ -606,6 +519,7 @@ func TestIssueOnDiscountedProducts(t *testing.T) {
} }
if resp.StatusCode == http.StatusOK { if resp.StatusCode == http.StatusOK {
compareJsonResponse(t, test.response, &IssueOnDiscountedProductsResponse{})
if resp.Code == "" { if resp.Code == "" {
t.Errorf("Code cannot be empty") t.Errorf("Code cannot be empty")
} }
@@ -613,28 +527,26 @@ func TestIssueOnDiscountedProducts(t *testing.T) {
} }
} }
func TestReportOnDiscountedProducts(t *testing.T) { func TestGetFBSStocks(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {
statusCode int statusCode int
headers map[string]string headers map[string]string
params *ReportOnDiscountedProductsParams params *GetFBSStocksParams
response string response string
}{ }{
// Test Ok // Test Ok
{ {
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"},
&ReportOnDiscountedProductsParams{ &GetFBSStocksParams{
Code: "d55f4517-8347-4e24-9d93-d6e736c1c07c", Language: "EN",
WarehouseIds: []int64{123},
}, },
`{ `{
"report": { "result": {
"created_at": "2022-10-04T10:07:08.146Z", "code": "d55f4517-8347-4e24-9d93-d6e736c1c07c"
"error": "string",
"file": "string",
"status": "string"
} }
}`, }`,
}, },
@@ -642,7 +554,7 @@ func TestReportOnDiscountedProducts(t *testing.T) {
{ {
http.StatusUnauthorized, http.StatusUnauthorized,
map[string]string{}, map[string]string{},
&ReportOnDiscountedProductsParams{}, &GetFBSStocksParams{},
`{ `{
"code": 16, "code": 16,
"message": "Client-Id and Api-Key headers are required" "message": "Client-Id and Api-Key headers are required"
@@ -653,59 +565,15 @@ func TestReportOnDiscountedProducts(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().ReportOnDiscountedProducts(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
if err != nil { resp, err := c.Reports().GetFBSStocks(ctx, test.params)
t.Error(err)
}
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
}
}
func TestListReportsOnDiscountedProducts(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"},
`{
"reports": [
{
"created_at": "2022-10-04T10:07:08.146Z",
"error": "string",
"file": "string",
"status": "string"
}
]
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
`{
"code": 16,
"message": "Client-Id and Api-Key headers are required"
}`,
},
}
for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Reports().ListReportsOnDiscountedProducts()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetFBSStocksResponse{})
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)
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
core "github.com/diphantxm/ozon-api-client" core "github.com/diphantxm/ozon-api-client"
@@ -11,29 +12,39 @@ type Strategies struct {
} }
type ListCompetitorsParams struct { type ListCompetitorsParams struct {
// Page number from which you want to download the list of competitors.
// The minimum value is 1
Page int64 `json:"page"` Page int64 `json:"page"`
// Maximum number of competitors on the page. Allowed values: 150
Limit int64 `json:"limit"` Limit int64 `json:"limit"`
} }
type ListCompetitorsResponse struct { type ListCompetitorsResponse struct {
core.CommonResponse core.CommonResponse
Competitors []struct { // List of competitors
Name string `json:"name"` Competitor []ListCompetitorsCompetitor `json:"competitor"`
Id int64 `json:"id"`
} `json:"competitors"`
// Total number of competitors
Total int32 `json:"total"` Total int32 `json:"total"`
} }
func (c Strategies) ListCompetitors(params *ListCompetitorsParams) (*ListCompetitorsResponse, error) { type ListCompetitorsCompetitor struct {
// Competitor's name
Name string `json:"competitor_name"`
// Competitor identifier
Id int64 `json:"competitor_id"`
}
// Method for getting a list of competitors—sellers with similar products in other online stores and marketplaces
func (c Strategies) ListCompetitors(ctx context.Context, params *ListCompetitorsParams) (*ListCompetitorsResponse, error) {
url := "/v1/pricing-strategy/competitors/list" url := "/v1/pricing-strategy/competitors/list"
resp := &ListCompetitorsResponse{} resp := &ListCompetitorsResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -43,41 +54,56 @@ func (c Strategies) ListCompetitors(params *ListCompetitorsParams) (*ListCompeti
} }
type ListStrategiesParams struct { type ListStrategiesParams struct {
// Page number from which you want to download the list of competitors.
// The minimum value is 1
Page int64 `json:"page"` Page int64 `json:"page"`
// Maximum number of competitors on the page. Allowed values: 150
Limit int64 `json:"limit"` Limit int64 `json:"limit"`
} }
type ListStrategiesResponse struct { type ListStrategiesResponse struct {
core.CommonResponse core.CommonResponse
Strategies []struct { // List of strategies
Id string `json:"id"` Strategies []ListStrategiesStrategy `json:"strategies"`
Name string `json:"name"`
Type StrategyType `json:"type"`
UpdateType StrategyUpdateType `json:"update_type"`
UpdatedAt string `json:"updated_at"`
ProductsCount int64 `json:"products_count"`
CompetitorsCount int64 `json:"competitors_count"`
Enabled bool `json:"enabled"`
} `json:"strategies"`
// Total number of strategies
Total int32 `json:"total"` Total int32 `json:"total"`
} }
func (c Strategies) List(params *ListStrategiesParams) (*ListStrategiesResponse, error) { type ListStrategiesStrategy struct {
// Strategy identifier
Id string `json:"strategy_id"`
// Strategy name
Name string `json:"strategy_name"`
// Strategy type
Type StrategyType `json:"type"`
// Type of the last strategy change
UpdateType StrategyUpdateType `json:"update_type"`
// Date of last change
UpdatedAt string `json:"updated_at"`
// Number of products in the strategy
ProductsCount int64 `json:"products_count"`
// Number of selected competitors
CompetitorsCount int64 `json:"competitors_count"`
// Strategy status
Enabled bool `json:"enabled"`
}
func (c Strategies) List(ctx context.Context, params *ListStrategiesParams) (*ListStrategiesResponse, error) {
url := "/v1/pricing-strategy/list" url := "/v1/pricing-strategy/list"
resp := &ListStrategiesResponse{} resp := &ListStrategiesResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -87,31 +113,40 @@ func (c Strategies) List(params *ListStrategiesParams) (*ListStrategiesResponse,
} }
type CreateStrategyParams struct { type CreateStrategyParams struct {
// List of competitors
Competitors []CreateStrategyCompetitor `json:"competitors"` Competitors []CreateStrategyCompetitor `json:"competitors"`
// Strategy name
StrategyName string `json:"strategy_name"` StrategyName string `json:"strategy_name"`
} }
type CreateStrategyCompetitor struct { type CreateStrategyCompetitor struct {
// Coefficient by which the minimum price among competitors will be multiplied.
// The allowed range is from 0.5 to 1.2
Coefficient float32 `json:"coefficient"` Coefficient float32 `json:"coefficient"`
// Competitor identifier
CompetitorId int64 `json:"competitor_id"` CompetitorId int64 `json:"competitor_id"`
} }
type CreateStrategyResponse struct { type CreateStrategyResponse struct {
core.CommonResponse core.CommonResponse
Result struct { // Method result
StrategyId string `json:"strategy_id"` Result CreateStrategyResult `json:"result"`
} `json:"result"`
} }
func (c Strategies) Create(params *CreateStrategyParams) (*CreateStrategyResponse, error) { type CreateStrategyResult struct {
// Strategy identifier
StrategyId string `json:"strategy_id"`
}
func (c Strategies) Create(ctx context.Context, params *CreateStrategyParams) (*CreateStrategyResponse, error) {
url := "/v1/pricing-strategy/create" url := "/v1/pricing-strategy/create"
resp := &CreateStrategyResponse{} resp := &CreateStrategyResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -121,31 +156,40 @@ func (c Strategies) Create(params *CreateStrategyParams) (*CreateStrategyRespons
} }
type InfoStrategyParams struct { type InfoStrategyParams struct {
// Strategy identifier
StrategyId string `json:"strategy_id"` StrategyId string `json:"strategy_id"`
} }
type InfoStrategyResponse struct { type InfoStrategyResponse struct {
core.CommonResponse core.CommonResponse
Result struct { // Method result
Competitors []CreateStrategyCompetitor `json:"competitors"` Result InfoStrategyResult `json:"result"`
Enabled bool `json:"enabled"`
Name string `json:"name"`
Type StrategyType `json:"type"`
UpdateType StrategyUpdateType `json:"update_type"`
} `json:"result"`
} }
func (c Strategies) Info(params *InfoStrategyParams) (*InfoStrategyResponse, error) { type InfoStrategyResult struct {
// List of competitors
Competitors []CreateStrategyCompetitor `json:"competitors"`
// Strategy status
Enabled bool `json:"enabled"`
// Strategy name
Name string `json:"name"`
// Strategy type
Type StrategyType `json:"type"`
// Type of the last strategy change
UpdateType StrategyUpdateType `json:"update_type"`
}
func (c Strategies) Info(ctx context.Context, params *InfoStrategyParams) (*InfoStrategyResponse, error) {
url := "/v1/pricing-strategy/info" url := "/v1/pricing-strategy/info"
resp := &InfoStrategyResponse{} resp := &InfoStrategyResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -155,10 +199,13 @@ func (c Strategies) Info(params *InfoStrategyParams) (*InfoStrategyResponse, err
} }
type UpdateStrategyParams struct { type UpdateStrategyParams struct {
// List of competitors
Competitors []CreateStrategyCompetitor `json:"competitors"` Competitors []CreateStrategyCompetitor `json:"competitors"`
// Product identifier
StrategyId string `json:"strategy_id"` StrategyId string `json:"strategy_id"`
// Strategy name
StrategyName string `json:"strategy_name"` StrategyName string `json:"strategy_name"`
} }
@@ -166,12 +213,12 @@ type UpdateStrategyResponse struct {
core.CommonResponse core.CommonResponse
} }
func (c Strategies) Update(params *UpdateStrategyParams) (*UpdateStrategyResponse, error) { func (c Strategies) Update(ctx context.Context, params *UpdateStrategyParams) (*UpdateStrategyResponse, error) {
url := "/v1/pricing-strategy/update" url := "/v1/pricing-strategy/update"
resp := &UpdateStrategyResponse{} resp := &UpdateStrategyResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -181,33 +228,45 @@ func (c Strategies) Update(params *UpdateStrategyParams) (*UpdateStrategyRespons
} }
type AddProductsToStrategyParams struct { type AddProductsToStrategyParams struct {
// List of product identifiers. The maximum number is 50
ProductId []int64 `json:"product_id"` ProductId []int64 `json:"product_id"`
// Product identifier
StrategyId string `json:"strategy_id"` StrategyId string `json:"strategy_id"`
} }
type AddProductsToStrategyResponse struct { type AddProductsToStrategyResponse struct {
core.CommonResponse core.CommonResponse
Result struct { // Method result
Errors []struct { Result AddProductsToStrategyResult `json:"result"`
Code string `json:"code"`
Error string `json:"error"`
ProductId int64 `json:"product_id"`
} `json:"errors"`
FailedProductCount int32 `json:"failed_product_count"`
} `json:"result"`
} }
func (c Strategies) AddProducts(params *AddProductsToStrategyParams) (*AddProductsToStrategyResponse, error) { type AddProductsToStrategyResult struct {
// Products with errors
Errors []AddProductsToStrategyResultError `json:"errors"`
// Number of products with errors
FailedProductCount int32 `json:"failed_product_count"`
}
type AddProductsToStrategyResultError struct {
// Error code
Code string `json:"code"`
// Error message
Error string `json:"error"`
// Product identifier
ProductId int64 `json:"product_id"`
}
func (c Strategies) AddProducts(ctx context.Context, params *AddProductsToStrategyParams) (*AddProductsToStrategyResponse, error) {
url := "/v1/pricing-strategy/products/add" url := "/v1/pricing-strategy/products/add"
resp := &AddProductsToStrategyResponse{} resp := &AddProductsToStrategyResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -217,27 +276,36 @@ func (c Strategies) AddProducts(params *AddProductsToStrategyParams) (*AddProduc
} }
type GetStrategiesByProductIdsParams struct { type GetStrategiesByProductIdsParams struct {
// List of product identifiers. The maximum number is 50
ProductId []int64 `json:"product_id"` ProductId []int64 `json:"product_id"`
} }
type GetStrategiesByProductIdsResponse struct { type GetStrategiesByProductIdsResponse struct {
core.CommonResponse core.CommonResponse
Result struct { // Method result
ProductsInfo []struct { Result GetStrategiesByProductIdsResult `json:"result"`
ProductId int64 `json:"product_id"`
StrategyId string `json:"strategy_id"`
} `json:"products_info"`
} `json:"result"`
} }
func (c Strategies) GetByProductIds(params *GetStrategiesByProductIdsParams) (*GetStrategiesByProductIdsResponse, error) { type GetStrategiesByProductIdsResult struct {
// Product information
ProductsInfo []GetStrategiesByProductIdsResultProductInfo `json:"products_info"`
}
type GetStrategiesByProductIdsResultProductInfo struct {
// Product identifier
ProductId int64 `json:"product_id"`
// Strategy identifier to which the product is bounded
StrategyId string `json:"strategy_id"`
}
func (c Strategies) GetByProductIds(ctx context.Context, params *GetStrategiesByProductIdsParams) (*GetStrategiesByProductIdsResponse, error) {
url := "/v1/pricing-strategy/strategy-ids-by-product-ids" url := "/v1/pricing-strategy/strategy-ids-by-product-ids"
resp := &GetStrategiesByProductIdsResponse{} resp := &GetStrategiesByProductIdsResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -247,23 +315,28 @@ func (c Strategies) GetByProductIds(params *GetStrategiesByProductIdsParams) (*G
} }
type ListProductsInStrategyParams struct { type ListProductsInStrategyParams struct {
// Strategy identifier
StrategyId string `json:"strategy_id"` StrategyId string `json:"strategy_id"`
} }
type ListProductsInStrategyResponse struct { type ListProductsInStrategyResponse struct {
core.CommonResponse core.CommonResponse
Result struct { // Method result
ProductId []string `json:"product_id"` Result ListProductsInStrategyResult `json:"result"`
} `json:"result"`
} }
func (c Strategies) ListProducts(params *ListProductsInStrategyParams) (*ListProductsInStrategyResponse, error) { type ListProductsInStrategyResult struct {
// Product identifier
ProductId []string `json:"product_id"`
}
func (c Strategies) ListProducts(ctx context.Context, params *ListProductsInStrategyParams) (*ListProductsInStrategyResponse, error) {
url := "/v1/pricing-strategy/products/list" url := "/v1/pricing-strategy/products/list"
resp := &ListProductsInStrategyResponse{} resp := &ListProductsInStrategyResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -273,33 +346,44 @@ func (c Strategies) ListProducts(params *ListProductsInStrategyParams) (*ListPro
} }
type GetCompetitorPriceParams struct { type GetCompetitorPriceParams struct {
// Product identifier
ProductId int64 `json:"product_id"` ProductId int64 `json:"product_id"`
} }
type GetCompetitorPriceResponse struct { type GetCompetitorPriceResponse struct {
core.CommonResponse core.CommonResponse
Result struct { // Method result
StrategyId string `json:"strategy_id"` Result GetCompetitorPriceResult `json:"result"`
IsEnabled bool `json:"is_enabled"`
StrategyProductPrice int32 `json:"strategy_product_price"`
PriceDownloadedAt string `json:"price_downloaded_at"`
StrategyCompetitorId int64 `json:"strategy_competitor_id"`
StrategyCompetitorProductURL string `json:"strategy_competitor_product_url"`
} `json:"result"`
} }
func (c Strategies) GetCompetitorPrice(params *GetCompetitorPriceParams) (*GetCompetitorPriceResponse, error) { type GetCompetitorPriceResult struct {
// Product identifier
StrategyId string `json:"strategy_id"`
// true if the product is in the pricing strategy
IsEnabled bool `json:"is_enabled"`
// Price of product in the strategy
StrategyProductPrice int32 `json:"strategy_product_price"`
// Price setting date
PriceDownloadedAt string `json:"price_downloaded_at"`
// Competitor identifier
StrategyCompetitorId int64 `json:"strategy_competitor_id"`
// Link to a competitor's product
StrategyCompetitorProductURL string `json:"strategy_competitor_product_url"`
}
// If you add a product to your pricing strategy, the method returns you the price and a link to the competitor's product
func (c Strategies) GetCompetitorPrice(ctx context.Context, params *GetCompetitorPriceParams) (*GetCompetitorPriceResponse, error) {
url := "/v1/pricing-strategy/product/info" url := "/v1/pricing-strategy/product/info"
resp := &GetCompetitorPriceResponse{} resp := &GetCompetitorPriceResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -309,23 +393,28 @@ func (c Strategies) GetCompetitorPrice(params *GetCompetitorPriceParams) (*GetCo
} }
type RemoveProductsFromStrategyParams struct { type RemoveProductsFromStrategyParams struct {
// List of product identifiers. The maximum number is 50
ProductId []int64 `json:"product_id"` ProductId []int64 `json:"product_id"`
} }
type RemoveProductsFromStrategyResponse struct { type RemoveProductsFromStrategyResponse struct {
core.CommonResponse core.CommonResponse
Result struct { // Method result
FailedProductCount int32 `json:"failed_product_count"` Result RemoveProductsFromStrategyResult `json:"result"`
} `json:"result"`
} }
func (c Strategies) RemoveProducts(params *RemoveProductsFromStrategyParams) (*RemoveProductsFromStrategyResponse, error) { type RemoveProductsFromStrategyResult struct {
// Number of products with errors
FailedProductCount int32 `json:"failed_product_count"`
}
func (c Strategies) RemoveProducts(ctx context.Context, params *RemoveProductsFromStrategyParams) (*RemoveProductsFromStrategyResponse, error) {
url := "/v1/pricing-strategy/products/delete" url := "/v1/pricing-strategy/products/delete"
resp := &RemoveProductsFromStrategyResponse{} resp := &RemoveProductsFromStrategyResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -335,8 +424,10 @@ func (c Strategies) RemoveProducts(params *RemoveProductsFromStrategyParams) (*R
} }
type ChangeStrategyStatusParams struct { type ChangeStrategyStatusParams struct {
// Strategy status
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
// Product identifier
StrategyId string `json:"strategy_id"` StrategyId string `json:"strategy_id"`
} }
@@ -344,12 +435,12 @@ type ChangeStrategyStatusResponse struct {
core.CommonResponse core.CommonResponse
} }
func (c Strategies) ChangeStatus(params *ChangeStrategyStatusParams) (*ChangeStrategyStatusResponse, error) { func (c Strategies) ChangeStatus(ctx context.Context, params *ChangeStrategyStatusParams) (*ChangeStrategyStatusResponse, error) {
url := "/v1/pricing-strategy/status" url := "/v1/pricing-strategy/status"
resp := &ChangeStrategyStatusResponse{} resp := &ChangeStrategyStatusResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }
@@ -359,6 +450,7 @@ func (c Strategies) ChangeStatus(params *ChangeStrategyStatusParams) (*ChangeStr
} }
type RemoveStrategyParams struct { type RemoveStrategyParams struct {
// Strategy identifier
StrategyId string `json:"strategy_id"` StrategyId string `json:"strategy_id"`
} }
@@ -366,12 +458,12 @@ type RemoveStrategyResponse struct {
core.CommonResponse core.CommonResponse
} }
func (c Strategies) Remove(params *RemoveStrategyParams) (*RemoveStrategyResponse, error) { func (c Strategies) Remove(ctx context.Context, params *RemoveStrategyParams) (*RemoveStrategyResponse, error) {
url := "/v1/pricing-strategy/delete" url := "/v1/pricing-strategy/delete"
resp := &RemoveStrategyResponse{} resp := &RemoveStrategyResponse{}
response, err := c.client.Request(http.MethodPost, 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
} }

View File

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -49,11 +50,15 @@ func TestListCompetitors(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().ListCompetitors(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().ListCompetitors(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListCompetitorsResponse{})
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)
} }
@@ -108,11 +113,15 @@ func TestListStrategies(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().List(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().List(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListStrategiesResponse{})
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)
} }
@@ -155,7 +164,7 @@ func TestCreateStrategy(t *testing.T) {
}, },
`{ `{
"result": { "result": {
"id": "4f3a1d4c-5833-4f04-b69b-495cbc1f6f1c" "strategy_id": "4f3a1d4c-5833-4f04-b69b-495cbc1f6f1c"
} }
}`, }`,
}, },
@@ -174,11 +183,15 @@ func TestCreateStrategy(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().Create(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().Create(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &CreateStrategyResponse{})
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)
} }
@@ -203,7 +216,7 @@ func TestInfoStrategy(t *testing.T) {
}, },
`{ `{
"result": { "result": {
"strategy_name": "test1", "name": "test1",
"enabled": true, "enabled": true,
"update_type": "strategyItemsListChanged", "update_type": "strategyItemsListChanged",
"type": "COMP_PRICE", "type": "COMP_PRICE",
@@ -235,11 +248,15 @@ func TestInfoStrategy(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().Info(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().Info(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &InfoStrategyResponse{})
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)
} }
@@ -302,11 +319,15 @@ func TestUpdateStrategy(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().Update(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().Update(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &UpdateStrategyResponse{})
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)
} }
@@ -351,11 +372,15 @@ func TestAddProductsToStrategy(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().AddProducts(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().AddProducts(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &AddProductsToStrategyResponse{})
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)
} }
@@ -404,11 +429,15 @@ func TestGetStrategiesByProductIds(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().GetByProductIds(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().GetByProductIds(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetStrategiesByProductIdsResponse{})
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)
} }
@@ -466,11 +495,15 @@ func TestListProductsInStrategy(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().ListProducts(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().ListProducts(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ListProductsInStrategyResponse{})
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)
} }
@@ -519,11 +552,15 @@ func TestGetCompetitorPrice(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().GetCompetitorPrice(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().GetCompetitorPrice(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetCompetitorPriceResponse{})
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)
} }
@@ -567,11 +604,15 @@ func TestRemoveProductsFromStrategy(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().RemoveProducts(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().RemoveProducts(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &RemoveProductsFromStrategyResponse{})
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)
} }
@@ -612,11 +653,15 @@ func TestChangeStrategyStatus(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().ChangeStatus(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().ChangeStatus(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &ChangeStatusToResponse{})
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)
} }
@@ -656,11 +701,15 @@ func TestRemoveStrategy(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Strategies().Remove(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Strategies().Remove(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &RemoveStrategyResponse{})
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)
} }

83
ozon/utils.go Normal file
View File

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

View File

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

@@ -1,6 +1,7 @@
package ozon package ozon
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -20,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"
}
] ]
}`, }`,
}, },
@@ -58,11 +69,15 @@ func TestGetListOfWarehouses(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Warehouses().GetListOfWarehouses() ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Warehouses().GetListOfWarehouses(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetListOfWarehousesResponse{})
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)
} }
@@ -94,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,
@@ -112,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
@@ -133,11 +149,15 @@ func TestGetListOfDeliveryMethods(t *testing.T) {
for _, test := range tests { for _, test := range tests {
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
resp, err := c.Warehouses().GetListOfDeliveryMethods(test.params) ctx, _ := context.WithTimeout(context.Background(), testTimeout)
resp, err := c.Warehouses().GetListOfDeliveryMethods(ctx, test.params)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue
} }
compareJsonResponse(t, test.response, &GetListOfDeliveryMethodsResponse{})
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)
} }
@@ -160,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)
}
}
}