From 9592e3a2d32084ea12aea4488af26386d7615f8c Mon Sep 17 00:00:00 2001 From: diPhantxm Date: Sat, 18 Mar 2023 19:44:31 +0300 Subject: [PATCH] add some methods for working with fbs --- ENDPOINTS.md | 10 +- ozon/fbs.go | 189 ++++++++++++++++++++++++++++++++++++ ozon/fbs_test.go | 245 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 439 insertions(+), 5 deletions(-) diff --git a/ENDPOINTS.md b/ENDPOINTS.md index f4b5d13..96d0ede 100644 --- a/ENDPOINTS.md +++ b/ENDPOINTS.md @@ -98,21 +98,21 @@ - [ ] 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 +- [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 - [ ] Generating status of digital acceptance and transfer certificate and waybill - [ ] Get digital shipment certificate -- [ ] Print the labeling +- [x] Print the labeling - [ ] Create a task to generate labeling -- [ ] Get a labeling file +- [x] Get a labeling file - [ ] Package unit labels - [ ] Open a dispute over a shipment -- [ ] Pass the shipment to shipping +- [x] Pass the shipment to shipping - [ ] Shipment cancellation reasons - [ ] Shipments cancellation reasons -- [ ] Cancel the shipment +- [x] Cancel the shipment - [ ] Add weight for bulk products in a shipment - [ ] Cancel sending some products in the shipment - [x] List of shipment certificates diff --git a/ozon/fbs.go b/ozon/fbs.go index 5b753bd..0b47501 100644 --- a/ozon/fbs.go +++ b/ozon/fbs.go @@ -1089,3 +1089,192 @@ func (c FBS) ChangeStatusToSendBySeller(params *ChangeStatusToParams) (*ChangeSt return resp, nil } + +type PassShipmentToShippingParams struct { + // Shipment identifier + PostingNumber []string `json:"posting_number"` +} + +type PassShipmentToShippingResponse struct { + core.CommonResponse + + // Request processing result. true, if the request was executed without errors + Result bool `json:"result"` +} + +// Transfers disputed orders to shipping. The shipment status will change to `awaiting_deliver` +func (c FBS) PassShipmentToShipping(params *PassShipmentToShippingParams) (*PassShipmentToShippingResponse, error) { + url := "/v2/posting/fbs/awaiting-delivery" + + resp := &PassShipmentToShippingResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type CancelShipmentParams struct { + // Shipment cancellation reason identifier + CancelReasonId int64 `json:"cancel_reason_id"` + + // Additional information on cancellation. If `cancel_reason_id` = 402, the parameter is required + CancelReasonMessage string `json:"cancel_reason_message"` + + // Shipment identifier + PostingNumber string `json:"posting_number"` +} + +type CancelShipmentResponse struct { + core.CommonResponse + + // Request processing result. true, if the request was executed without errors + Result bool `json:"result"` +} + +// Change shipment status to cancelled. +// +// If you are using the rFBS scheme, you have the following cancellation reason identifiers (cancel_reason_id) available: +// - 352 — product is out of stock; +// - 400 — only defective products left; +// - 401 — cancellation from arbitration; +// - 402 — other reason; +// - 665 — the customer did not pick the order; +// - 666 — delivery is not available in the region; +// - 667 — order was lost by the delivery service. +// For presumably delivered orders only the last 3 reasons are available. +// +// If cancel_reason_id parameter value is 402, fill the cancel_reason_message field +func (c FBS) CancelShipment(params *CancelShipmentParams) (*CancelShipmentResponse, error) { + url := "/v2/posting/fbs/cancel" + + resp := &CancelShipmentResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type CreateActParams struct { + // Number of package units. + // + // Use this parameter if you have trusted acceptance enabled and ship orders by package units. + // If you do not have trusted acceptance enabled, skip it + ContainersCount int32 `json:"containers_count"` + + // Delivery method identifier + DeliveryMethodId int64 `json:"delivery_method_id"` + + // Shipping date. + // + // To make documents printing available before the shipping day, + // enable Printing the acceptance certificate in advance in your personal account under the method settings. + // The time for packaging (packaging SLA) should be more than 13 hours + DepartureDate time.Time `json:"departure_date"` +} + +type CreateActResponse struct { + core.CommonResponse + + // Method result + Result struct { + // Document generation task number + Id int64 `json:"id"` + } `json:"result"` +} + +// Launches the procedure for generating the transfer documents: acceptance and transfer certificate and the waybill. +// +// To generate and receive transfer documents, transfer the shipment to the `awaiting_deliver` status +func (c FBS) CreateAct(params *CreateActParams) (*CreateActResponse, error) { + url := "/v2/posting/fbs/act/create" + + resp := &CreateActResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type GetLabelingParams struct { + // Task identifier for labeling generation from the /v1/posting/fbs/package-label/create method response + TaskId int64 `json:"task_id"` +} + +type GetLabelingResponse struct { + core.CommonResponse + + // Method result + Result struct { + // Error code + Error string `json:"error"` + + // Link to a labeling file + FileUrl string `json:"file_url"` + + // Status of labeling generation: + // - pending — task is in the queue. + // - in_progress — being generated. + // - completed — labeling file is ready. + // - error — error occurred during file generation + Status string `json:"status"` + } `json:"result"` +} + +// Method for getting labeling after using the /v1/posting/fbs/package-label/create method +func (c FBS) GetLabeling(params *GetLabelingParams) (*GetLabelingResponse, error) { + url := "/v1/posting/fbs/package-label/get" + + resp := &GetLabelingResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type PrintLabelingParams struct { + // Shipment identifier + PostingNumber []string `json:"posting_number"` +} + +type PrintLabelingResponse struct { + core.CommonResponse + + // Order content + Content string `json:"content"` +} + +// Generates a PDF file with a labeling for the specified shipments. You can pass a maximum of 20 identifiers in one request. +// If an error occurs for at least one shipment, the labeling will not be generated for all shipments in the request. +// +// We recommend requesting the label 45-60 seconds after the shipments were packed. +// +// The next postings are not ready error means that the label is not ready. Try again later +func (c FBS) PrintLabeling(params *PrintLabelingParams) (*PrintLabelingResponse, error) { + url := "/v2/posting/fbs/package-label" + + resp := &PrintLabelingResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} diff --git a/ozon/fbs_test.go b/ozon/fbs_test.go index f0ec3b1..5abe867 100644 --- a/ozon/fbs_test.go +++ b/ozon/fbs_test.go @@ -969,3 +969,248 @@ func TestChangeStatusTo(t *testing.T) { assertResponse(t, &test, sendBySellerResp) } } + +func TestPassShipmentToShipping(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *PassShipmentToShippingParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &PassShipmentToShippingParams{ + PostingNumber: []string{"33920143-1195-1"}, + }, + `{ + "result": true + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &PassShipmentToShippingParams{}, + `{ + "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().PassShipmentToShipping(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 TestCancelShipment(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *CancelShipmentParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &CancelShipmentParams{ + CancelReasonId: 352, + CancelReasonMessage: "Product is out of stock", + PostingNumber: "33920113-1231-1", + }, + `{ + "result": true + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &CancelShipmentParams{}, + `{ + "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().CancelShipment(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 TestCreateAct(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *CreateActParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &CreateActParams{ + ContainersCount: 1, + DeliveryMethodId: 230039077005, + DepartureDate: core.TimeFromString(t, "2006-01-02T15:04:05Z", "2022-06-10T11:42:06.444Z"), + }, + `{ + "result": { + "id": 5819327210249 + } + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &CreateActParams{}, + `{ + "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().CreateAct(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 TestGetLabeling(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *GetLabelingParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &GetLabelingParams{ + TaskId: 158, + }, + `{ + "result": { + "error": "24", + "file_url": "some-url", + "status": "completed" + } + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &GetLabelingParams{}, + `{ + "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().GetLabeling(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.Status == "" { + t.Errorf("Status cannot be empty") + } + } + } +} + +func TestPrintLabeling(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *PrintLabelingParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &PrintLabelingParams{ + PostingNumber: []string{"48173252-0034-4"}, + }, + `{ + "content": "https://cdn1.ozone.ru/s3/ozon-disk-api/c4a11c8b748033daf6cdd44aca7ed4c492e55d6f4810f13feae4792afa7934191647255705" + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &PrintLabelingParams{}, + `{ + "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().PrintLabeling(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) + } + } +}