11 Commits

Author SHA1 Message Date
diPhantxm
5fb08c30cb add all remained methods for fbs 2023-03-19 01:51:25 +03:00
diPhantxm
c307bc31bd add some more additional endpoints for working with fbs 2023-03-18 23:38:13 +03:00
diPhantxm
f108c846b0 add methods for labeling and product check statuses 2023-03-18 20:34:04 +03:00
diPhantxm
9592e3a2d3 add some methods for working with fbs 2023-03-18 19:44:31 +03:00
diPhantxm
7b4ed2a988 implemented all methods for changing shipment status for fbs 2023-03-18 18:46:08 +03:00
diPhantxm
ec7ea5e1ef add some methods for shipment and delivering for fbs 2023-03-18 18:16:31 +03:00
diPhantxm
da9bedf63b add method for getting shipment details by identifier for fbs 2023-03-18 17:01:57 +03:00
diPhantxm
0fe3d86c48 add method for getting shipment data by barcode for fbs 2023-03-18 16:20:51 +03:00
Kirill
159e1501df update readme
fix example
2023-03-18 03:43:54 +03:00
Kirill
1d6a3f3eb8 Update readme
add instructions how to get Client-Id and Api-Key
2023-03-18 03:07:50 +03:00
diPhantxm
466cbc6379 extend tests 2023-03-18 03:05:06 +03:00
15 changed files with 4293 additions and 126 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
*.out
*.out
mistakes.md

View File

@@ -83,49 +83,49 @@
- [ ] Shipment details
## FBS and rFBS products labeling
- [ ] Validate labeling codes
- [ ] Check and save product items data
- [ ] Get product items check statuses
- [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)
- [ ] Get shipment details by identifier (version 3)
- [ ] Get shipment data by barcode
- [ ] List of manufacturing countries
- [ ] Set the manufacturing country
- [ ] Specify number of boxes for multi-box shipments
- [ ] Get drop-off point restrictions
- [ ] Partial pack the order
- [ ] Create an acceptance and transfer certificate and a waybill
- [ ] Status of acceptance and transfer certificate and waybill
- [ ] Available freights list
- [ ] Get acceptance and transfer certificate and waybill
- [ ] Generating status of digital acceptance and transfer certificate and waybill
- [ ] Get digital shipment certificate
- [ ] Print the labeling
- [ ] Create a task to generate labeling
- [ ] Get a labeling file
- [ ] Package unit labels
- [ ] Open a dispute over a shipment
- [ ] Pass the shipment to shipping
- [ ] Shipment cancellation reasons
- [ ] Shipments cancellation reasons
- [ ] Cancel the shipment
- [ ] Add weight for bulk products in a shipment
- [ ] Cancel sending some products in the shipment
- [ ] List of shipment certificates
- [ ] Sign shipment certificates
- [ ] List of shipments in the certificate
- [ ] Change the status to "Delivering"
- [ ] Add tracking numbers
- [ ] Change the status to "Last Mile"
- [ ] Change the status to "Delivered"
- [ ] Change status to "Sent by seller"
- [ ] Dates available for delivery reschedule
- [ ] Reschedule shipment delivery date
- [ ] ETGB customs declarations
- [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)

View File

