Compare commits

...

84 commits

Author SHA1 Message Date
c62a02aa1f
Merge pull request #100 from dendd1/master
fix for clear cart method
2025-04-04 15:14:19 +03:00
Суханов Данила
871459c8b7 fix for clear cart method 2025-04-04 15:12:04 +03:00
d0b0dd59d6
Merge pull request #99 from dendd1/master
support /customer-interaction/site/cart
2025-04-01 14:51:34 +03:00
Суханов Данила
cc83657f32 support /customer-interaction/site/cart 2025-04-01 14:49:13 +03:00
53e2ab5130
Merge pull request #98 from Neur0toxine/fix/costs_filter_field_types
Fix costs filter field types
2025-03-18 17:20:29 +03:00
0123057e86 fix lint issues 2025-03-18 17:18:06 +03:00
a8cae8b200 fix type in the tests 2025-03-18 17:11:38 +03:00
5fa64ff23e
Merge pull request #97 from Neur0toxine/rate-limit
rate limiter
2025-03-18 17:02:23 +03:00
e2113a640e fix linter 2025-03-18 17:00:42 +03:00
2f33b56cd3 rate limiter 2025-03-18 16:53:45 +03:00
Pavel Tsayukov
8892fe6895 Fix types of fields in CostsFilter 2025-02-18 14:55:13 +03:00
Pavel Tsayukov
908f16b173 Fix wrong name of query key for OrderExternalIds field 2025-02-18 14:55:12 +03:00
2859073353
Merge pull request #95 from Neur0toxine/fix-regionId-cityId
fix regionId & cityId unmarshaling error
2025-01-31 11:50:00 +03:00
a482cd1a2f update linter config 2025-01-31 11:47:59 +03:00
92a5741c84 fix ci 2025-01-31 11:41:22 +03:00
634ec386b1 fix regionId & cityId unmarshaling error 2025-01-31 11:38:47 +03:00
d101ddb097
Merge pull request #94 from RenCurs/transport-action
add mg transport action
2024-12-04 10:32:34 +03:00
Ruslan Efanov
3186470ed9 add mg transport action 2024-12-04 10:24:19 +03:00
904796f97a
Support for urlLike filter in GET /api/v5/store/products 2024-09-25 11:29:48 +03:00
Vlasov
2f0f55be42 Added support for urlLike filter in GET /api/v5/store/products 2024-09-24 18:38:29 +03:00
c79e6c0497
Fix loyalty account request fields 2024-09-11 15:01:53 +03:00
2587dd786a
add offers method 2024-09-11 14:56:04 +03:00
Ruslan Efanov
2333dbf493 add offers method 2024-09-11 14:54:33 +03:00
Aleksandr Kokockin
d195460141 rename expireDate field 2024-09-10 12:47:44 +03:00
Aleksandr Kokockin
e6fc8f1e0e rename page field 2024-09-10 12:47:24 +03:00
c9e5b1f79d
add mgTransport callbacks 2024-09-04 13:34:37 +03:00
3282ab045e
Merge branch 'master' into add-mgTransport-callbacks 2024-09-04 13:25:44 +03:00
4398f85214
add field refreshToken for mg integrations 2024-09-02 11:23:29 +03:00
Ruslan Efanov
77b49e04ab add field refreshToken for mg integrations 2024-09-02 11:13:24 +03:00
29062b8bf0 add title field to ChatVisitedPage 2024-03-20 15:01:10 +03:00
7c8e142cab country & city fields instead of ip field 2024-03-19 12:01:02 +03:00
e28631dcb2 fieldalignment fix 2024-03-19 12:01:02 +03:00
a4de6df146 fix field names 2024-03-19 12:01:02 +03:00
81a09e24d4 callbacks structs 2024-03-19 12:01:02 +03:00
b5e7c3ff33 add mgTransport callbacks 2024-03-19 12:01:02 +03:00
76135226fb
add services support 2023-12-28 12:55:00 +03:00
a72a57fbe1 add services support 2023-12-28 12:52:45 +03:00
c2a33378b8
add new API methods & new parameters 2023-09-06 15:44:19 +03:00
Danila
583362bfe3 Adding functionality from recent updates to the library 2023-09-06 07:40:46 +03:00
Danila
ab648cd06a Adding functionality from recent updates to the library 2023-09-05 18:09:36 +03:00
Danila
5c6d2ebead Adding functionality from recent updates to the library 2023-09-05 15:06:08 +03:00
407ecf5066
add mg settings to settings response 2023-08-11 12:23:51 +03:00
Kirill Sukhorukov
2875b8620a add mg settings to settings response 2023-08-10 12:25:39 +03:00
b445dfdfe5
add new order field DialogID and fix custom fields marshaling 2023-08-01 08:01:12 +03:00
1e9692ec15
Update marshaling.go 2023-07-31 16:52:08 +03:00
Kirill Sukhorukov
0ed90e8351 add new order field DialogID and fix custom fields marshaling 2023-07-27 14:06:03 +03:00
076ce77bdb
fix for template api methods 2023-05-29 15:08:31 +03:00
Ruslan Efanov
8377a8789d fix model fields 2023-05-26 17:19:44 +03:00
280f078632
add methods for mg templates 2023-05-24 11:20:05 +03:00
Ruslan Efanov
e6efd56497 fix test 2023-05-23 15:17:38 +03:00
Ruslan Efanov
9bd3d646fc correct templates methods 2023-05-23 13:09:05 +03:00
Ефанов Руслан
16e2bc304c add methods for mg templates 2023-05-17 17:51:33 +03:00
d08ed4e1b2
Add multi dictionary custom field support 2023-05-03 21:51:13 +03:00
Vlasov
afb7c1b881 Add multi dictionary custom field support 2023-05-03 18:04:53 +03:00
e513134df9
update linter and it's rules 2023-01-24 17:42:34 +03:00
5b7ed8697e
update types with new fields 2023-01-24 17:42:13 +03:00
107a4d150b update types with new fields 2023-01-24 11:13:20 +03:00
a51bab6df4
Add Settings field NonWorkingDays 2023-01-17 17:52:31 +03:00
Ruslan Efanov
49905ab9c6 update linter and it's rules 2022-12-30 11:19:20 +03:00
06b0395a93
/api/v5/notifications/send support 2022-12-29 16:20:23 +03:00
67d72f2fe1 /api/v5/notifications/send support 2022-12-29 16:17:29 +03:00
Vragov Roman
bafdf24755 Add Settings field NonWorkingDays 2022-12-26 16:18:03 +03:00
6c5eb72848
add support for more methods 2022-12-09 14:37:12 +03:00
Ruslan Efanov
141c247482 fix linter errors 2022-12-09 12:34:16 +03:00
Ruslan Efanov
22cbdd7fcf add parameter of failed entity in response for upload methods for orders and customers 2022-12-09 11:38:21 +03:00
Ruslan Efanov
1bd5b77b3f 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`
2022-12-08 17:31:08 +03:00
Ruslan Efanov
ddc2b3f785 add /api/v5/loyalty/loyalties, /api/v5/loyalty/loyalties/{id}
add field in `deliveryPaymentTypes` for ` /api/v5/reference/delivery-types`
2022-12-08 13:32:54 +03:00
Ruslan Efanov
90f790e148 add /api/v5/loyalty/accounts 2022-12-07 17:40:52 +03:00
Ruslan Efanov
921d4c1295 Add/edit methods
add `/api/v5/store/products/batch/create`, `/api/v5/store/products/batch/edit`
add fields `isError` and `isPreprocessing` for statuses of integration delivery
add field `deliveryType` for response of creating integration module with inegration delivery
add `/api/v5/loyalty/account/create`, `/api/v5/loyalty/account/{id}`, `/api/v5/loyalty/account/{id}/edit`
add `/api/v5/loyalty/account/{id}/activate`, `/api/v5/loyalty/account/{id}/bonus/credit`
add `/api/v5/loyalty/account/{id}/bonus/{status}/details`
2022-12-07 15:06:59 +03:00
Ruslan Efanov
2f6ab28d85 add method of batch products operations 2022-12-07 15:06:59 +03:00
573cbb9679
remove omitempty for active field in json tag for IntegrationModule 2022-12-07 09:38:59 +03:00
Ruslan Efanov
50e25d61bc change type on active field 2022-12-06 22:27:56 +03:00
Ruslan Efanov
a29e2419ff remove omitempty for active field in json tag for IntegrationModule struct 2022-12-06 17:35:32 +03:00
37df181264
/api/v5/loyalty/bonus/operations method 2022-08-09 17:06:34 +03:00
26e66ab630
paidAtFrom and paidAtTo order filter fields 2022-08-09 17:04:49 +03:00
c482546e07
remove unnecessary spaces from tags 2022-08-09 16:50:06 +03:00
Tyschitskaya Maria
2117664f1f fix docs for loyalty bonus operations methods 2022-08-09 15:27:01 +03:00
Tyschitskaya Maria
5e45297368 loyalty bonus operations methods 2022-08-09 13:45:11 +03:00
defernest
6f7fb5ceb9 add 'paidAt<From|To>' filter for orders request 2022-06-15 18:53:33 +03:00
Alex Lushpai
f6b4a15f78
'Ordering' field to 'Site' definition 2022-05-24 12:28:19 +03:00
Alexander Kozlov
e316e3565c added 'Ordering' field to 'Site' definition 2022-05-23 11:38:20 +03:00
Alex Lushpai
995d19007d
В метод Setting добавлены настройки рабочего времени 2022-04-29 13:13:38 +03:00
Vragov Roman
1355012c58 В метод Setting добавлены настройки рабочего времени 2022-04-29 12:27:47 +03:00
pm14kas
06433b7c19
updated Site definition (#66)
Co-authored-by: Alexander Kozlov <kozlov_a@intaro.email>
2022-04-28 11:18:18 +03:00
15 changed files with 6790 additions and 1405 deletions

View file

@ -20,10 +20,10 @@ jobs:
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Go 1.17
uses: actions/setup-go@v2
- name: Set up Go 1.23
uses: actions/setup-go@v3
with:
go-version: '1.17'
go-version: '1.23'
- name: Get dependencies
run: |
go mod tidy
@ -31,21 +31,22 @@ jobs:
- name: Lint code with golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.45.2
version: v1.62.2
only-new-issues: true
skip-pkg-cache: true
args: --build-tags=testutils
tests:
name: Tests
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.13', '1.14', '1.15', '1.16', '1.17']
go-version: ['1.19', '1.20', '1.21', '1.22', '1.23', 'stable']
include:
- go-version: '1.17'
- go-version: '1.23'
coverage: 1
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- name: Check out code into the Go module directory
@ -58,17 +59,21 @@ jobs:
env:
COVERAGE: ${{ matrix.coverage }}
if: env.COVERAGE != 1
run: go test ./...
run: |
go install gotest.tools/gotestsum@latest
gotestsum --format testdox ./... -tags=testutils -v -cpu 2 -timeout 60s -race
- name: Tests with coverage
env:
COVERAGE: ${{ matrix.coverage }}
if: env.COVERAGE == 1
run: |
go test ./... -race -coverprofile=coverage.txt -covermode=atomic "$d"
go install gotest.tools/gotestsum@latest
gotestsum --format testdox ./... -tags=testutils -v -cpu 2 -timeout 60s -race -cover -coverprofile=coverage.txt -covermode=atomic "$d"
- name: Coverage
env:
COVERAGE: ${{ matrix.coverage }}
if: env.COVERAGE == 1
run: |
go install github.com/axw/gocov/gocov@latest
gocov convert ./coverage.txt | gocov report
bash <(curl -s https://codecov.io/bash)
rm coverage.txt

View file

@ -2,29 +2,69 @@ run:
skip-dirs-use-default: true
allow-parallel-runners: true
issues:
exclude-files:
- testutils.go
exclude-rules:
- path: _test\.go
linters:
- lll
- errcheck
- misspell
- ineffassign
- whitespace
- makezero
- errcheck
- funlen
- goconst
- gocognit
- gocyclo
- godot
- unused
- errchkjson
- varnamelen
exclude-use-default: true
exclude-case-sensitive: false
max-issues-per-linter: 0
max-same-issues: 0
fix: true
output:
format: colored-line-number
formats:
- format: colored-line-number
sort-results: true
# Linters below do not support go1.18 yet because of generics.
# See https://github.com/golangci/golangci-lint/issues/2649
# - bodyclose
# - sqlclosecheck
linters:
disable-all: true
enable:
- deadcode
- asciicheck
- asasalint
- varnamelen
- reassign
- nilnil
- nilerr
- nakedret
- goprintffuncname
- typecheck
- errchkjson
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- unused
- unparam
- varcheck
- bodyclose
- dogsled
- dupl
- errorlint
- exhaustive
- exportloopref
- copyloopvar
- funlen
- gocognit
- goconst
@ -33,16 +73,13 @@ linters:
- godot
- goimports
- revive
- gomnd
- gosec
- ifshort
- lll
- makezero
- misspell
- nestif
- prealloc
- predeclared
- sqlclosecheck
- unconvert
- whitespace
@ -53,6 +90,7 @@ linters-settings:
enable:
- assign
- atomic
- atomicalign
- bools
- buildtag
- copylocks
@ -67,7 +105,6 @@ linters-settings:
- unmarshal
- unreachable
- unsafeptr
- unused
settings:
printf:
funcs:
@ -135,11 +172,13 @@ linters-settings:
threshold: 200
errorlint:
errorf: true
asserts: false
comparison: false
exhaustive:
check-generated: false
default-signifies-exhaustive: false
funlen:
lines: 60
lines: 90
statements: 40
gocognit:
min-complexity: 25
@ -149,8 +188,6 @@ linters-settings:
local-prefixes: github.com/retailcrm/api-client-go/v2
lll:
line-length: 160
maligned:
suggest-new: true
misspell:
locale: US
nestif:
@ -158,41 +195,19 @@ linters-settings:
whitespace:
multi-if: false
multi-func: false
issues:
exclude-rules:
- path: _test\.go
linters:
- gomnd
- lll
- bodyclose
- errcheck
- sqlclosecheck
- misspell
- ineffassign
- whitespace
- makezero
- maligned
- ifshort
- errcheck
- funlen
- goconst
- gocognit
- gocyclo
- godot
- gocritic
- gosec
- staticcheck
- unused
exclude-use-default: true
exclude-case-sensitive: false
max-issues-per-linter: 0
max-same-issues: 0
fix: true
varnamelen:
max-distance: 10
ignore-map-index-ok: true
ignore-type-assert-ok: true
ignore-chan-recv-ok: true
ignore-decls:
- t *testing.T
- e error
- i int
severity:
default-severity: error
case-sensitive: false
service:
golangci-lint-version: 1.36.x
golangci-lint-version: 1.62.x

4023
client.go

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

8
enum.go Normal file
View file

@ -0,0 +1,8 @@
package retailcrm
var UserGroupSuperadmins UserGroupType = "superadmins"
var (
NotificationTypeError NotificationType = "api.error"
NotificationTypeInfo NotificationType = "api.info"
)

View file

@ -9,6 +9,8 @@ import (
var missingParameterMatcher = regexp.MustCompile(`^Parameter \'([\w\]\[\_\-]+)\' is missing$`)
var (
// ErrRateLimited will be returned if request was rate limited.
ErrRateLimited = NewAPIError("rate limit exceeded")
// ErrMissingCredentials will be returned if no API key was provided to the API.
ErrMissingCredentials = NewAPIError(`apiKey is missing`)
// ErrInvalidCredentials will be returned if provided API key is invalid.

View file

@ -199,6 +199,7 @@ type OrdersFilter struct {
PaymentStatuses []string `url:"paymentStatuses,omitempty,brackets"`
PaymentTypes []string `url:"paymentTypes,omitempty,brackets"`
DeliveryTypes []string `url:"deliveryTypes,omitempty,brackets"`
DeliveryServices []string `url:"deliveryServices,omitempty,brackets"`
OrderMethods []string `url:"orderMethods,omitempty,brackets"`
ShipmentStores []string `url:"shipmentStores,omitempty,brackets"`
Couriers []string `url:"couriers,omitempty,brackets"`
@ -207,6 +208,8 @@ type OrdersFilter struct {
Sites []string `url:"sites,omitempty,brackets"`
CreatedAtFrom string `url:"createdAtFrom,omitempty"`
CreatedAtTo string `url:"createdAtTo,omitempty"`
PaidAtFrom string `url:"paidAtFrom,omitempty"`
PaidAtTo string `url:"paidAtTo,omitempty"`
FullPaidAtFrom string `url:"fullPaidAtFrom,omitempty"`
FullPaidAtTo string `url:"fullPaidAtTo,omitempty"`
DeliveryDateFrom string `url:"deliveryDateFrom,omitempty"`
@ -349,6 +352,7 @@ type ProductsFilter struct {
ExternalID string `url:"externalId,omitempty"`
Manufacturer string `url:"manufacturer,omitempty"`
URL string `url:"url,omitempty"`
URLLike string `url:"urlLike,omitempty"`
PriceType string `url:"priceType,omitempty"`
OfferExternalID string `url:"offerExternalId,omitempty"`
Sites []string `url:"sites,omitempty,brackets"`
@ -377,22 +381,22 @@ type ShipmentFilter struct {
// CostsFilter type.
type CostsFilter struct {
MinSumm string `url:"minSumm,omitempty"`
MaxSumm string `url:"maxSumm,omitempty"`
MinSumm int `url:"minSumm,omitempty"`
MaxSumm int `url:"maxSumm,omitempty"`
OrderNumber string `url:"orderNumber,omitempty"`
Comment string `url:"orderNumber,omitempty"`
Ids []string `url:"ids,omitempty,brackets"`
IDs []int `url:"ids,omitempty,brackets"`
Sites []string `url:"sites,omitempty,brackets"`
CreatedBy []string `url:"createdBy,omitempty,brackets"`
CreatedBy []int `url:"createdBy,omitempty,brackets"`
CostGroups []string `url:"costGroups,omitempty,brackets"`
CostItems []string `url:"costItems,omitempty,brackets"`
Users []string `url:"users,omitempty,brackets"`
Users []int `url:"users,omitempty,brackets"`
DateFrom string `url:"dateFrom,omitempty"`
DateTo string `url:"dateTo,omitempty"`
CreatedAtFrom string `url:"createdAtFrom,omitempty"`
CreatedAtTo string `url:"createdAtTo,omitempty"`
OrderIds []string `url:"orderIds,omitempty,brackets"`
OrderExternalIds []string `url:"orderIds,omitempty,brackets"`
OrderIDs []int `url:"orderIds,omitempty,brackets"`
OrderExternalIDs []string `url:"orderExternalIds,omitempty,brackets"`
}
// FilesFilter type.
@ -427,3 +431,67 @@ type CustomDictionariesFilter struct {
Name string `url:"name,omitempty"`
Code string `url:"code,omitempty"`
}
// BonusOperationsFilter type.
type BonusOperationsFilter struct {
Loyalties []int `url:"loyalties,omitempty,brackets"`
}
type AccountBonusOperationsFilter struct {
CreatedAtFrom string `url:"createdAtFrom,omitempty"`
CreatedAtTo string `url:"createdAtTo,omitempty"`
}
type LoyaltyBonusAPIFilterType struct {
Date string `url:"date,omitempty"`
}
type LoyaltyAccountAPIFilter struct {
ID string `url:"id,omitempty"`
Status string `url:"status,,omitempty"`
Customer string `url:"customer,omitempty"`
MinOrderSum string `url:"minOrdersSum,omitempty"`
MaxOrderSum string `url:"maxOrdersSum,omitempty"`
MinAmount string `url:"minAmount,omitempty"`
MaxAmount string `url:"maxAmount,omitempty"`
PhoneNumber string `url:"phoneNumber,omitempty"`
CardNumber string `url:"cardNumber,omitempty"`
Ids []int `url:"ids,omitempty,brackets"`
Loyalties []int `url:"loyalties,omitempty,brackets"`
Sites []string `url:"sites,omitempty,brackets"`
Level int `url:"level,omitempty"`
CreatedAtFrom string `url:"createdAtFrom,omitempty"`
CreatedAtTo string `url:"createdAtTo,omitempty"`
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"`
}
type LoyaltyAPIFilter struct {
Active *int `url:"active,omitempty"`
Blocked *int `url:"blocked,omitempty"`
Ids []int `url:"ids,omitempty,brackets"`
Sites []string `url:"sites,omitempty,brackets"`
}
type OffersFilter struct {
Ids []int `url:"ids,omitempty,brackets"`
Active *int `url:"active,omitempty"`
}
type SiteFilter struct {
// SiteBy contains information about what is betrayed site id or site code.
// id|code, default is code.
SiteBy string `url:"siteBy,omitempty"`
}
type GetCartFilter struct {
// SiteBy contains information about what is betrayed site id or site code.
// id|code, default is code.
SiteBy string `url:"siteBy,omitempty"`
// By contains information about what is betrayed: customer id or customer externalId.
// id|externalId, default is externalId.
By string `url:"by,omitempty"`
}

View file

@ -58,6 +58,30 @@ func (l *StringMap) UnmarshalJSON(data []byte) error {
return nil
}
func (l *CustomFieldMap) UnmarshalJSON(data []byte) error {
var i interface{}
var items CustomFieldMap
if err := json.Unmarshal(data, &i); err != nil {
return err
}
switch e := i.(type) {
case map[string]interface{}:
items = make(CustomFieldMap, len(e))
for idx, val := range e {
items[idx] = val
}
case []interface{}:
items = make(CustomFieldMap, len(e))
for idx, val := range e {
items[strconv.Itoa(idx)] = val
}
}
*l = items
return nil
}
func (p *OrderPayments) UnmarshalJSON(data []byte) error {
var i interface{}
var m OrderPayments

View file

@ -44,7 +44,7 @@ func TestAPIErrorsList_UnmarshalJSON(t *testing.T) {
}
func TestCustomFieldsList_UnmarshalJSON(t *testing.T) {
var list StringMap
var list CustomFieldMap
require.NoError(t, json.Unmarshal([]byte(`["first", "second"]`), &list))
assert.Len(t, list, 2)
@ -56,6 +56,13 @@ func TestCustomFieldsList_UnmarshalJSON(t *testing.T) {
assert.Equal(t, list["a"], "first")
assert.Equal(t, list["b"], "second")
require.NoError(t, json.Unmarshal([]byte(`{"a": ["first", "second"], "b": "second"}`), &list))
assert.Len(t, list, 2)
assert.Len(t, list["a"].([]interface{}), 2)
assert.Equal(t, list["a"].([]interface{})[0], "first")
assert.Equal(t, list["a"].([]interface{})[1], "second")
assert.Equal(t, list["b"], "second")
require.NoError(t, json.Unmarshal([]byte(`[]`), &list))
assert.Len(t, list, 0)
}

View file

@ -194,6 +194,22 @@ type DeliveryShipmentsRequest struct {
Page int `url:"page,omitempty"`
}
// ClearCartRequest type.
type ClearCartRequest struct {
ClearedAt string `url:"clearedAt,omitempty"`
Customer CartCustomer `url:"customer,omitempty"`
Order ClearCartOrder `url:"order,omitempty"`
}
// SetCartRequest type.
type SetCartRequest struct {
ExternalID string `url:"externalId,omitempty"`
DroppedAt string `url:"droppedAt,omitempty"`
Link string `url:"link,omitempty"`
Customer CartCustomer `url:"customer,omitempty"`
Items []SetCartItem `url:"items,omitempty"`
}
// CostsRequest type.
type CostsRequest struct {
Filter CostsFilter `url:"filter,omitempty"`
@ -232,6 +248,63 @@ type ConnectRequest struct {
URL string `json:"systemUrl"`
}
// BonusOperationsRequest type.
type BonusOperationsRequest struct {
Filter BonusOperationsFilter `url:"filter,omitempty"`
Limit int `url:"limit,omitempty"`
Cursor string `url:"cursor,omitempty"`
}
// AccountBonusOperationsRequest type.
type AccountBonusOperationsRequest struct {
Filter AccountBonusOperationsFilter `url:"filter,omitempty"`
Limit int `url:"limit,omitempty"`
Page int `url:"page,omitempty"`
}
type LoyaltyBonusCreditRequest struct {
Amount float64 `url:"amount"`
ActivationDate string `url:"activationDate,omitempty"`
ExpiredDate string `url:"expireDate,omitempty"`
Comment string `url:"comment,omitempty"`
}
type LoyaltyBonusStatusDetailsRequest struct {
Limit int `url:"limit,omitempty"`
Page int `url:"page,omitempty"`
Filter LoyaltyBonusAPIFilterType `url:"filter,omitempty"`
}
type LoyaltyAccountsRequest struct {
Limit int `url:"limit,omitempty"`
Page int `url:"page,omitempty"`
Filter LoyaltyAccountAPIFilter `url:"filter,omitempty"`
}
type LoyaltyCalculateRequest struct {
Site string
Order Order
Bonuses float32
}
type LoyaltiesRequest struct {
Limit int `url:"limit,omitempty"`
Page int `url:"page,omitempty"`
Filter LoyaltyAPIFilter `url:"filter,omitempty"`
}
type NotificationsSendRequest struct {
UserGroups []UserGroupType `json:"userGroups,omitempty"`
Type NotificationType `json:"type"`
Message string `json:"message"`
UserIDs []string `json:"userIds,omitempty"`
}
type EditMGChannelTemplateRequest struct {
Templates []MGChannelTemplate `json:"templates"`
Removed []int `json:"removed"`
}
// SystemURL returns system URL from the connection request without trailing slash.
func (r ConnectRequest) SystemURL() string {
if r.URL == "" {
@ -253,3 +326,7 @@ func (r ConnectRequest) Verify(secret string) bool {
}
return hmac.Equal([]byte(r.Token), []byte(hex.EncodeToString(mac.Sum(nil))))
}
type OffersRequest struct {
OffersFilter `url:"filter,omitempty"`
}

View file

@ -5,6 +5,17 @@ type SuccessfulResponse struct {
Success bool `json:"success"`
}
type CreateLoyaltyAccountResponse struct {
SuccessfulResponse
LoyaltyAccount LoyaltyAccount `json:"loyaltyAccount,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
type EditLoyaltyAccountResponse struct {
SuccessfulResponse
LoyaltyAccount LoyaltyAccount `json:"loyaltyAccount,omitempty"`
}
// CreateResponse type.
type CreateResponse struct {
Success bool `json:"success"`
@ -112,6 +123,7 @@ type CorporateCustomerChangeResponse CustomerChangeResponse
type CustomersUploadResponse struct {
Success bool `json:"success"`
UploadedCustomers []IdentifiersPair `json:"uploadedCustomers,omitempty"`
FailedCustomers []ExternalID `json:"failedCustomers,omitempty"`
}
// CorporateCustomersUploadResponse type.
@ -156,6 +168,7 @@ type OrdersStatusesResponse struct {
type OrdersUploadResponse struct {
Success bool `json:"success"`
UploadedOrders []IdentifiersPair `json:"uploadedOrders,omitempty"`
FailedOrders []ExternalID `json:"failedOrders,omitempty"`
}
// OrdersHistoryResponse type.
@ -370,6 +383,23 @@ type ProductsResponse struct {
Products []Product `json:"products,omitempty"`
}
type ProductEditNotFoundResponse struct {
ID string `json:"id"`
ExternalID string `json:"externalId,omitempty"`
}
type ProductsBatchEditResponse struct {
SuccessfulResponse
ProcessedProductsCount int `json:"processedProductsCount,omitempty"`
NotFoundProducts []ProductEditNotFoundResponse `json:"notFoundProducts,omitempty"`
}
type ProductsBatchCreateResponse struct {
SuccessfulResponse
ProcessedProductsCount int `json:"processedProductsCount,omitempty"`
AddedProducts []int `json:"addedProducts,omitempty"`
}
// ProductsPropertiesResponse type.
type ProductsPropertiesResponse struct {
Success bool `json:"success"`
@ -377,6 +407,12 @@ type ProductsPropertiesResponse struct {
Properties []Property `json:"properties,omitempty"`
}
// CartResponse type.
type CartResponse struct {
SuccessfulResponse
Cart Cart `json:"cart"`
}
// DeliveryShipmentsResponse type.
type DeliveryShipmentsResponse struct {
Success bool `json:"success"`
@ -417,9 +453,10 @@ type IntegrationModuleEditResponse struct {
// ResponseInfo type.
type ResponseInfo struct {
MgTransportInfo MgInfo `json:"mgTransport,omitempty"`
MgBotInfo MgInfo `json:"mgBot,omitempty"`
BillingInfo *BillingInfo `json:"billingInfo,omitempty"`
MgTransportInfo MgInfo `json:"mgTransport,omitempty"`
MgBotInfo MgInfo `json:"mgBot,omitempty"`
BillingInfo *BillingInfo `json:"billingInfo,omitempty"`
DeliveryTypeInfo DeliveryTypeInfo `json:"deliveryType,omitempty"`
}
type BillingInfo struct {
@ -550,3 +587,113 @@ func NewConnectResponse(accountURL string) ConnectResponse {
AccountURL: accountURL,
}
}
// BonusOperationsResponse type.
type BonusOperationsResponse struct {
Success bool `json:"success"`
Pagination *CursorPagination `json:"pagination,omitempty"`
BonusOperations []BonusOperation `json:"bonusOperations,omitempty"`
}
// AccountBonusOperationsResponse type.
type AccountBonusOperationsResponse struct {
Success bool `json:"success"`
Pagination *Pagination `json:"pagination,omitempty"`
BonusOperations []BonusOperation `json:"bonusOperations,omitempty"`
}
// ClientIDResponse type.
type ClientIDResponse struct {
ErrorMsg string `json:"errorMsg,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
FailedClientIds []ClientID `json:"failed_client_ids,omitempty"`
Success bool `json:"success"`
}
// SourcesResponse type.
type SourcesResponse struct {
ErrorMsg string `json:"errorMsg,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
FailedSources []Source `json:"failed_sources,omitempty"`
Success bool `json:"success"`
}
// CurrencyResponse type.
type CurrencyResponse struct {
Currencies []Currency `json:"currencies,omitempty"`
Success bool `json:"success"`
}
// CurrencyCreateResponse type.
type CurrencyCreateResponse struct {
Success bool `json:"success"`
ID int `json:"id,omitempty"`
}
type LoyaltyAccountResponse struct {
SuccessfulResponse
LoyaltyAccount `json:"loyaltyAccount"`
}
type LoyaltyAccountActivateResponse struct {
SuccessfulResponse
LoyaltyAccount `json:"loyaltyAccount"`
Verification SmsVerification `json:"verification,omitempty"`
}
type LoyaltyBonusCreditResponse struct {
SuccessfulResponse
LoyaltyBonus LoyaltyBonus `json:"loyaltyBonus"`
}
type LoyaltyBonusDetailsResponse struct {
SuccessfulResponse
Pagination `json:"pagination"`
Statistic LoyaltyBonusStatisticResponse `json:"statistic"`
Bonuses []BonusDetail `json:"bonuses,omitempty"`
}
type LoyaltyBonusStatisticResponse struct {
TotalAmount float64 `json:"totalAmount"`
}
type LoyaltyAccountsResponse struct {
SuccessfulResponse
Pagination *Pagination `json:"pagination"`
LoyaltyAccounts []LoyaltyAccount `json:"loyaltyAccounts,omitempty"`
}
type LoyaltyCalculateResponse struct {
SuccessfulResponse
Order SerializedLoyaltyOrder `json:"order,omitempty"`
Calculations []LoyaltyCalculation `json:"calculations,omitempty"`
Loyalty SerializedLoyalty `json:"loyalty,omitempty"`
}
type LoyaltiesResponse struct {
SuccessfulResponse
Pagination *Pagination `json:"pagination"`
Loyalties []Loyalty `json:"loyalties,omitempty"`
}
type LoyaltyResponse struct {
SuccessfulResponse
Loyalty Loyalty `json:"loyalty"`
}
type ActionProductsGroupResponse struct {
SuccessfulResponse
ID int `json:"id"`
}
type MGChannelTemplatesResponse struct {
Pagination *Pagination `json:"pagination"`
Templates []MGChannelTemplate `json:"templates"`
SuccessfulResponse
}
type StoreOffersResponse struct {
Pagination *Pagination `json:"pagination"`
SuccessfulResponse
Offers []Offer `json:"offers,omitempty"`
}

30
system_time.go Normal file
View file

@ -0,0 +1,30 @@
package retailcrm
import (
"fmt"
"strings"
"time"
)
type SystemTime time.Time
const systemTimeLayout = "2006-01-02 15:04:05"
// UnmarshalJSON parses time.Time from system format.
func (st *SystemTime) UnmarshalJSON(b []byte) (err error) {
s := strings.Trim(string(b), `"`)
nt, err := time.Parse(systemTimeLayout, s)
*st = SystemTime(nt)
return
}
// MarshalJSON will marshal time.Time to system format.
func (st SystemTime) MarshalJSON() ([]byte, error) {
return []byte(st.String()), nil
}
// String returns the time in the custom format.
func (st *SystemTime) String() string {
t := time.Time(*st)
return fmt.Sprintf("%q", t.Format(systemTimeLayout))
}

122
template.go Normal file
View file

@ -0,0 +1,122 @@
package retailcrm
import (
"encoding/json"
"errors"
"fmt"
)
const (
// TemplateItemTypeText is a type for text chunk in template.
TemplateItemTypeText uint8 = iota
// TemplateItemTypeVar is a type for variable in template.
TemplateItemTypeVar
QuickReply ButtonType = "QUICK_REPLY"
PhoneNumber ButtonType = "PHONE_NUMBER"
URL ButtonType = "URL"
)
const (
// TemplateVarCustom is a custom variable type.
TemplateVarCustom = "custom"
// TemplateVarName is a name variable type.
TemplateVarName = "name"
// TemplateVarFirstName is a first name variable type.
TemplateVarFirstName = "first_name"
// TemplateVarLastName is a last name variable type.
TemplateVarLastName = "last_name"
)
// templateVarAssoc for checking variable validity, only for internal use.
var templateVarAssoc = map[string]interface{}{
TemplateVarCustom: nil,
TemplateVarName: nil,
TemplateVarFirstName: nil,
TemplateVarLastName: nil,
}
type Text struct {
Parts []TextTemplateItem `json:"parts"`
Example []string `json:"example,omitempty"`
}
type Media struct {
Example string `json:"example,omitempty"`
}
type Header struct {
Text *Text `json:"text,omitempty"`
Document *Media `json:"document,omitempty"`
Image *Media `json:"image,omitempty"`
Video *Media `json:"video,omitempty"`
}
type TemplateItemList []TextTemplateItem
// TextTemplateItem is a part of template.
type TextTemplateItem struct {
Text string
VarType string
Type uint8
}
// MarshalJSON controls how TextTemplateItem will be marshaled into JSON.
func (t TextTemplateItem) MarshalJSON() ([]byte, error) {
switch t.Type {
case TemplateItemTypeText:
return json.Marshal(t.Text)
case TemplateItemTypeVar:
return json.Marshal(map[string]interface{}{
"var": t.VarType,
})
}
return nil, errors.New("unknown TextTemplateItem type")
}
// UnmarshalJSON will correctly unmarshal TextTemplateItem.
func (t *TextTemplateItem) UnmarshalJSON(b []byte) error {
var obj interface{}
err := json.Unmarshal(b, &obj)
if err != nil {
return err
}
switch bodyPart := obj.(type) {
case string:
t.Type = TemplateItemTypeText
t.Text = bodyPart
case map[string]interface{}:
// {} case
if len(bodyPart) == 0 {
t.Type = TemplateItemTypeText
t.Text = "{}"
return nil
}
if varTypeCurr, ok := bodyPart["var"].(string); ok {
if _, ok := templateVarAssoc[varTypeCurr]; !ok {
return fmt.Errorf("invalid placeholder var '%s'", varTypeCurr)
}
t.Type = TemplateItemTypeVar
t.VarType = varTypeCurr
} else {
return errors.New("invalid TextTemplateItem")
}
default:
return errors.New("invalid TextTemplateItem")
}
return nil
}
type ButtonType string
type Button struct {
Type ButtonType `json:"type"`
URL string `json:"url,omitempty"`
Text string `json:"text,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
Example []string `json:"example,omitempty"`
}

565
testutils.go Normal file
View file

@ -0,0 +1,565 @@
//go:build testutils
// +build testutils
package retailcrm
func getProductsCreate() []ProductCreate {
products := []ProductCreate{
{
CatalogID: 3,
BaseProduct: BaseProduct{
Name: "Product 1",
URL: "https://example.com/p/1",
Article: "p1",
ExternalID: "ext1",
Manufacturer: "man1",
Description: "Description 1",
Popular: true,
Stock: true,
Novelty: true,
Recommended: true,
Active: true,
Markable: true,
},
Groups: []ProductEditGroupInput{{ID: 19}},
},
{
CatalogID: 3,
BaseProduct: BaseProduct{
Name: "Product 2",
URL: "https://example.com/p/2",
Article: "p2",
ExternalID: "ext2",
Manufacturer: "man2",
Description: "Description 2",
Popular: true,
Stock: true,
Novelty: true,
Recommended: true,
Active: true,
Markable: true,
},
Groups: []ProductEditGroupInput{{ID: 19}},
},
}
return products
}
func getProductsCreateResponse() ProductsBatchCreateResponse {
return ProductsBatchCreateResponse{
SuccessfulResponse: SuccessfulResponse{Success: true},
ProcessedProductsCount: 2,
AddedProducts: []int{1, 2},
}
}
func getProductsEdit() []ProductEdit {
products := []ProductEdit{
{
BaseProduct: getProductsCreate()[0].BaseProduct,
ID: 194,
CatalogID: 3,
Site: "second",
},
{
BaseProduct: getProductsCreate()[1].BaseProduct,
ID: 195,
CatalogID: 3,
Site: "second",
},
}
return products
}
func getProductsEditResponse() ProductsBatchEditResponse {
return ProductsBatchEditResponse{
SuccessfulResponse: SuccessfulResponse{Success: true},
ProcessedProductsCount: 2,
NotFoundProducts: nil,
}
}
func getLoyaltyAccountCreate() SerializedCreateLoyaltyAccount {
return SerializedCreateLoyaltyAccount{
SerializedBaseLoyaltyAccount: SerializedBaseLoyaltyAccount{
PhoneNumber: "89151005004",
CustomFields: []interface{}{"dog"},
},
Customer: SerializedEntityCustomer{
ID: 123,
},
}
}
func getLoyaltyAccountCreateResponse() CreateLoyaltyAccountResponse {
return CreateLoyaltyAccountResponse{
SuccessfulResponse: SuccessfulResponse{Success: true},
LoyaltyAccount: LoyaltyAccount{
Active: true,
ID: 13,
PhoneNumber: "89151005004",
LoyaltyLevel: LoyaltyLevel{},
CreatedAt: "2022-11-24 12:39:37",
ActivatedAt: "2022-11-24 12:39:37",
CustomFields: map[string]interface{}{
"animal": "dog",
},
},
}
}
func getLoyaltyAccountEditResponse() EditLoyaltyAccountResponse {
return EditLoyaltyAccountResponse{
SuccessfulResponse: SuccessfulResponse{Success: true},
LoyaltyAccount: LoyaltyAccount{
Active: true,
ID: 13,
PhoneNumber: "89142221020",
LoyaltyLevel: LoyaltyLevel{},
CreatedAt: "2022-11-24 12:39:37",
ActivatedAt: "2022-11-24 12:39:37",
CustomFields: map[string]interface{}{
"animal": "dog",
},
},
}
}
func getLoyaltyAccountResponse() string {
return `{
"success": true,
"loyaltyAccount": {
"active": true,
"id": 13,
"loyalty": {
"id": 2
},
"customer": {
"id": 123,
"customFields": {},
"firstName": "Руслан1",
"lastName": "Ефанов",
"patronymic": ""
},
"phoneNumber": "89142221020",
"amount": 0,
"ordersSum": 0,
"nextLevelSum": 10000,
"level": {
"type": "bonus_percent",
"id": 5,
"name": "Новичок",
"sum": 0,
"privilegeSize": 5,
"privilegeSizePromo": 3
},
"createdAt": "2022-11-24 12:39:37",
"activatedAt": "2022-11-24 12:39:37",
"status": "activated",
"customFields": {
"custom_multiselect": ["test1", "test3"],
"custom_select": "test2",
"custom_integer": 456,
"custom_float": 8.43
}
}
}`
}
func getBonusDetailsResponse() string {
return `{
"success": true,
"pagination": {
"limit": 20,
"totalCount": 41,
"currentPage": 3,
"totalPageCount": 3
},
"statistic": {
"totalAmount": 240
},
"bonuses": [
{
"date": "2022-12-08",
"amount": 240
}
]
}`
}
func getLoyaltyAccountsResponse() string {
return `{
"success": true,
"pagination": {
"limit": 20,
"totalCount": 1,
"currentPage": 1,
"totalPageCount": 1
},
"loyaltyAccounts": [
{
"active": true,
"id": 14,
"loyalty": {
"id": 2
},
"customer": {
"id": 109,
"firstName": "Казимир",
"lastName": "Эльбрусов"
},
"phoneNumber": "89185556363",
"amount": 0,
"ordersSum": 0,
"nextLevelSum": 10000,
"level": {
"type": "bonus_percent",
"id": 5,
"name": "Новичок",
"sum": 0,
"privilegeSize": 5,
"privilegeSizePromo": 3
},
"createdAt": "2022-12-07 15:27:04",
"activatedAt": "2022-12-07 15:27:04",
"status": "activated"
}
]
}`
}
func getLoyaltyCalculateReq() LoyaltyCalculateRequest {
return LoyaltyCalculateRequest{
Site: "main",
Order: Order{
PrivilegeType: "loyalty_level",
Customer: &Customer{
ID: 123,
},
Items: []OrderItem{
{
InitialPrice: 10000,
Quantity: 1,
Offer: Offer{ID: 214},
PriceType: &PriceType{Code: "base"},
},
},
},
Bonuses: 10,
}
}
func getLoyaltyCalculateResponse() string {
return `{
"success": true,
"order": {
"bonusesCreditTotal": 999,
"bonusesChargeTotal": 10,
"privilegeType": "loyalty_level",
"totalSumm": 9990,
"loyaltyAccount": {
"id": 13,
"amount": 240
},
"loyaltyLevel": {
"id": 6,
"name": "Любитель"
},
"customer": {
"id": 123,
"personalDiscount": 0
},
"delivery": {
"cost": 0
},
"site": "main",
"items": [
{
"bonusesChargeTotal": 10,
"bonusesCreditTotal": 999,
"priceType": {
"code": "base"
},
"initialPrice": 10000,
"discounts": [
{
"type": "bonus_charge",
"amount": 10
}
],
"discountTotal": 10,
"prices": [
{
"price": 9990,
"quantity": 1
}
],
"quantity": 1,
"offer": {
"xmlId": "696999ed-bc8d-4d0f-9627-527acf7b1d57"
}
}
]
},
"calculations": [
{
"privilegeType": "loyalty_level",
"discount": 10,
"creditBonuses": 999,
"maxChargeBonuses": 240,
"maximum": true
},
{
"privilegeType": "none",
"discount": 10,
"creditBonuses": 0,
"maxChargeBonuses": 240,
"maximum": false
}
],
"loyalty": {
"name": "Бонусная программа",
"chargeRate": 1
}
}`
}
func getLoyaltiesResponse() string {
return `{
"success": true,
"pagination": {
"limit": 20,
"totalCount": 1,
"currentPage": 1,
"totalPageCount": 1
},
"loyalties": [
{
"levels": [
{
"type": "bonus_percent",
"id": 5,
"name": "Новичок",
"sum": 0,
"privilegeSize": 5,
"privilegeSizePromo": 3
},
{
"type": "bonus_percent",
"id": 6,
"name": "Любитель",
"sum": 10000,
"privilegeSize": 10,
"privilegeSizePromo": 5
},
{
"type": "bonus_percent",
"id": 7,
"name": "Продвинутый покупатель",
"sum": 25000,
"privilegeSize": 15,
"privilegeSizePromo": 7
},
{
"type": "bonus_percent",
"id": 8,
"name": "Мастер шоппинга",
"sum": 50000,
"privilegeSize": 20,
"privilegeSizePromo": 10
}
],
"active": true,
"blocked": false,
"id": 2,
"name": "Бонусная программа",
"confirmSmsCharge": false,
"confirmSmsRegistration": false,
"createdAt": "2022-01-18 15:40:22",
"activatedAt": "2022-12-08 12:05:45"
}
]
}`
}
func getLoyaltyResponse() string {
return `{
"success": true,
"loyalty": {
"levels": [
{
"type": "bonus_percent",
"id": 5,
"name": "Новичок",
"sum": 0,
"privilegeSize": 5,
"privilegeSizePromo": 3
},
{
"type": "bonus_percent",
"id": 6,
"name": "Любитель",
"sum": 10000,
"privilegeSize": 10,
"privilegeSizePromo": 5
},
{
"type": "bonus_percent",
"id": 7,
"name": "Продвинутый покупатель",
"sum": 25000,
"privilegeSize": 15,
"privilegeSizePromo": 7
},
{
"type": "bonus_percent",
"id": 8,
"name": "Мастер шоппинга",
"sum": 50000,
"privilegeSize": 20,
"privilegeSizePromo": 10
}
],
"active": true,
"blocked": false,
"id": 2,
"name": "Бонусная программа",
"confirmSmsCharge": false,
"confirmSmsRegistration": false,
"createdAt": "2022-01-18 15:40:22",
"activatedAt": "2022-12-08 12:05:45"
}
}`
}
func getMGTemplatesResponse() string {
return `{
"success": true,
"pagination": {
"limit": 10,
"totalCount": 100,
"currentPage": 5,
"totalPageCount": 10
},
"templates": [
{
"id": 1,
"externalId": 0,
"channel": {
"allowedSendByPhone": false,
"id": 1,
"externalId": 1,
"type": "fbmessenger",
"active": true,
"name": "fbmessenger"
},
"code": "namespace#NAMEAAA#ru",
"name": "NAMEAAA",
"active": true,
"template": [
"Text_0",
{
"var": "custom"
}
],
"templateExample": ["Text_1"],
"namespace": "namespace_0",
"lang": "en",
"category": "test_0",
"header": {
"text": {
"parts": [
"JABAAA",
{
"var": "custom"
}
],
"example": [
"AAAAAA"
]
},
"image": {
"example": "https://example.com/file/123.png"
},
"document": {
"example": "https://example.com/file/123.pdf"
},
"video": {
"example": "https://example.com/file/123.mp4"
}
},
"footer": "footer_0",
"buttons": [
{
"type": "PHONE_NUMBER",
"text": "your-phone-button-text",
"phoneNumber": "+79895553535"
},
{
"type": "QUICK_REPLY",
"text": "Yes"
},
{
"type": "URL",
"url": "https://example.com/file/{{1}}",
"text": "button",
"example": [
"https://www.website.com/dynamic-url-example"
]
}
],
"verificationStatus": "APPROVED"
}
]
}`
}
func getMGTemplatesForEdit() string {
return `[{"header":{"text":{"parts":["Hello,",{"var":"custom"}],"example":["Henry"]},"document":{"example":"https://example.com/file/123.pdf"},"image":{"example":"https://example.com/file/123.png"},"video":{"example":"https://example.com/file/123.mp4"}},"lang":"en","category":"test_0","code":"namespace#name_0#ru","name":"name_0","namespace":"namespace","footer":"footer_0","verificationStatus":"REJECTED","template":["Text_0",{"var":"custom"}],"buttons":[{"type":"PHONE_NUMBER","text":"your-phone-button-text","phoneNumber":"+79895553535"},{"type":"QUICK_REPLY","text":"Yes"},{"type":"URL","url":"https://example.com/file/{{1}}","text":"button","example":["https://www.website.com/dynamic-url-example"]}],"templateExample":["WIU"],"id":1,"externalId":10,"mgChannelId":110,"active":true}]`
}
func getStoreOfferResponse() string {
return `{
"success": true,
"pagination": {
"limit": 20,
"totalCount": 1,
"currentPage": 1,
"totalPageCount": 1
},
"offers": [
{
"images": [
"https://s3-s1.retailcrm.tech/ru-central1/retailcrm/dev-vega-d32aea7f9a5bc26eba6ad986077cea03/product/65a92fa0bb737-test.jpeg"
],
"id": 76,
"site": "main",
"name": "Название\nПеревод строки",
"article": "Артикул",
"product": {
"type": "product",
"catalogId": 2,
"id": 222
},
"prices": [
{
"priceType": "base",
"price": 10000,
"ordering": 991,
"currency": "RUB"
}
],
"purchasePrice": 10,
"quantity": 5,
"active": true,
"unit": {
"code": "pc",
"name": "Штука",
"sym": "шт."
}
}
]
}`
}

811
types.go

File diff suppressed because it is too large Load diff