7 Commits

5 changed files with 342 additions and 21 deletions

View File

@@ -479,3 +479,29 @@ const (
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"
)

View File

@@ -340,7 +340,7 @@ type PostingProduct struct {
type FBSCustomer struct {
// Delivery address details
Address FBSCustomerAddress `json:"customer"`
Address FBSCustomerAddress `json:"address"`
// Customer e-mail
CustomerEmail string `json:"customer_email"`
@@ -1594,7 +1594,13 @@ type PrintLabelingResponse struct {
core.CommonResponse
// Order content
Content string `json:"content"`
Content string `json:"file_content"`
// File name
FileName string `json:"file_name"`
// File type
ContentType string `json:"content_type"`
}
// Generates a PDF file with a labeling for the specified shipments. You can pass a maximum of 20 identifiers in one request.
@@ -2139,20 +2145,20 @@ type GetDigitalActParams struct {
// - act_of_acceptance — acceptance certificate,
// - act_of_mismatch — discrepancy certificate,
// - act_of_excess — surplus certificate
DocType string `json:"doc_type"`
DocType DigitalActType `json:"doc_type"`
}
type GetDigitalActResponse struct {
core.CommonResponse
// File content in binary format
Content string `json:"content"`
Content string `json:"file_content"`
// File name
Name string `json:"name"`
Name string `json:"file_name"`
// File type
Type string `json:"type"`
Type string `json:"content_type"`
}
// Specify the type of a certificate in the doc_type parameter: `act_of_acceptance`, `act_of_mismatch`, `act_of_excess`
@@ -2161,7 +2167,9 @@ func (c FBS) GetDigitalAct(params *GetDigitalActParams) (*GetDigitalActResponse,
resp := &GetDigitalActResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp, nil)
response, err := c.client.Request(http.MethodPost, url, params, resp, map[string]string{
"Content-Type": "application/json",
})
if err != nil {
return nil, err
}
@@ -2194,7 +2202,9 @@ func (c FBS) PackageUnitLabel(params *PackageUnitLabelsParams) (*PackageUnitLabe
resp := &PackageUnitLabelsResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp, nil)
response, err := c.client.Request(http.MethodPost, url, params, resp, map[string]string{
"Content-Type": "application/json",
})
if err != nil {
return nil, err
}
@@ -2645,3 +2655,105 @@ func (c FBS) ETGBCustomsDeclarations(params *ETGBCustomsDeclarationsParams) (*ET
return resp, nil
}
type BarcodeFromProductShipmentParams struct {
// Freight identifier
Id int64 `json:"id"`
}
type BarcodeFromProductShipmentResponse struct {
core.CommonResponse
// Link to barcode image
Content string `json:"file_content"`
// File name
Name string `json:"file_name"`
// File type
Type string `json:"content_type"`
}
// Method for getting a barcode to show at a pick-up point or sorting center during the shipment
func (c FBS) BarcodeFromProductShipment(params *BarcodeFromProductShipmentParams) (*BarcodeFromProductShipmentResponse, error) {
url := "/v2/posting/fbs/act/get-barcode"
resp := &BarcodeFromProductShipmentResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp, map[string]string{
"Content-Type": "image/png",
})
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}
type BarcodeValueFromProductShipmentParams struct {
// Freight identifier
Id int64 `json:"id"`
}
type BarcodeValueFromProductShipmentResponse struct {
core.CommonResponse
// Barcode in text format
Result string `json:"result"`
}
// Use this method to get the barcode from the /v2/posting/fbs/act/get-barcode response in text format.
func (c FBS) BarcodeValueFromProductShipment(params *BarcodeValueFromProductShipmentParams) (*BarcodeValueFromProductShipmentResponse, error) {
url := "/v2/posting/fbs/act/get-barcode/text"
resp := &BarcodeValueFromProductShipmentResponse{}
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 GetActPDFParams struct {
// Document generation task number (freight identifier) received from
// the POST `/v2/posting/fbs/act/create` method
Id int64 `json:"id"`
}
type GetActPDFResponse struct {
core.CommonResponse
// File content in binary format
FileContent string `json:"file_content"`
// File name
FileName string `json:"file_name"`
// File type
ContentType string `json:"content_type"`
}
// Get the generated transfer documents in PDF format: an acceptance and transfer certificate and a waybill.
//
// If you are not connected to electronic document circulation (EDC), the method returns an acceptance and transfer certificate and a waybill.
//
// If you are connected to EDC, the method returns a waybill only.
func (c FBS) GetActPDF(params *GetActPDFParams) (*GetActPDFResponse, error) {
url := "/v2/posting/fbs/act/get-pdf"
resp := &GetActPDFResponse{}
response, err := c.client.Request(http.MethodPost, url, params, resp, map[string]string{
"Content-Type": "application/pdf",
})
if err != nil {
return nil, err
}
response.CopyCommonResponse(&resp.CommonResponse)
return resp, nil
}

View File

@@ -1132,9 +1132,8 @@ func TestGetLabeling(t *testing.T) {
},
`{
"result": {
"error": "24",
"file_url": "some-url",
"status": "completed"
"status": "completed",
"file_url": "https://cdn1.ozone.ru/s3/sc-temporary/e6/0c/e60cdfd7aed78c2b44d134504fbd591d.pdf"
}
}`,
},
@@ -1186,7 +1185,9 @@ func TestPrintLabeling(t *testing.T) {
PostingNumber: []string{"48173252-0034-4"},
},
`{
"content": "https://cdn1.ozone.ru/s3/ozon-disk-api/c4a11c8b748033daf6cdd44aca7ed4c492e55d6f4810f13feae4792afa7934191647255705"
"content_type": "application/pdf",
"file_name": "ticket-170660-2023-07-13T13:17:06Z.pdf",
"file_content": "%PDF-1.7\n%âãÏÓ\n53 0 obj\n<</MarkInfo<</Marked true/Type/MarkInfo>>/Pages 9 0 R/StructTreeRoot 10 0 R/Type/Catalog>>\nendobj\n8 0 obj\n<</Filter/FlateDecode/Length 2888>>\nstream\nxœå[[ݶ\u0011~?¿BÏ\u0005Bs†\u001c^\u0000Àwí5ú\u0010 m\u0016Èsà¦)\n;hÒ\u0014èÏïG‰\u0014)‰<{äµ] ]?¬¬oIÎ}†¤F±‰óϤñï\u001bÕü×X­´OÏï?^~¹$<ø¨È9q\u0013Y\u0012åñì§_¼|ÿ‡égü\t+\u0012\u001bxžª}Æxšҿ¿¼›–‡_º¼xg¦Ÿþ5Oku˜œÌ3ýíògüûå\"Ni\u0016C\u0001°\u000fA9g‰'r¢\"\u0013YóĪ\u0018NÑ{\u001d–ÕóZ¬\\Ô\""
}`,
},
// Test No Client-Id or Api-Key
@@ -1212,6 +1213,12 @@ func TestPrintLabeling(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.Content == "" {
t.Error("content cannot be empty")
}
}
}
}
@@ -1888,9 +1895,9 @@ func TestGetDigitalAct(t *testing.T) {
DocType: "act_of_acceptance",
},
`{
"content": "string",
"name": "string",
"type": "string"
"content_type": "application/pdf",
"file_name": "20816409_act_of_mismatch.pdf",
"file_content": "%PDF-1.4\n%ÓôÌá\n1 0 obj\n<<\n/Creator(Chromium)\n/Producer(PDFsharp 1.50.5147 \\([www.pdfsharp.com|http://www.pdfsharp.com/]\\) \\(Original: Skia/PDF m103\\))\n/CreationDate(D:20230625092529+00'00')\n/ModDate(D:20230625092529+00'00')\n>>\nendobj\n2 0 obj\n<<\n/Type/Page\n/Resources\n<<\n/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]\n/ExtGState\n<<\n/G3 3 0 R\n/G8 8 0 R\n>>\n/XObject\n<<\n/X6 6 0 R\n/X7 7 0 R\n>>\n/Font\n<<\n/F4 4 0 R\n/F5 5 0 R\n>>\n>>\n/MediaBox[0 0 594.96 841.92]\n/Contents 9 0 R\n/StructParents 0\n/Parent 13 0 R\n/Group\n<<\n/CS/DeviceRGB\n/S/Transparency\n>>\n>>\nendobj\n3 0 obj\n<<\n/ca 1\n/BM/Normal\n>>\nendobj\n4 0 obj\n<<\n/Type/Font\n/Subtype/Type0\n/BaseFont/AAAAAA+LiberationSans\n/Encoding/Identity-H\n/DescendantFonts[160 0 R]\n/ToUnicode 161 0 R\n>>\nendobj\n5 0 obj\n<<\n/Type/Font\n/Subtype/Type0\n/BaseFont/BAAAAA+LiberationSans-Bold\n/Encoding/Identity-H\n/DescendantFonts[164 0"
}`,
},
// Test No Client-Id or Api-Key
@@ -2526,3 +2533,169 @@ func TestETGBCustomsDeclarations(t *testing.T) {
}
}
}
func TestBarcodeFromProductShipment(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *BarcodeFromProductShipmentParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&BarcodeFromProductShipmentParams{
Id: 295662811,
},
`{
"content_type": "image/png",
"file_name": "20913984_barcode.png",
"file_content": "‰PNG\r\n\u001a\n\u0000\u0000\u0000\rIHDR\u0000\u0000\u0003\u0010\u0000\u0000\u0000\u0010\u0000\u0000\u0000\u0000íZ\u000e'\u0000\u0000\u0002pIDATxœìÕÁJ\u00031\u0014@Q+þÿ/×E\u0017\u000e¼›\u0010u¡-ç¬$£Éˌp?î÷·§t» }ýü¸ÃcåzŸ¹2w˜OWû\\Ϛ뫧×Ùö;œì|rÇýßîç¼úî{˜§¬N?™í7oì•v¸®Ÿµ¹Ãù„û•¹¾ÿÏ9ÿî?›až¸ºéê7O&߿É9çÉ\u000eÏáý¯\u0007\u0000à\u0012\b\u0000’@\u0000\u0004\u0002€$\u0010\u0000$\u0000 \t\u0004\u0000I \u0000H\u0002\u0001@\u0012\b\u0000’@\u0000\u0004\u0002€$\u0010\u0000$\u0000 \t\u0004\u0000I \u0000H\u0002\u0001@\u0012\b\u0000’@\u0000\u0004\u0002€$\u0010\u0000$\u0000 \t\u0004\u0000I \u0000H\u0002\u0001@\u0012\b\u0000’@\u0000\u0004\u0002€$\u0010\u0000$\u0000 \t\u0004\u0000I \u0000H\u0002\u0001@\u0012\b\u0000’@\u0000\u0004\u0002€$\u0010\u0000"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&BarcodeFromProductShipmentParams{},
`{
"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().BarcodeFromProductShipment(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.Content == "" {
t.Errorf("content cannot be empty")
}
if resp.Type == "" {
t.Error("type cannot be empty")
}
if resp.Name == "" {
t.Error("name cannot be empty")
}
}
}
}
func TestBarcodeValueFromProductShipment(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *BarcodeValueFromProductShipmentParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&BarcodeValueFromProductShipmentParams{
Id: 295662811,
},
`{
"result": "%303%24276481394"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&BarcodeValueFromProductShipmentParams{},
`{
"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().BarcodeValueFromProductShipment(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 == "" {
t.Errorf("result cannot be empty")
}
}
}
}
func TestGetActPDF(t *testing.T) {
t.Parallel()
tests := []struct {
statusCode int
headers map[string]string
params *GetActPDFParams
response string
}{
// Test Ok
{
http.StatusOK,
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
&GetActPDFParams{
Id: 22435521842000,
},
`{
"content_type": "application/pdf",
"file_name": "20928233.pdf",
"file_content": "binarystring"
}`,
},
// Test No Client-Id or Api-Key
{
http.StatusUnauthorized,
map[string]string{},
&GetActPDFParams{},
`{
"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().GetActPDF(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.FileContent == "" {
t.Errorf("result cannot be empty")
}
}
}
}

View File

@@ -655,6 +655,15 @@ type UpdatePricesPrice struct {
// If the current price of the product is from 400 to 10 000 rubles inclusive, the difference between the values of price and old_price fields should be more than 5%, but not less than 20 rubles.
Price string `json:"price"`
// Attribute for enabling and disabling pricing strategies auto-application
//
// If you've previously enabled automatic application of pricing strategies and don't want to disable it, pass UNKNOWN in the next requests.
//
// If you pass `ENABLED` in this parameter, pass `strategy_id` in the `/v1/pricing-strategy/products/add` method request.
//
// If you pass `DISABLED` in this parameter, the product is removed from the strategy
PriceStrategyEnabled PriceStrategy `json:"price_strategy_enabled"`
// Product identifier
ProductId int64 `json:"product_id"`
}

View File

@@ -448,12 +448,13 @@ func TestUpdatePrices(t *testing.T) {
&UpdatePricesParams{
Prices: []UpdatePricesPrice{
{
AutoActionEnabled: "UNKNOWN",
CurrencyCode: "RUB",
MinPrice: "800",
OldPrice: "0",
Price: "1448",
ProductId: 1386,
AutoActionEnabled: "UNKNOWN",
CurrencyCode: "RUB",
MinPrice: "800",
OldPrice: "0",
Price: "1448",
ProductId: 1386,
PriceStrategyEnabled: PriceStrategyUnknown,
},
},
},