@@ -11,6 +11,8 @@ Read full [documentation](https://docs.ozon.ru/api/seller/en/#tag/Introduction)
You can check [list of supported endpoints](ENDPOINTS.md)
## How to start
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.
```bash
go get github.com/diphantxm/ozon-api-client
@@ -33,7 +35,7 @@ func main() {
client := ozon.NewClient("my-client-id", "my-api-key")
// Send request with parameters
resp, err := client.GetProductDetails(&ozon.GetProductDetailsParams{
resp, err := client.Products().GetProductDetails(&ozon.GetProductDetailsParams{
ProductId: 123456789,
})
if err != nil || resp.StatusCode != http.StatusOK {

View File

@@ -112,7 +112,7 @@ type GetStocksOnWarehousesParams struct {
// Number of values per page.
//
// Default is 100
Limit int64 `json:"limit"`
Limit int64 `json:"limit" default:"100"`
// 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"`

View File

@@ -127,5 +127,11 @@ func TestGetStocksOnWarehouses(t *testing.T) {
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.Rows) > int(test.params.Limit) {
t.Errorf("Length of rows is bigger than limit")
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -708,7 +708,7 @@ type CreateOrUpdateProductItem struct {
// Service type. Pass one of the values in upper case:
// - IS_CODE_SERVICE,
// - IS_NO_CODE_SERVICE
ServiceType string `json:"service_type"`
ServiceType string `json:"service_type" default:"IS_CODE_SERVICE"`
// VAT rate for the product:
// - 0 — not subject to VAT,

View File

@@ -1,6 +1,7 @@
package ozon
import (
"fmt"
"net/http"
"testing"
@@ -77,6 +78,20 @@ func TestGetStocksInfo(t *testing.T) {
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.Items) > int(test.params.Limit) {
t.Errorf("Amount of items in response cannot be bigger than limit")
}
if len(resp.Result.Items) > 0 {
if resp.Result.Items[0].ProductId == 0 {
t.Errorf("Product id cannot be 0")
}
if resp.Result.Items[0].OfferId == "" {
t.Errorf("Offer id cannot be empty")
}
}
}
}
}
@@ -243,6 +258,21 @@ func TestGetProductDetails(t *testing.T) {
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.Id != test.params.ProductId {
t.Errorf("Id of product in response is not equal product_id in request")
}
if resp.Result.OfferId == "" {
t.Errorf("Offer id cannot be empty")
}
if resp.Result.CategoryId == 0 {
t.Errorf("Category id cannot be 0")
}
if resp.Result.CurrencyCode == "" {
t.Errorf("Currency code cannot be empty")
}
}
}
}
@@ -302,6 +332,20 @@ func TestUpdateStocks(t *testing.T) {
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) != len(test.params.Stocks) {
t.Errorf("Length of stocks in request and response are not equal")
}
if len(resp.Result) > 0 {
if resp.Result[0].OfferId != test.params.Stocks[0].OfferId {
t.Errorf("Offer ids in request and response are not equal")
}
if resp.Result[0].ProductId != test.params.Stocks[0].ProductId {
t.Errorf("Product ids in request and response are not equal")
}
}
}
}
}
@@ -357,6 +401,17 @@ func TestStocksInSellersWarehouse(t *testing.T) {
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) != len(test.params.FBSSKU) {
t.Errorf("Length of skus in request and response must be equal")
}
if len(resp.Result) > 0 {
if fmt.Sprint(resp.Result[0].FBSSKU) == test.params.FBSSKU[0] {
t.Errorf("fbs sku in request and response are not equal")
}
}
}
}
}
@@ -419,6 +474,17 @@ func TestUpdatePrices(t *testing.T) {
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) != len(test.params.Prices) {
t.Errorf("Length of prices in request and response are not equal")
}
if len(resp.Result) > 0 {
if resp.Result[0].ProductId != test.params.Prices[0].ProductId {
t.Errorf("Product ids in request and response are not equal")
}
}
}
}
}
@@ -439,7 +505,7 @@ func TestUpdateQuantityStockProducts(t *testing.T) {
Stocks: []UpdateQuantityStockProductsStock{
{
OfferId: "PH11042",
ProductId: 313455276,
ProductId: 118597312,
Stock: 100,
WarehouseId: 22142605386000,
},
@@ -480,6 +546,23 @@ func TestUpdateQuantityStockProducts(t *testing.T) {
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) != len(test.params.Stocks) {
t.Errorf("Length of stocks in request and response are not equal")
}
if len(resp.Result) > 0 {
if resp.Result[0].Offerid != test.params.Stocks[0].OfferId {
t.Errorf("Offer ids in request and response are not equal")
}
if resp.Result[0].ProductId != test.params.Stocks[0].ProductId {
t.Errorf("Product ids in request and response are not equal")
}
if resp.Result[0].WarehouseId != test.params.Stocks[0].WarehouseId {
t.Errorf("Warehouse ids in request and response are not equal")
}
}
}
}
}
@@ -597,6 +680,12 @@ func TestCreateOrUpdateProduct(t *testing.T) {
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.TaskId == 0 {
t.Errorf("Task id cannot be 0")
}
}
}
}
@@ -658,6 +747,23 @@ func TestGetListOfProducts(t *testing.T) {
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.Items) != int(resp.Result.Total) {
t.Errorf("Length of items is not equal total")
}
if resp.Result.Total > int32(test.params.Limit) {
t.Errorf("Length of items is bigger than limit")
}
if len(resp.Result.Items) > 0 {
if resp.Result.Items[0].OfferId == "" {
t.Errorf("Offer id cannot be empty")
}
if resp.Result.Items[0].ProductId == 0 {
t.Errorf("Product id cannot be 0")
}
}
}
}
}
@@ -887,5 +993,16 @@ func TestGetProductsRatingBySKU(t *testing.T) {
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.Products) != len(test.params.SKUs) {
t.Errorf("Length of products in response is not equal length of skus in request")
}
if len(resp.Products) > 0 {
if resp.Products[0].SKU != test.params.SKUs[0] {
t.Errorf("SKU in request and response are not equal")
}
}
}
}
}

