refactoring

correct example for methods
fix linters error
add `/api/v5/orders/{externalId}/delivery/cancel`, `/api/v5/store/product-groups/create`, `/api/v5/store/product-groups/{externalId}/edit`
This commit is contained in:
Ruslan Efanov 2022-12-08 16:24:52 +03:00
parent ddc2b3f785
commit 1bd5b77b3f
8 changed files with 399 additions and 48 deletions

View File

@ -34,6 +34,7 @@ jobs:
version: v1.45.2
only-new-issues: true
skip-pkg-cache: true
args: --build-tags=testutils
tests:
name: Tests
runs-on: ubuntu-latest
@ -58,13 +59,13 @@ jobs:
env:
COVERAGE: ${{ matrix.coverage }}
if: env.COVERAGE != 1
run: go test ./...
run: go test -tags=testutils ./...
- name: Tests with coverage
env:
COVERAGE: ${{ matrix.coverage }}
if: env.COVERAGE == 1
run: |
go test ./... -race -coverprofile=coverage.txt -covermode=atomic "$d"
go test -tags=testutils ./... -race -coverprofile=coverage.txt -covermode=atomic "$d"
- name: Coverage
env:
COVERAGE: ${{ matrix.coverage }}

View File

@ -1,6 +1,8 @@
run:
skip-dirs-use-default: true
allow-parallel-runners: true
skip-files:
- testutils.go
output:
format: colored-line-number

226
client.go
View File

