add methods for labeling and product check statuses
This commit is contained in:
12
ENDPOINTS.md
12
ENDPOINTS.md
@@ -83,9 +83,9 @@
|
||||
- [ ] 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
|
||||
@@ -96,16 +96,16 @@
|
||||
- [ ] List of manufacturing countries
|
||||
- [ ] Set the manufacturing country
|
||||
- [ ] Specify number of boxes for multi-box shipments
|
||||
- [ ] Get drop-off point restrictions
|
||||
- [x] Get drop-off point restrictions
|
||||
- [ ] Partial pack the order
|
||||
- [x] 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
|
||||
- [x] Get acceptance and transfer certificate and waybill
|
||||
- [ ] Generating status of digital acceptance and transfer certificate and waybill
|
||||
- [ ] Get digital shipment certificate
|
||||
- [x] Print the labeling
|
||||
- [ ] Create a task to generate labeling
|
||||
- [x] Create a task to generate labeling
|
||||
- [x] Get a labeling file
|
||||
- [ ] Package unit labels
|
||||
- [ ] Open a dispute over a shipment
|
||||
|
||||
205
ozon/fbs.go
205
ozon/fbs.go
@@ -728,22 +728,7 @@ type GetShipmentDataByIdentifierResponse struct {
|
||||
SKU int64 `json:"sku"`
|
||||
|
||||
// Array of exemplars
|
||||
Exemplars []struct {
|
||||
// Mandatory “Chestny ZNAK” labeling
|
||||
MandatoryMark string `json:"mandatory_mark"`
|
||||
|
||||
// Сustoms cargo declaration (CCD) number
|
||||
GTD string `json:"gtd"`
|
||||
|
||||
// Indication that a сustoms cargo declaration (CCD) number hasn't been specified
|
||||
IsGTDAbsest bool `json:"is_gtd_absent"`
|
||||
|
||||
// Product batch registration number
|
||||
RNPT string `json:"rnpt"`
|
||||
|
||||
// Indication that a product batch registration number hasn't been specified
|
||||
IsRNPTAbsent bool `json:"is_rnpt_absent"`
|
||||
} `json:"exemplars"`
|
||||
Exemplars []FBSProductExemplar `json:"exemplars"`
|
||||
} `json:"products"`
|
||||
} `json:"product_exemplars"`
|
||||
|
||||
@@ -798,6 +783,23 @@ type GetShipmentDataByIdentifierResponse struct {
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type FBSProductExemplar struct {
|
||||
// Mandatory “Chestny ZNAK” labeling
|
||||
MandatoryMark string `json:"mandatory_mark"`
|
||||
|
||||
// Сustoms cargo declaration (CCD) number
|
||||
GTD string `json:"gtd"`
|
||||
|
||||
// Indication that a сustoms cargo declaration (CCD) number hasn't been specified
|
||||
IsGTDAbsest bool `json:"is_gtd_absent"`
|
||||
|
||||
// Product batch registration number
|
||||
RNPT string `json:"rnpt"`
|
||||
|
||||
// Indication that a product batch registration number hasn't been specified
|
||||
IsRNPTAbsent bool `json:"is_rnpt_absent"`
|
||||
}
|
||||
|
||||
// Method for getting shipment details by identifier
|
||||
func (c FBS) GetShipmentDataByIdentifier(params *GetShipmentDataByIdentifierParams) (*GetShipmentDataByIdentifierResponse, error) {
|
||||
url := "/v3/posting/fbs/get"
|
||||
@@ -1278,3 +1280,174 @@ func (c FBS) PrintLabeling(params *PrintLabelingParams) (*PrintLabelingResponse,
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type CreateTaskForGeneratingLabelParams struct {
|
||||
// Numbers of shipments that need labeling
|
||||
PostingNumber []string `json:"posting_number"`
|
||||
}
|
||||
|
||||
type CreateTaskForGeneratingLabelResponse struct {
|
||||
core.CommonResponse
|
||||
|
||||
// Method result
|
||||
Result struct {
|
||||
// Task identifier for labeling generation
|
||||
TaskId int64 `json:"task_id"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// Method for creating a task for asynchronous labeling generation.
|
||||
//
|
||||
// To get labels created as a result of the method, use the /v1/posting/fbs/package-label/get method
|
||||
func (c FBS) CreateTaskForGeneratingLabel(params *CreateTaskForGeneratingLabelParams) (*CreateTaskForGeneratingLabelResponse, error) {
|
||||
url := "/v2/posting/fbs/package-label"
|
||||
|
||||
resp := &CreateTaskForGeneratingLabelResponse{}
|
||||
|
||||
response, err := c.client.Request(http.MethodPost, url, params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.CopyCommonResponse(&resp.CommonResponse)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type GetDropOffPointRestrictionsParams struct {
|
||||
// The number of shipment for which you want to determine the restrictions
|
||||
PostingNumber string `json:"posting_number"`
|
||||
}
|
||||
|
||||
type GetDropOffPointRestrictionsResponse struct {
|
||||
core.CommonResponse
|
||||
|
||||
// Method result
|
||||
Result struct {
|
||||
// Shipment number
|
||||
PostingNumber string `json:"posting_number"`
|
||||
|
||||
// Maximum weight limit in grams
|
||||
MaxPostingWeight float64 `json:"max_posting_weight"`
|
||||
|
||||
// Minimum weight limit in grams
|
||||
MinPostingWeight float64 `json:"min_posting_weight"`
|
||||
|
||||
// Width limit in centimeters
|
||||
Width float64 `json:"width"`
|
||||
|
||||
// Length limit in centimeters
|
||||
Length float64 `json:"length"`
|
||||
|
||||
// Height limit in centimeters
|
||||
Height float64 `json:"height"`
|
||||
|
||||
// Maximum shipment cost limit in rubles
|
||||
MaxPostingPrice float64 `json:"max_posting_price"`
|
||||
|
||||
// Minimum shipment cost limit in rubles
|
||||
MinPostingPrice float64 `json:"min_posting_price"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// Method for getting dimensions, weight, and other restrictions of the drop-off point by the shipment number.
|
||||
// The method is applicable only for the FBS scheme
|
||||
func (c FBS) GetDropOffPointRestrictions(params *GetDropOffPointRestrictionsParams) (*GetDropOffPointRestrictionsResponse, error) {
|
||||
url := "/v1/posting/fbs/restrictions"
|
||||
|
||||
resp := &GetDropOffPointRestrictionsResponse{}
|
||||
|
||||
response, err := c.client.Request(http.MethodPost, url, params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.CopyCommonResponse(&resp.CommonResponse)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type CheckProductItemsDataParams struct {
|
||||
// Shipment number
|
||||
PostingNumber string `json:"posting_number"`
|
||||
|
||||
Products CheckProductItemsDataProduct `json:"products"`
|
||||
}
|
||||
|
||||
type CheckProductItemsDataProduct struct {
|
||||
// Product items data
|
||||
Exemplars []FBSProductExemplar `json:"exemplars"`
|
||||
|
||||
// SKU, FBS product identifier in the Ozon system
|
||||
ProductId int64 `json:"product_id"`
|
||||
}
|
||||
|
||||
type CheckProductItemsDataResponse struct {
|
||||
core.CommonResponse
|
||||
|
||||
// Method result. true if the request was processed successfully
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
|
||||
// Asynchronous method:
|
||||
// - for checking the availability of product items in the “Chestny ZNAK” labeling system
|
||||
// - for saving product items data
|
||||
// To get the checks results, use the `/v4/fbs/posting/product/exemplar/status method`
|
||||
//
|
||||
// If necessary, specify the number of the cargo customs declaration in the gtd parameter. If it is missing, pass the value `is_gtd_absent` = true
|
||||
//
|
||||
// If you have multiple identical products in a shipment, specify one `product_id` and `exemplars` array for each product in the shipment
|
||||
//
|
||||
// Always pass a complete set of product items data
|
||||
//
|
||||
// For example, you have 10 product items in your system.
|
||||
// You have passed them for checking and saving. Then they added another 60 product items to your system.
|
||||
// When you pass product items for checking and saving again, pass all of them: both old and newly added
|
||||
func (c FBS) CheckproductItemsData(params *CheckProductItemsDataParams) (*CheckProductItemsDataResponse, error) {
|
||||
url := "/v4/fbs/posting/product/exemplar/set"
|
||||
|
||||
resp := &CheckProductItemsDataResponse{}
|
||||
|
||||
response, err := c.client.Request(http.MethodPost, url, params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.CopyCommonResponse(&resp.CommonResponse)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type GetProductItemsCheckStatusesParams struct {
|
||||
// Shipment number
|
||||
PostingNumber string `json:"posting_number"`
|
||||
}
|
||||
|
||||
type GetProductItemsCheckStatusesResponse struct {
|
||||
core.CommonResponse
|
||||
|
||||
// Shipment number
|
||||
PostingNumber string `json:"posting_number"`
|
||||
|
||||
// Products list
|
||||
Products []CheckProductItemsDataProduct `json:"products"`
|
||||
|
||||
// Product items check statuses and order collection availability:
|
||||
// - ship_available — order collection is available,
|
||||
// - ship_not_available — order collection is unavailable,
|
||||
// - validation_in_process — product items validation is in progress
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// Method for getting check statuses of product items that were passed in the `/fbs/posting/product/exemplar/set` method.
|
||||
// Also returns data on these product items.
|
||||
func (c FBS) GetProductItemsCheckStatuses(params *GetProductItemsCheckStatusesParams) (*GetProductItemsCheckStatusesResponse, error) {
|
||||
url := "/v4/fbs/posting/product/exemplar/status"
|
||||
|
||||
resp := &GetProductItemsCheckStatusesResponse{}
|
||||
|
||||
response, err := c.client.Request(http.MethodPost, url, params, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.CopyCommonResponse(&resp.CommonResponse)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
249
ozon/fbs_test.go
249
ozon/fbs_test.go
@@ -1214,3 +1214,252 @@ func TestPrintLabeling(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTaskForGeneratingLabel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
statusCode int
|
||||
headers map[string]string
|
||||
params *CreateTaskForGeneratingLabelParams
|
||||
response string
|
||||
}{
|
||||
// Test Ok
|
||||
{
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
&CreateTaskForGeneratingLabelParams{
|
||||
PostingNumber: []string{"48173252-0034-4"},
|
||||
},
|
||||
`{
|
||||
"result": {
|
||||
"task_id": 5819327210249
|
||||
}
|
||||
}`,
|
||||
},
|
||||
// Test No Client-Id or Api-Key
|
||||
{
|
||||
http.StatusUnauthorized,
|
||||
map[string]string{},
|
||||
&CreateTaskForGeneratingLabelParams{},
|
||||
`{
|
||||
"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.FBS().CreateTaskForGeneratingLabel(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.TaskId == 0 {
|
||||
t.Errorf("Task id cannot be 0")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDropOffPointRestrictions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
statusCode int
|
||||
headers map[string]string
|
||||
params *GetDropOffPointRestrictionsParams
|
||||
response string
|
||||
}{
|
||||
// Test Ok
|
||||
{
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
&GetDropOffPointRestrictionsParams{
|
||||
PostingNumber: "48173252-0034-4",
|
||||
},
|
||||
`{
|
||||
"result": {
|
||||
"posting_number": "48173252-0034-4",
|
||||
"max_posting_weight": 0,
|
||||
"min_posting_weight": 0,
|
||||
"width": 0,
|
||||
"length": 0,
|
||||
"height": 0,
|
||||
"max_posting_price": 0,
|
||||
"min_posting_price": 0
|
||||
}
|
||||
}`,
|
||||
},
|
||||
// Test No Client-Id or Api-Key
|
||||
{
|
||||
http.StatusUnauthorized,
|
||||
map[string]string{},
|
||||
&GetDropOffPointRestrictionsParams{},
|
||||
`{
|
||||
"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.FBS().GetDropOffPointRestrictions(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.PostingNumber != test.params.PostingNumber {
|
||||
t.Errorf("Posting numbers in request and response are not equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckProductItemsData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
statusCode int
|
||||
headers map[string]string
|
||||
params *CheckProductItemsDataParams
|
||||
response string
|
||||
}{
|
||||
// Test Ok
|
||||
{
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
&CheckProductItemsDataParams{
|
||||
PostingNumber: "48173252-0034-4",
|
||||
Products: CheckProductItemsDataProduct{
|
||||
Exemplars: []FBSProductExemplar{
|
||||
{
|
||||
IsGTDAbsest: true,
|
||||
MandatoryMark: "010290000151642731tVMohkbfFgunB",
|
||||
},
|
||||
},
|
||||
ProductId: 476925391,
|
||||
},
|
||||
},
|
||||
`{
|
||||
"result": true
|
||||
}`,
|
||||
},
|
||||
// Test No Client-Id or Api-Key
|
||||
{
|
||||
http.StatusUnauthorized,
|
||||
map[string]string{},
|
||||
&CheckProductItemsDataParams{},
|
||||
`{
|
||||
"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.FBS().CheckproductItemsData(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 TestGetProductItemsCheckStatuses(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
statusCode int
|
||||
headers map[string]string
|
||||
params *GetProductItemsCheckStatusesParams
|
||||
response string
|
||||
}{
|
||||
// Test Ok
|
||||
{
|
||||
http.StatusOK,
|
||||
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||
&GetProductItemsCheckStatusesParams{
|
||||
PostingNumber: "23281294-0063-2",
|
||||
},
|
||||
`{
|
||||
"posting_number": "23281294-0063-2",
|
||||
"products": [
|
||||
{
|
||||
"product_id": 476925391,
|
||||
"exemplars": [
|
||||
{
|
||||
"mandatory_mark": "010290000151642731tVMohkbfFgunB",
|
||||
"gtd": "",
|
||||
"is_gtd_absent": true,
|
||||
"mandatory_mark_check_status": "passed",
|
||||
"mandatory_mark_error_codes": [],
|
||||
"gtd_check_status": "passed",
|
||||
"gtd_error_codes": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"status": "ship_available"
|
||||
}`,
|
||||
},
|
||||
// Test No Client-Id or Api-Key
|
||||
{
|
||||
http.StatusUnauthorized,
|
||||
map[string]string{},
|
||||
&GetProductItemsCheckStatusesParams{},
|
||||
`{
|
||||
"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.FBS().GetProductItemsCheckStatuses(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.PostingNumber != test.params.PostingNumber {
|
||||
t.Errorf("Posting numbers in request and response are not equal")
|
||||
}
|
||||
if resp.Status == "" {
|
||||
t.Errorf("Status cannot be empty")
|
||||
}
|
||||
if len(resp.Products) > 0 {
|
||||
if resp.Products[0].ProductId == 0 {
|
||||
t.Errorf("Product id cannot be 0")
|
||||
}
|
||||
if len(resp.Products[0].Exemplars) > 0 {
|
||||
if resp.Products[0].Exemplars[0].MandatoryMark == "" {
|
||||
t.Errorf("Mandatory mark cannot be empty")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user