View File

@@ -64,6 +64,17 @@ func TestGetAvailablePromotions(t *testing.T) {
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) > 0 {
if resp.Result[0].Id == 0 {
t.Errorf("Id cannot be 0")
}
if resp.Result[0].ActionType == "" {
t.Errorf("Action type cannot be empty")
}
}
}
}
}
@@ -122,5 +133,11 @@ func TestAddToPromotion(t *testing.T) {
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.ProductIds) != len(test.params.Products) {
t.Errorf("Length of products in response and request must be equal")
}
}
}
}

View File

@@ -66,6 +66,14 @@ func TestGetCurrentRatingInfo(t *testing.T) {
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.Groups) > 0 {
if len(resp.Groups[0].Items) == 0 {
t.Errorf("Length of items in a group cannot be 0")
}
}
}
}
}
@@ -146,5 +154,13 @@ func TestGetRatingInfoForPeriod(t *testing.T) {
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.Ratings) > 0 {
if resp.Ratings[0].Rating == "" {
t.Errorf("Rating system cannot be empty")
}
}
}
}
}

View File

@@ -31,7 +31,7 @@ type GetReportsListParams struct {
// - SELLER_RETURNS — returns report,
// - SELLER_POSTINGS — shipments report,
// - SELLER_FINANCE — financial report
ReportType string `json:"report_type"`
ReportType string `json:"report_type" default:"ALL"`
}
type GetReportsListResponse struct {
@@ -39,41 +39,44 @@ type GetReportsListResponse struct {
// Method result
Result struct {
// Unique report identifier
Code string `json:"code"`
// Array with generated reports
Reports []struct {
// Unique report identifier
Code string `json:"code"`
// Report creation date
CreatedAt time.Time `json:"created_at"`
// Report creation date
CreatedAt time.Time `json:"created_at"`
// Error code when generating the report
Error string `json:"error"`
// Error code when generating the report
Error string `json:"error"`
// Link to CSV file
File string `json:"file"`
// Link to CSV file
File string `json:"file"`
// Array with the filters specified when the seller created the report
Params struct {
} `json:"params"`
// Array with the filters specified when the seller created the report
Params struct {
} `json:"params"`
// Report type:
// - SELLER_PRODUCTS — products report,
// - SELLER_TRANSACTIONS — transactions report,
// - SELLER_PRODUCT_PRICES — product prices report,
// - SELLER_STOCK — stocks report,
// - SELLER_PRODUCT_MOVEMENT — products movement report,
// - SELLER_RETURNS — returns report,
// - SELLER_POSTINGS — shipments report,
// - SELLER_FINANCE — financial report
ReportType string `json:"report_type"`
// Report 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"`
// Report generation status
// - `success`
// - `failed`
Status string `json:"status"`
} `json:"reports"`
// Total number of reports
Total int32 `json:"total"`
} `json:"result"`
// Total number of reports
Total int32 `json:"total"`
}
// Returns the list of reports that have been generated before
@@ -136,7 +139,7 @@ type GetReportDetailsResponse struct {
// Returns information about a created report by its identifier
func (c Reports) GetReportDetails(params *GetReportDetailsParams) (*GetReportDetailsResponse, error) {
url := "/v1/report/list"
url := "/v1/report/info"
resp := &GetReportDetailsResponse{}
@@ -405,7 +408,7 @@ type GetShipmentReportParams struct {
// Response language:
// - RU — Russian
// - EN — English
Language string `json:"language"`
Language string `json:"language" default:"DEFAULT"`
}
type GetShipmentReportFilter struct {

View File

@@ -77,6 +77,15 @@ func TestGetList(t *testing.T) {
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if int(resp.Result.Total) != len(resp.Result.Reports) {
t.Errorf("Amount of reports (%d) is not equal to total (%d)", len(resp.Result.Reports), resp.Result.Total)
}
if len(resp.Result.Reports) > 0 {
if resp.Result.Reports[0].Status == "" {
t.Errorf("Status must be 'success' or 'failed'")
}
}
}
}
@@ -129,6 +138,12 @@ func TestGetReportDetails(t *testing.T) {
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.Status == "" {
t.Errorf("Status must be 'success' or 'failed'")
}
}
}
}
@@ -197,6 +212,14 @@ func TestGetFinancialReport(t *testing.T) {
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.CashFlows) > 0 {
if resp.Result.CashFlows[0].CurrencyCode == "" {
t.Errorf("Currency Code cannot be empty")
}
}
}
}
}
@@ -243,6 +266,12 @@ func TestGetProductsReport(t *testing.T) {
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")
}
}
}
}
@@ -289,6 +318,12 @@ func TestGetStocksReport(t *testing.T) {
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")
}
}
}
}
@@ -338,6 +373,12 @@ func TestGetProductsMovementReport(t *testing.T) {
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")
}
}
}
}
@@ -388,6 +429,12 @@ func TestGetReturnsReport(t *testing.T) {
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")
}
}
}
}
@@ -440,5 +487,11 @@ func TestGetShipmentReport(t *testing.T) {
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")
}
}
}
}