@ -5834,7 +5834,7 @@ func (c *Client) LoyaltyAccountCreate(site string, loyaltyAccount SerializedCrea
// if data.Success == true {
// log.Printf("%v", data.LoyaltyAccount.PhoneNumber)
// }
func (c *Client) LoyaltyAccountEdit(ID int, loyaltyAccount SerializedEditLoyaltyAccount) (EditLoyaltyAccountResponse, int, error) {
func (c *Client) LoyaltyAccountEdit(id int, loyaltyAccount SerializedEditLoyaltyAccount) (EditLoyaltyAccountResponse, int, error) {
var result EditLoyaltyAccountResponse
loyaltyAccountJSON, _ := json.Marshal(loyaltyAccount)
@ -5842,7 +5842,7 @@ func (c *Client) LoyaltyAccountEdit(ID int, loyaltyAccount SerializedEditLoyalty
"loyaltyAccount": {string(loyaltyAccountJSON)},
}
resp, status, err := c.PostRequest(fmt.Sprintf("/loyalty/account/%d/edit", ID), p)
resp, status, err := c.PostRequest(fmt.Sprintf("/loyalty/account/%d/edit", id), p)
if err != nil {
return result, status, err
@ -5878,10 +5878,10 @@ func (c *Client) LoyaltyAccountEdit(ID int, loyaltyAccount SerializedEditLoyalty
// if data.Success == true {
// log.Printf("%v", data.LoyaltyAccount.PhoneNumber)
// }
func (c *Client) LoyaltyAccount(ID int) (LoyaltyAccountResponse, int, error) {
func (c *Client) LoyaltyAccount(id int) (LoyaltyAccountResponse, int, error) {
var result LoyaltyAccountResponse
resp, status, err := c.GetRequest(fmt.Sprintf("/loyalty/account/%d", ID))
resp, status, err := c.GetRequest(fmt.Sprintf("/loyalty/account/%d", id))
if err != nil {
return result, status, err
@ -5917,10 +5917,10 @@ func (c *Client) LoyaltyAccount(ID int) (LoyaltyAccountResponse, int, error) {
// if data.Success == true {
// log.Printf("%v", data.LoyaltyAccount.Active)
// }
func (c *Client) LoyaltyAccountActivate(ID int) (LoyaltyAccountActivateResponse, int, error) {
func (c *Client) LoyaltyAccountActivate(id int) (LoyaltyAccountActivateResponse, int, error) {
var result LoyaltyAccountActivateResponse
resp, status, err := c.PostRequest(fmt.Sprintf("/loyalty/account/%d/activate", ID), strings.NewReader(""))
resp, status, err := c.PostRequest(fmt.Sprintf("/loyalty/account/%d/activate", id), strings.NewReader(""))
if err != nil {
return result, status, err
@ -5962,11 +5962,11 @@ func (c *Client) LoyaltyAccountActivate(ID int) (LoyaltyAccountActivateResponse,
// if data.Success == true {
// log.Printf("%v", data.LoyaltyBonus.ActivationDate)
// }
func (c *Client) LoyaltyBonusCredit(ID int, req LoyaltyBonusCreditRequest) (LoyaltyBonusCreditResponse, int, error) {
func (c *Client) LoyaltyBonusCredit(id int, req LoyaltyBonusCreditRequest) (LoyaltyBonusCreditResponse, int, error) {
var result LoyaltyBonusCreditResponse
p, _ := query.Values(req)
resp, status, err := c.PostRequest(fmt.Sprintf("/loyalty/account/%d/bonus/credit", ID), p)
resp, status, err := c.PostRequest(fmt.Sprintf("/loyalty/account/%d/bonus/credit", id), p)
if err != nil {
return result, status, err
@ -6010,13 +6010,13 @@ func (c *Client) LoyaltyBonusCredit(ID int, req LoyaltyBonusCreditRequest) (Loya
// }
// }
func (c *Client) LoyaltyBonusStatusDetails(
ID int, statusType string, request LoyaltyBonusStatusDetailsRequest,
id int, statusType string, request LoyaltyBonusStatusDetailsRequest,
) (LoyaltyBonusDetailsResponse, int, error) {
var result LoyaltyBonusDetailsResponse
p, _ := query.Values(request)
resp, status, err := c.GetRequest(fmt.Sprintf("/loyalty/account/%d/bonus/%s/details?%s", ID, statusType, p.Encode()))
resp, status, err := c.GetRequest(fmt.Sprintf("/loyalty/account/%d/bonus/%s/details?%s", id, statusType, p.Encode()))
if err != nil {
return result, status, err
@ -6040,13 +6040,13 @@ func (c *Client) LoyaltyBonusStatusDetails(
// var client = retailcrm.New("https://demo.url", "09jIJ")
//
// req := LoyaltyAccountsRequest{
// Filter: LoyaltyAccountApiFilter{
// Filter: LoyaltyAccountAPIFilter{
// Status: "activated",
// PhoneNumber: "89185556363",
// Ids: []int{14},
// Level: 5,
// Loyalties: []int{2},
// CustomerId: "109",
// CustomerID: "109",
// },
// }
//
@ -6129,7 +6129,7 @@ func (c *Client) LoyaltyAccounts(req LoyaltyAccountsRequest) (LoyaltyAccountsRes
func (c *Client) LoyaltyCalculate(req LoyaltyCalculateRequest) (LoyaltyCalculateResponse, int, error) {
var result LoyaltyCalculateResponse
orderJSON, err := json.Marshal(req.Order)
orderJSON, _ := json.Marshal(req.Order)
p := url.Values{
"site": {req.Site},
@ -6152,7 +6152,7 @@ func (c *Client) LoyaltyCalculate(req LoyaltyCalculateRequest) (LoyaltyCalculate
return result, status, nil
}
// GetLoyalties calculations of the maximum discount
// GetLoyalties returns list of loyalty programs
//
// For more information see https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5#get--api-v5-loyalty-loyalties
//
@ -6161,7 +6161,7 @@ func (c *Client) LoyaltyCalculate(req LoyaltyCalculateRequest) (LoyaltyCalculate
// var client = retailcrm.New("https://demo.url", "09jIJ")
//
// req := LoyaltiesRequest{
// Filter: LoyaltyApiFilter{
// Filter: LoyaltyAPIFilter{
// Active: active,
// Ids: []int{2},
// Sites: []string{"main"},
@ -6204,15 +6204,15 @@ func (c *Client) GetLoyalties(req LoyaltiesRequest) (LoyaltiesResponse, int, err
return result, status, nil
}
// GetLoyaltyById calculations of the maximum discount
// GetLoyaltyByID return program of loyalty by id
//
// For more information see https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5#get--api-v5-loyalty-loyalties
// For more information see https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5#get--api-v5-loyalty-loyalties-id
//
// Example:
//
// var client = retailcrm.New("https://demo.url", "09jIJ")
//
// data, status, err := client.GetLoyaltyById(2)
// data, status, err := client.GetLoyaltyByID(2)
//
// if err != nil {
// if apiErr, ok := retailcrm.AsAPIError(err); ok {
@ -6226,10 +6226,10 @@ func (c *Client) GetLoyalties(req LoyaltiesRequest) (LoyaltiesResponse, int, err
// log.Printf("%v", res.Loyalty.ID)
// log.Printf("%v", res.Loyalty.Active)
// }
func (c *Client) GetLoyaltyById(ID int) (LoyaltyResponse, int, error) {
func (c *Client) GetLoyaltyByID(id int) (LoyaltyResponse, int, error) {
var result LoyaltyResponse
resp, status, err := c.GetRequest(fmt.Sprintf("/loyalty/loyalties/%d", ID))
resp, status, err := c.GetRequest(fmt.Sprintf("/loyalty/loyalties/%d", id))
if err != nil {
return result, status, err
@ -6244,10 +6244,36 @@ func (c *Client) GetLoyaltyById(ID int) (LoyaltyResponse, int, error) {
return result, status, nil
}
func (c *Client) OrderIntegrationDeliveryCancel(by string, force bool, ID string) (SuccessfulResponse, int, error) {
// OrderIntegrationDeliveryCancel cancels of integration delivery
//
// For more information see https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5#post--api-v5-orders-externalId-delivery-cancel
//
// Example:
//
// var client = retailcrm.New("https://demo.url", "09jIJ")
//
// data, status, err := client.OrderIntegrationDeliveryCancel("externalId", false, "1001C")
//
// if err != nil {
// if apiErr, ok := retailcrm.AsAPIError(err); ok {
// log.Fatalf("http status: %d, %s", status, apiErr.String())
// }
//
// log.Fatalf("http status: %d, error: %s", status, err)
// }
//
// if data.Success == true {
// log.Printf("%v", res.Success)
// }
func (c *Client) OrderIntegrationDeliveryCancel(by string, force bool, id string) (SuccessfulResponse, int, error) {
var result SuccessfulResponse
resp, status, err := c.PostRequest(fmt.Sprintf("/orders/%s/delivery/cancel?by=%s&force=%t", ID, checkBy(by), force), strings.NewReader(""))
p := url.Values{
"by": {checkBy(by)},
"force": {fmt.Sprintf("%t", force)},
}
resp, status, err := c.PostRequest(fmt.Sprintf("/orders/%s/delivery/cancel?%s", id, p.Encode()), strings.NewReader(""))
if err != nil {
return result, status, err
@ -6261,3 +6287,159 @@ func (c *Client) OrderIntegrationDeliveryCancel(by string, force bool, ID string
return result, status, nil
}
// CreateProductsGroup adds a product group
//
// For more information see https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5#post--api-v5-store-product-groups-create
//
// Example:
//
// var client = retailcrm.New("https://demo.url", "09jIJ")
//
// group := ProductGroup{
// ParentID: 125,
// Name: "Фрукты",
// Site: "main",
// Active: true,
// ExternalID: "abc22",
// }
//
// data, status, err := client.CreateProductsGroup(group)
//
// if err != nil {
// if apiErr, ok := retailcrm.AsAPIError(err); ok {
// log.Fatalf("http status: %d, %s", status, apiErr.String())
// }
//
// log.Fatalf("http status: %d, error: %s", status, err)
// }
//
// if data.Success == true {
// log.Printf("%v", res.ID)
// }
func (c *Client) CreateProductsGroup(group ProductGroup) (ActionProductsGroupResponse, int, error) {
var result ActionProductsGroupResponse
groupJSON, _ := json.Marshal(group)
p := url.Values{
"productGroup": {string(groupJSON)},
}
resp, status, err := c.PostRequest("/store/product-groups/create", p)
if err != nil {
return result, status, err
}
err = json.Unmarshal(resp, &result)
if err != nil {
return result, status, err
}
return result, status, nil
}
// EditProductsGroup edits a product group
//
// For more information see https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5#post--api-v5-store-product-groups-externalId-edit
//
// Example:
//
// var client = retailcrm.New("https://demo.url", "09jIJ")
//
// group := ProductGroup{
// Name: "Овощи",
// Active: true,
// ExternalID: "abc22",
// }
//
// data, status, err := client.EditProductsGroup("by", "125", "main", group)
//
// if err != nil {
// if apiErr, ok := retailcrm.AsAPIError(err); ok {
// log.Fatalf("http status: %d, %s", status, apiErr.String())
// }
//
// log.Fatalf("http status: %d, error: %s", status, err)
// }
//
// if data.Success == true {
// log.Printf("%v", res.ID)
// }
func (c *Client) EditProductsGroup(by, id, site string, group ProductGroup) (ActionProductsGroupResponse, int, error) {
var result ActionProductsGroupResponse
groupJSON, _ := json.Marshal(group)
p := url.Values{
"by": {checkBy(by)},
"site": {site},
"productGroup": {string(groupJSON)},
}
resp, status, err := c.PostRequest(fmt.Sprintf("/store/product-groups/%s/edit", id), p)
if err != nil {
return result, status, err
}
err = json.Unmarshal(resp, &result)
if err != nil {
return result, status, err
}
return result, status, nil
}
func (c *Client) GetOrderPlate(by, orderID, site string, plateID int) (io.ReadCloser, int, error) {
p := url.Values{
"by": {checkBy(by)},
"site": {site},
}
requestURL := fmt.Sprintf("%s/api/v5/orders/%s/plates/%d/print?%s", c.URL, orderID, plateID, p.Encode())
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
return nil, 0, err
}
req.Header.Set("X-API-KEY", c.Key)
if c.Debug {
c.writeLog("API Request: %s %s", requestURL, c.Key)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, 0, err
}
if resp.StatusCode >= http.StatusInternalServerError {
return nil, resp.StatusCode, CreateGenericAPIError(
fmt.Sprintf("HTTP request error. Status code: %d.", resp.StatusCode))
}
if resp.StatusCode >= http.StatusBadRequest && resp.StatusCode < http.StatusInternalServerError {
res, err := buildRawResponse(resp)
if err != nil {
return nil, 0, err
}
return nil, resp.StatusCode, CreateAPIError(res)
}
reader := resp.Body
err = reader.Close()
if err != nil {
return nil, 0, err
}
return reader, resp.StatusCode, nil
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/google/go-querystring/query"
"io"
"io/ioutil"
"log"
"math/rand"
@ -523,7 +524,7 @@ func TestClient_CustomersUpload(t *testing.T) {
func TestClient_CustomersUpload_Fail(t *testing.T) {
c := client()
customers := []Customer{{ExternalID: strconv.Itoa(iCodeFail)}}
customers := []Customer{{ExternalID: strconv.Itoa(iCodeFail)}, {FirstName: "John"}}
defer gock.Off()
@ -531,13 +532,29 @@ func TestClient_CustomersUpload_Fail(t *testing.T) {
p := url.Values{
"customers": {string(str)},
}
// TODO ???
gock.New(crmURL).
Post("/api/v5/customers/upload").
MatchType("url").
BodyString(p.Encode()).
Reply(460).
BodyString(`{"success": false, "errorMsg": "Customers are loaded with ErrorsList"}`)
BodyString(fmt.Sprintf(`{
"success": false,
"uploadedCustomers": [
{
"id": 132
}
],
"failedCustomers": [
{
"externalId": "%d"
}
],
"errorMsg": "Customers are loaded with errors",
"errors": {
"managerId": "Something went wrong"
}
}`, iCodeFail))
data, _, err := c.CustomersUpload(customers)
if err == nil {
@ -5176,9 +5193,19 @@ func TestClient_IntegrationModule(t *testing.T) {
},
}
integrations, err := json.Marshal(integrationModule.Integrations)
assert.NoError(t, err)
jsonData := fmt.Sprintf(
`{"code":"%s","integrationCode":"%s","active":false,"name":"%s","logo":"%s","clientId":"%s","baseUrl":"%s","accountUrl":"%s"}`,
code, code, integrationModule.Name, integrationModule.Logo, integrationModule.ClientID, integrationModule.BaseURL, integrationModule.AccountURL,
`{"code":"%s","integrationCode":"%s","active":false,"name":"%s","logo":"%s","clientId":"%s","baseUrl":"%s","accountUrl":"%s","integrations":%s}`,
code,
code,
integrationModule.Name,
integrationModule.Logo,
integrationModule.ClientID,
integrationModule.BaseURL,
integrationModule.AccountURL,
integrations,
)
pr := url.Values{
@ -7302,13 +7329,13 @@ func TestClient_LoyaltyAccounts(t *testing.T) {
defer gock.Off()
req := LoyaltyAccountsRequest{
Filter: LoyaltyAccountApiFilter{
Filter: LoyaltyAccountAPIFilter{
Status: "activated",
PhoneNumber: "89185556363",
Ids: []int{14},
Level: 5,
Loyalties: []int{2},
CustomerId: "109",
CustomerID: "109",
},
}
@ -7413,7 +7440,7 @@ func TestClient_GetLoyalties(t *testing.T) {
*active = 1
req := LoyaltiesRequest{
Filter: LoyaltyApiFilter{
Filter: LoyaltyAPIFilter{
Active: active,
Ids: []int{2},
Sites: []string{"main"},
@ -7463,7 +7490,7 @@ func TestClient_GetLoyaltyById(t *testing.T) {
Reply(http.StatusOK).
JSON(getLoyaltyResponse())
res, status, err := client().GetLoyaltyById(2)
res, status, err := client().GetLoyaltyByID(2)
if err != nil {
t.Errorf("%v", err)
@ -7514,3 +7541,134 @@ func TestClient_OrderIntegrationDeliveryCancel(t *testing.T) {
t.Errorf("%v", err)
}
}
func TestClient_CreateProductsGroup(t *testing.T) {
defer gock.Off()
group := ProductGroup{
ParentID: 19,
Name: "Подкатегория хлама",
Site: "main",
Active: true,
Description: "Ну и хлам!",
ExternalID: "ti22",
}
body, err := json.Marshal(group)
assert.NoError(t, err)
p := url.Values{
"productGroup": {string(body)},
}
gock.New(crmURL).
Post(prefix + "/store/product-groups/create").
BodyString(p.Encode()).
Reply(http.StatusCreated).
JSON(`{"success":true,"id":32}`)
res, status, err := client().CreateProductsGroup(group)
if err != nil {
t.Errorf("%v", err)
}
if !statuses[status] {
t.Errorf("%v", err)
}
if res.Success != true {
t.Errorf("%v", err)
}
assert.Equal(t, 32, res.ID)
}
func TestClient_EditProductsGroup(t *testing.T) {
defer gock.Off()
group := ProductGroup{
Name: "Ценнейший хлам из хламов",
Active: true,
ExternalID: "ti22",
}
body, err := json.Marshal(group)
assert.NoError(t, err)
p := url.Values{
"by": {"id"},
"site": {"main"},
"productGroup": {string(body)},
}
gock.New(crmURL).
Post(prefix + "/store/product-groups/32/edit").
BodyString(p.Encode()).
Reply(http.StatusOK).
JSON(`{"success":true,"id":32}`)
res, status, err := client().EditProductsGroup("id", "32", "main", group)
if err != nil {
t.Errorf("%v", err)
}
if !statuses[status] {
t.Errorf("%v", err)
}
if res.Success != true {
t.Errorf("%v", err)
}
assert.Equal(t, 32, res.ID)
}
func TestClient_GetOrderPlate(t *testing.T) {
defer gock.Off()
gock.New(crmURL).
Get(prefix + "/orders/124/plates/1/print").
MatchParams(map[string]string{
"by": "id",
"site": "main",
}).
Reply(200).
Body(io.NopCloser(strings.NewReader("PDF")))
data, status, err := client().GetOrderPlate("id", "124", "main", 1)
if err != nil {
t.Errorf("%v", err)
}
assert.NotNil(t, data)
assert.Equal(t, status, http.StatusOK)
}
func TestClient_GetOrderPlateFail(t *testing.T) {
defer gock.Off()
gock.New(crmURL).
Get(prefix + "/orders/124/plates/1/print").
MatchParams(map[string]string{
"by": "id",
"site": "main",
}).
Reply(404).
JSON(`{
"success": false,
"errorMsg": "Not found"
}`)
data, status, err := client().GetOrderPlate("id", "124", "main", 1)
if err == nil {
t.Error("Expected error")
}
assert.Nil(t, data)
assert.Equal(t, status, http.StatusNotFound)
assert.Equal(t, "Not found", err.(APIError).Error()) //nolint:errorlint
}

View File

@ -440,11 +440,11 @@ type AccountBonusOperationsFilter struct {
CreatedAtTo string `url:"createdAtTo,omitempty"`
}
type LoyaltyBonusApiFilterType struct {
type LoyaltyBonusAPIFilterType struct {
Date string `url:"date,omitempty"`
}
type LoyaltyAccountApiFilter struct {
type LoyaltyAccountAPIFilter struct {
ID string `url:"id,omitempty"`
Status string `url:"status,,omitempty"`
Customer string `url:"customer,omitempty"`
@ -463,11 +463,11 @@ type LoyaltyAccountApiFilter struct {
BurnDateFrom string `url:"burnDateFrom,omitempty"`
BurnDateTo string `url:"burnDateTo,omitempty"`
CustomFields []string `url:"customFields,omitempty,brackets"`
CustomerId string `url:"customerId,omitempty"`
CustomerExternalId string `url:"customerExternalId,omitempty"`
CustomerID string `url:"customerId,omitempty"`
CustomerExternalID string `url:"customerExternalId,omitempty"`
}
type LoyaltyApiFilter struct {
type LoyaltyAPIFilter struct {
Active *int `url:"active,omitempty"`
Blocked *int `url:"blocked,omitempty"`
Ids []int `url:"ids,omitempty,brackets"`

View File

@ -256,13 +256,13 @@ type LoyaltyBonusCreditRequest struct {
type LoyaltyBonusStatusDetailsRequest struct {
Limit int `url:"limit,omitempty"`
Page int `url:"page,omitempty"`
Filter LoyaltyBonusApiFilterType `url:"filter,omitempty"`
Filter LoyaltyBonusAPIFilterType `url:"filter,omitempty"`
}
type LoyaltyAccountsRequest struct {
Limit int `url:"limit,omitempty"`
Page int `url:"limit,omitempty"`
Filter LoyaltyAccountApiFilter `url:"filter,omitempty"`
Filter LoyaltyAccountAPIFilter `url:"filter,omitempty"`
}
type LoyaltyCalculateRequest struct {
@ -274,7 +274,7 @@ type LoyaltyCalculateRequest struct {
type LoyaltiesRequest struct {
Limit int `url:"limit,omitempty"`
Page int `url:"page,omitempty"`
Filter LoyaltyApiFilter `url:"filter,omitempty"`
Filter LoyaltyAPIFilter `url:"filter,omitempty"`
}
// SystemURL returns system URL from the connection request without trailing slash.

View File

@ -644,3 +644,8 @@ type LoyaltyResponse struct {
SuccessfulResponse
Loyalty Loyalty `json:"loyalty"`
}
type ActionProductsGroupResponse struct {
SuccessfulResponse
ID int `json:"id"`
}

View File

@ -785,7 +785,7 @@ type DeliveryType struct {
VatRate string `json:"vatRate,omitempty"`
DefaultForCrm bool `json:"defaultForCrm,omitempty"`
DeliveryServices []string `json:"deliveryServices,omitempty"`
PaymentTypes []string `json:"paymentTypes,omitempty"` //Deprecated, use DeliveryPaymentTypes
PaymentTypes []string `json:"paymentTypes,omitempty"` // Deprecated, use DeliveryPaymentTypes
DeliveryPaymentTypes []DeliveryPaymentType `json:"deliveryPaymentTypes,omitempty"`
}
@ -927,7 +927,7 @@ type Site struct {
DefaultForCRM bool `json:"defaultForCrm,omitempty"`
Ordering int `json:"ordering,omitempty"`
IsDemo bool `json:"isDemo,omitempty"`
CatalogId string `json:"catalogId,omitempty"`
CatalogID string `json:"catalogId,omitempty"`
IsCatalogMainSite bool `json:"isCatalogMainSite,omitempty"`
}
@ -948,11 +948,14 @@ type Store struct {
// ProductGroup type.
type ProductGroup struct {
ID int `json:"id,omitempty"`
ParentID int `json:"parentId,omitempty"`
Name string `json:"name,omitempty"`
Site string `json:"site,omitempty"`
Active bool `json:"active,omitempty"`
ID int `json:"id,omitempty"`
ParentID int `json:"parentId,omitempty"`
Name string `json:"name,omitempty"`
Site string `json:"site,omitempty"`
Active bool `json:"active,omitempty"`
Description string `json:"description,omitempty"`
ExternalID string `json:"externalId,omitempty"`
ParentExternalID string `json:"parentExternalId,omitempty"`
}
// BaseProduct type.