add methods for packing orders and getting list of products
This commit is contained in:
@@ -6,12 +6,12 @@
|
||||
- [ ] Characteristics value directory
|
||||
|
||||
## Uploading and updating products
|
||||
- [ ] Create or update a product
|
||||
- [x] Create or update a product
|
||||
- [ ] Get the product import status
|
||||
- [ ] Create a product by Ozon ID
|
||||
- [ ] Upload and update product images
|
||||
- [ ] Check products images uploading status
|
||||
- [ ] List of products
|
||||
- [x] List of products
|
||||
- [x] Product details
|
||||
- [ ] Get products' content rating by SKU
|
||||
- [ ] Get a list of products by identifiers
|
||||
@@ -86,7 +86,7 @@
|
||||
- [ ] Validate labeling codes
|
||||
- [ ] Check and save product items data
|
||||
- [ ] Get product items check statuses
|
||||
- [ ] Pack the order (version 4)
|
||||
- [x] Pack the order (version 4)
|
||||
|
||||
## FBS and rFBS
|
||||
- [x] List of unprocessed shipments (version 3)
|
||||
@@ -97,7 +97,6 @@
|
||||
- [ ] Set the manufacturing country
|
||||
- [ ] Specify number of boxes for multi-box shipments
|
||||
- [ ] Get drop-off point restrictions
|
||||
- [ ] Pack the order (version 3)
|
||||
- [ ] Partial pack the order
|
||||
- [ ] Create an acceptance and transfer certificate and a waybill
|
||||
- [ ] Status of acceptance and transfer certificate and waybill
|
||||
|
||||
89
ozon/fbs.go
89
ozon/fbs.go
@@ -108,15 +108,7 @@ type FBSPosting struct {
|
||||
ParentPostingNumber string `json:"parent_posting_number"`
|
||||
PostingNumber string `json:"posting_number"`
|
||||
|
||||
Products []struct {
|
||||
MandatoryMark []string `json:"mandatory_mark"`
|
||||
Name string `json:"name"`
|
||||
OfferId string `json:"offer_id"`
|
||||
CurrencyCode string `json:"currency_code"`
|
||||
Price string `json:"price"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
SKU int64 `json:"sku"`
|
||||
} `json:"products"`
|
||||
Products []PostingProduct `json:"products"`
|
||||
|
||||
Requirements struct {
|
||||
ProductsRequiringGTD []string `json:"products_requiring_gtd"`
|
||||
@@ -131,6 +123,16 @@ type FBSPosting struct {
|
||||
TrackingNumber string `json:"tracking_number"`
|
||||
}
|
||||
|
||||
type PostingProduct struct {
|
||||
MandatoryMark []string `json:"mandatory_mark"`
|
||||
Name string `json:"name"`
|
||||
OfferId string `json:"offer_id"`
|
||||
CurrencyCode string `json:"currency_code"`
|
||||
Price string `json:"price"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
SKU int64 `json:"sku"`
|
||||
}
|
||||
|
||||
type FBSCustomer struct {
|
||||
Address struct {
|
||||
AddressTail string `json:"address_tail"`
|
||||
@@ -300,3 +302,72 @@ func (c Client) GetFBSShipmentsList(params *GetFBSShipmentsListParams) (*GetFBSS
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type PackOrderParams struct {
|
||||
// List of packages. Each package contains a list of shipments that the order was divided into
|
||||
Packages []PackOrderPackage `json:"packages"`
|
||||
|
||||
// Shipment number
|
||||
PostingNumber string `json:"posting_number"`
|
||||
|
||||
// Additional information
|
||||
With PackOrderWith `json:"with"`
|
||||
}
|
||||
|
||||
type PackOrderPackage struct {
|
||||
Products []PackOrderPackageProduct `json:"products"`
|
||||
}
|
||||
|
||||
type PackOrderPackageProduct struct {
|
||||
// Product identifier
|
||||
ProductId int64 `json:"product_id"`
|
||||
|
||||
// Product items quantity
|
||||
Quantity int32 `json:"quantity"`
|
||||
}
|
||||
|
||||
type PackOrderWith struct {
|
||||
// Pass true to get additional information
|
||||
AdditionalData bool `json:"additional_data"`
|
||||
}
|
||||
|
||||
type PackOrderResponse struct {
|
||||
core.CommonResponse
|
||||
|
||||
// Additional information about shipments
|
||||
AdditionalData []struct {
|
||||
// Shipment number
|
||||
PostingNumber string `json:"posting_number"`
|
||||
|
||||
// List of products in the shipment
|
||||
Products []PostingProduct `json:"products"`
|
||||
} `json:"additional_data"`
|
||||
|
||||
// Order packaging result
|
||||
Result []string `json:"result"`
|
||||
}
|
||||
|
||||
// Divides the order into shipments and changes its status to awaiting_deliver.
|
||||
//
|
||||
// Each element of the packages may contain several instances of the products. One instance of the products is one shipment. Each element of the products is a product included into the shipment.
|
||||
//
|
||||
// It is necessary to split the order if:
|
||||
//
|
||||
// the products do not fit in one package,
|
||||
// the products cannot be put in one package.
|
||||
// Differs from /v2/posting/fbs/ship by the presence of the field exemplar_info in the request.
|
||||
//
|
||||
// If necessary, specify the number of the cargo customs declaration in the gtd parameter. If it is missing, pass the value is_gtd_absent = true
|
||||
func (c Client) PackOrder(params *PackOrderParams) (*PackOrderResponse, error) {
|
||||
url := "/v4/posting/fbs/ship"
|
||||
|
||||
resp := &PackOrderResponse{}
|
||||
|
||||
response, err := c.client.Request(http.MethodPost, url, params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.CopyCommonResponse(&resp.CommonResponse)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -262,3 +262,51 @@ func TestGetFBSShipmentsList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackOrder(t *testing.T) {
|
||||
tests := []struct {
|
||||
statusCode int
|
||||
headers map[string]string
|
||||
params *PackOrderParams
|
||||
response string
|
||||
}{
|
||||
{
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
&PackOrderParams{
|
||||
Packages: []PackOrderPackage{
|
||||
{
|
||||
Products: []PackOrderPackageProduct{
|
||||
{
|
||||
ProductId: 185479045,
|
||||
Quantity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PostingNumber: "89491381-0072-1",
|
||||
With: PackOrderWith{
|
||||
AdditionalData: true,
|
||||
},
|
||||
},
|
||||
`{
|
||||
"result": [
|
||||
"89491381-0072-1"
|
||||
]
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||
|
||||
resp, err := c.PackOrder(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
235
ozon/products.go
235
ozon/products.go
@@ -616,3 +616,238 @@ func (c Client) UpdatePrices(params *UpdatePricesParams) (*UpdatePricesResponse,
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type CreateOrUpdateProductParams struct {
|
||||
// Data array
|
||||
Items []CreateOrUpdateProductItem `json:"items"`
|
||||
}
|
||||
|
||||
// Data array
|
||||
type CreateOrUpdateProductItem struct {
|
||||
// Array with the product characteristics. The characteristics depend on category.
|
||||
// You can view them in Help Center or via API
|
||||
Attributes []CreateOrUpdateAttribute `json:"attributes"`
|
||||
|
||||
// Product barcode
|
||||
Barcode string `json:"barcode"`
|
||||
|
||||
// Category identifier
|
||||
CategoryId int64 `json:"category_id"`
|
||||
|
||||
// Marketing color.
|
||||
//
|
||||
// Pass the link to the image in the public cloud storage. The image format is JPG
|
||||
ColorImage string `json:"color_image"`
|
||||
|
||||
// Array of characteristics that have nested attributes
|
||||
ComplexAttributes []CreateOrUpdateComplexAttribute `json:"complex_attributes"`
|
||||
|
||||
// Package depth
|
||||
Depth int32 `json:"depth"`
|
||||
|
||||
// Dimensions measurement units:
|
||||
// - mm — millimeters,
|
||||
// - cm — centimeters,
|
||||
// - in — inches
|
||||
DimensionUnit string `json:"dimension_unit"`
|
||||
|
||||
// Geo-restrictions. Pass a list consisting of name values received in the response of the /v1/products/geo-restrictions-catalog-by-filter method
|
||||
GeoNames []string `json:"geo_names"`
|
||||
|
||||
// Package height
|
||||
Height int32 `json:"height"`
|
||||
|
||||
// Array of images, up to 15 files. The images are displayed on the site in the same order as they are in the array.
|
||||
//
|
||||
// The first one will be set as the main image for the product if the primary_image parameter is not specified.
|
||||
//
|
||||
// If you use the primary_image parameter, the maximum number of images is 14. If the primary_image parameter is not specified, you can upload up to 15 images.
|
||||
//
|
||||
// Pass links to images in the public cloud storage. The image format is JPG or PNG
|
||||
Images []string `json:"images"`
|
||||
|
||||
// Link to main product image
|
||||
PrimaryImage string `json:"primary_image"`
|
||||
|
||||
// Array of 360 images—up to 70 files.
|
||||
//
|
||||
// Pass links to images in the public cloud storage. The image format is JPG
|
||||
Images360 []string `json:"images_360"`
|
||||
|
||||
// Product name. Up to 500 characters
|
||||
Name string `json:"name"`
|
||||
|
||||
// Product identifier in the seller's system.
|
||||
//
|
||||
// The maximum length of a string is 50 characters
|
||||
OfferId string `json:"offer_id"`
|
||||
|
||||
// Currency of your prices. The passed value must be the same as the one set in the personal account settings.
|
||||
// By default, the passed value is RUB, Russian ruble.
|
||||
//
|
||||
// For example, if your currency set in the settings is yuan, pass the value CNY, otherwise an error will be returned
|
||||
CurrencyCode string `json:"currency_code"`
|
||||
|
||||
// Price before discounts. Displayed strikethrough on the product description page. Specified in rubles. The fractional part is separated by decimal point, up to two digits after the decimal point.
|
||||
//
|
||||
// If you specified the old_price before and updated the price parameter you should update the old_price too
|
||||
OldPrice string `json:"old_price"`
|
||||
|
||||
// List of PDF files
|
||||
PDFList []CreateOrUpdateProductPDF `json:"pdf_list"`
|
||||
|
||||
// Price for customers with an Ozon Premium subscription
|
||||
PremiumPrice string `json:"premium_price"`
|
||||
|
||||
// Product price including discounts. This value is shown on the product description card.
|
||||
// If there are no discounts on the product, specify the old_price value
|
||||
Price string `json:"price"`
|
||||
|
||||
// Default: "IS_CODE_SERVICE"
|
||||
// Service type. Pass one of the values in upper case:
|
||||
// - IS_CODE_SERVICE,
|
||||
// - IS_NO_CODE_SERVICE
|
||||
ServiceType string `json:"service_type"`
|
||||
|
||||
// VAT rate for the product:
|
||||
// - 0 — not subject to VAT,
|
||||
// - 0.1 — 10%,
|
||||
// - 0.2 — 20%
|
||||
VAT string `json:"vat"`
|
||||
|
||||
// Product weight with the package. The limit value is 1000 kilograms or a corresponding converted value in other measurement units
|
||||
Weight int32 `json:"weight"`
|
||||
|
||||
// Weight measurement units:
|
||||
// - g—grams,
|
||||
// - kg—kilograms,
|
||||
// - lb—pounds
|
||||
WeightUnit string `json:"weight_unit"`
|
||||
|
||||
// Package width
|
||||
Width int32 `json:"width"`
|
||||
}
|
||||
|
||||
// Array with the product characteristics. The characteristics depend on category.
|
||||
// You can view them in Help Center or via API
|
||||
type CreateOrUpdateAttribute struct {
|
||||
// Identifier of the characteristic that supports nested properties.
|
||||
// For example, the "Processor" characteristic has nested characteristics "Manufacturer", "L2 Cache", and others.
|
||||
// Each of the nested characteristics can have multiple value variants
|
||||
ComplexId int64 `json:"complex_id"`
|
||||
|
||||
// Characteristic identifier
|
||||
Id int64 `json:"id"`
|
||||
|
||||
Values []CreateOrUpdateAttributeValue `json:"values"`
|
||||
}
|
||||
|
||||
type CreateOrUpdateAttributeValue struct {
|
||||
// Directory identifier
|
||||
DictionaryValueId int64 `json:"dictrionary_value_id"`
|
||||
|
||||
// Value from the directory
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type CreateOrUpdateComplexAttribute struct {
|
||||
Attributes []CreateOrUpdateAttribute `json:"attributes"`
|
||||
}
|
||||
|
||||
type CreateOrUpdateProductPDF struct {
|
||||
// Storage order index
|
||||
Index int64 `json:"index"`
|
||||
|
||||
// File name
|
||||
Name string `json:"name"`
|
||||
|
||||
// File address
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type CreateOrUpdateProductResponse struct {
|
||||
core.CommonResponse
|
||||
|
||||
// Method result
|
||||
Result struct {
|
||||
// Number of task for products upload
|
||||
TaskId int64 `json:"task_id"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// This method allows you to create products and update their details
|
||||
func (c Client) CreateOrUpdateProduct(params *CreateOrUpdateProductParams) (*CreateOrUpdateProductResponse, error) {
|
||||
url := "/v2/product/import"
|
||||
|
||||
resp := &CreateOrUpdateProductResponse{}
|
||||
|
||||
response, err := c.client.Request(http.MethodPost, url, params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.CopyCommonResponse(&resp.CommonResponse)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type GetListOfProductsParams struct {
|
||||
// Filter by product
|
||||
Filter GetListOfProductsFilter `json:"filter"`
|
||||
|
||||
// Identifier of the last value on the page. Leave this field blank in the first request.
|
||||
//
|
||||
// To get the next values, specify last_id from the response of the previous request
|
||||
LastId string `json:"last_id"`
|
||||
|
||||
// Number of values per page. Minimum is 1, maximum is 1000
|
||||
Limit int64 `json:"limit"`
|
||||
}
|
||||
|
||||
type GetListOfProductsFilter struct {
|
||||
// Filter by the offer_id parameter. You can pass a list of values in this parameter
|
||||
OfferId []string `json:"offer_id"`
|
||||
|
||||
// Filter by the product_id parameter. You can pass a list of values in this parameter
|
||||
ProductId []int64 `json:"product_id"`
|
||||
|
||||
// Filter by product visibility
|
||||
Visibility string `json:"visibility"`
|
||||
}
|
||||
|
||||
type GetListOfProductsResponse struct {
|
||||
core.CommonResponse
|
||||
|
||||
// Result
|
||||
Result struct {
|
||||
// Products list
|
||||
Items []struct {
|
||||
// Product identifier in the seller's system
|
||||
OfferId string `json:"offer_id"`
|
||||
|
||||
// Product ID
|
||||
ProductId int64 `json:"product_id"`
|
||||
} `json:"items"`
|
||||
|
||||
// Identifier of the last value on the page.
|
||||
//
|
||||
// To get the next values, specify the recieved value in the next request in the last_id parameter
|
||||
LastId string `json:"last_id"`
|
||||
|
||||
// Total number of products
|
||||
Total int32 `json:"total"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
func (c Client) GetListOfProducts(params *GetListOfProductsParams) (*GetListOfProductsResponse, error) {
|
||||
url := "/v2/product/list"
|
||||
|
||||
resp := &GetListOfProductsResponse{}
|
||||
|
||||
response, err := c.client.Request(http.MethodPost, url, params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.CopyCommonResponse(&resp.CommonResponse)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -396,3 +396,203 @@ func TestUpdatePrices(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateQuantityStockProducts(t *testing.T) {
|
||||
tests := []struct {
|
||||
statusCode int
|
||||
headers map[string]string
|
||||
params *UpdateQuantityStockProductsParams
|
||||
response string
|
||||
}{
|
||||
{
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
&UpdateQuantityStockProductsParams{
|
||||
Stocks: []UpdateQuantityStockProductsStock{
|
||||
{
|
||||
OfferId: "PH11042",
|
||||
ProductId: 313455276,
|
||||
Stock: 100,
|
||||
WarehouseId: 22142605386000,
|
||||
},
|
||||
},
|
||||
},
|
||||
`{
|
||||
"result": [
|
||||
{
|
||||
"warehouse_id": 22142605386000,
|
||||
"product_id": 118597312,
|
||||
"offer_id": "PH11042",
|
||||
"updated": true,
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||
|
||||
resp, err := c.UpdateQuantityStockProducts(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOrUpdateProduct(t *testing.T) {
|
||||
tests := []struct {
|
||||
statusCode int
|
||||
headers map[string]string
|
||||
params *CreateOrUpdateProductParams
|
||||
response string
|
||||
}{
|
||||
{
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
&CreateOrUpdateProductParams{
|
||||
Items: []CreateOrUpdateProductItem{
|
||||
{
|
||||
Attributes: []CreateOrUpdateAttribute{
|
||||
{
|
||||
ComplexId: 0,
|
||||
Id: 5076,
|
||||
Values: []CreateOrUpdateAttributeValue{
|
||||
{
|
||||
DictionaryValueId: 971082156,
|
||||
Value: "Стойка для акустической системы",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ComplexId: 0,
|
||||
Id: 9048,
|
||||
Values: []CreateOrUpdateAttributeValue{
|
||||
{
|
||||
Value: "Комплект защитных плёнок для X3 NFC. Темный хлопок",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ComplexId: 0,
|
||||
Id: 8229,
|
||||
Values: []CreateOrUpdateAttributeValue{
|
||||
{
|
||||
DictionaryValueId: 95911,
|
||||
Value: "Комплект защитных плёнок для X3 NFC. Темный хлопок",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ComplexId: 0,
|
||||
Id: 85,
|
||||
Values: []CreateOrUpdateAttributeValue{
|
||||
{
|
||||
DictionaryValueId: 5060050,
|
||||
Value: "Samsung",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ComplexId: 0,
|
||||
Id: 10096,
|
||||
Values: []CreateOrUpdateAttributeValue{
|
||||
{
|
||||
DictionaryValueId: 61576,
|
||||
Value: "серый",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Barcode: "112772873170",
|
||||
CategoryId: 17033876,
|
||||
CurrencyCode: "RUB",
|
||||
Depth: 10,
|
||||
DimensionUnit: "mm",
|
||||
Height: 250,
|
||||
Name: "Комплект защитных плёнок для X3 NFC. Темный хлопок",
|
||||
OfferId: "143210608",
|
||||
OldPrice: "1100",
|
||||
PremiumPrice: "900",
|
||||
Price: "1000",
|
||||
VAT: "0.1",
|
||||
Weight: 100,
|
||||
WeightUnit: "g",
|
||||
Width: 150,
|
||||
},
|
||||
},
|
||||
},
|
||||
`{
|
||||
"result": {
|
||||
"task_id": 172549793
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||
|
||||
resp, err := c.CreateOrUpdateProduct(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetListOfProducts(t *testing.T) {
|
||||
tests := []struct {
|
||||
statusCode int
|
||||
headers map[string]string
|
||||
params *GetListOfProductsParams
|
||||
response string
|
||||
}{
|
||||
{
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
&GetListOfProductsParams{
|
||||
Filter: GetListOfProductsFilter{
|
||||
OfferId: []string{"136748"},
|
||||
ProductId: []int64{223681945},
|
||||
Visibility: "ALL",
|
||||
},
|
||||
LastId: "",
|
||||
Limit: 100,
|
||||
},
|
||||
`{
|
||||
"result": {
|
||||
"items": [
|
||||
{
|
||||
"product_id": 223681945,
|
||||
"offer_id": "136748"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"last_id": "bnVсbA=="
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||
|
||||
resp, err := c.GetListOfProducts(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user