View File

@@ -32,15 +32,15 @@ func TestGetFBOReturns(t *testing.T) {
"returns": [
{
"accepted_from_customer_moment": "2019-08-24T14:15:22Z",
"company_id": 0,
"company_id": 123456789,
"current_place_name": "my-place",
"dst_place_name": "that-place",
"id": 0,
"id": 123456789,
"is_opened": true,
"posting_number": "some number",
"return_reason_name": "ripped",
"returned_to_ozon_moment": "2019-08-24T14:15:22Z",
"sku": 0,
"sku": 123456789,
"status_name": "delivering"
}
]
@@ -69,6 +69,20 @@ func TestGetFBOReturns(t *testing.T) {
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Returns) > 0 {
if resp.Returns[0].Id == 0 {
t.Errorf("Id cannot be 0")
}
if resp.Returns[0].CompanyId == 0 {
t.Errorf("Company id cannot be 0")
}
if resp.Returns[0].SKU == 0 {
t.Errorf("SKU cannot be 0")
}
}
}
}
}
@@ -152,5 +166,25 @@ func TestGetFBSReturns(t *testing.T) {
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 int(resp.Result.Count) != len(resp.Result.Returns) {
t.Errorf("Count must equal to length of returns")
}
if len(resp.Result.Returns) > 0 {
if resp.Result.Returns[0].Id == 0 {
t.Errorf("Id cannot be 0")
}
if resp.Result.Returns[0].ProductId == 0 {
t.Errorf("Product id cannot be 0")
}
if resp.Result.Returns[0].SKU == 0 {
t.Errorf("SKU cannot be 0")
}
if resp.Result.Returns[0].Status == "" {
t.Errorf("Status cannot be empty")
}
}
}
}
}

View File

@@ -66,6 +66,17 @@ func TestGetListOfWarehouses(t *testing.T) {
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) > 0 {
if resp.Result[0].WarehouseId == 0 {
t.Errorf("Warehouse id cannot be 0")
}
if resp.Result[0].Name == "" {
t.Errorf("Name cannot be empty")
}
}
}
}
}
@@ -130,5 +141,22 @@ func TestGetListOfDeliveryMethods(t *testing.T) {
if resp.StatusCode != test.statusCode {
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
}
if resp.StatusCode == http.StatusOK {
if len(resp.Result) > 0 {
if resp.Result[0].Id == 0 {
t.Errorf("Id cannot be 0")
}
if resp.Result[0].Name == "" {
t.Errorf("Name cannot be empty")
}
if resp.Result[0].Status == "" {
t.Errorf("Status cannot be empty")
}
if resp.Result[0].WarehouseId == 0 {
t.Errorf("Warehouse id cannot be 0")
}
}
}
}
}