@sixtyninecommerce/admin 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2739 -17
- package/dist/api-tokens/index.d.ts +15 -5
- package/dist/api-tokens/index.js +1 -0
- package/dist/apps/event-subscription.d.ts +59 -0
- package/dist/apps/event-subscription.js +32 -0
- package/dist/apps/hook-secret.d.ts +22 -0
- package/dist/apps/hook-secret.js +36 -0
- package/dist/apps/index.d.ts +54 -3
- package/dist/apps/index.js +17 -0
- package/dist/audit/index.d.ts +7 -0
- package/dist/auth/queries.d.ts +5 -1
- package/dist/auth/queries.js +6 -0
- package/dist/bulk/index.d.ts +323 -0
- package/dist/bulk/index.js +162 -0
- package/dist/catalog/assets.d.ts +147 -0
- package/dist/catalog/assets.js +164 -0
- package/dist/catalog/collections.d.ts +109 -0
- package/dist/catalog/collections.js +91 -0
- package/dist/catalog/facets.d.ts +103 -0
- package/dist/catalog/facets.js +100 -0
- package/dist/catalog/index.d.ts +115 -0
- package/dist/catalog/index.js +21 -0
- package/dist/catalog/options.d.ts +89 -0
- package/dist/catalog/options.js +88 -0
- package/dist/catalog/products-bulk.d.ts +21 -0
- package/dist/catalog/products-bulk.js +58 -0
- package/dist/catalog/products.d.ts +650 -0
- package/dist/catalog/products.js +458 -0
- package/dist/catalog/properties.d.ts +234 -0
- package/dist/catalog/properties.js +158 -0
- package/dist/catalog/types.d.ts +174 -0
- package/dist/catalog/types.js +141 -0
- package/dist/catalog/variants.d.ts +324 -0
- package/dist/catalog/variants.js +288 -0
- package/dist/catalog/visibility.d.ts +57 -0
- package/dist/catalog/visibility.js +59 -0
- package/dist/client.d.ts +192 -3
- package/dist/client.js +87 -12
- package/dist/compliance/index.d.ts +88 -0
- package/dist/compliance/index.js +88 -0
- package/dist/customer-data-redaction.d.ts +14 -0
- package/dist/customer-data-redaction.js +22 -0
- package/dist/customers/index.d.ts +428 -17
- package/dist/customers/index.js +322 -15
- package/dist/documents/index.d.ts +513 -16
- package/dist/documents/index.js +229 -3
- package/dist/events/index.d.ts +259 -0
- package/dist/events/index.js +147 -0
- package/dist/feeds/ai.d.ts +46 -0
- package/dist/feeds/ai.js +43 -0
- package/dist/feeds/categories.d.ts +50 -0
- package/dist/feeds/categories.js +56 -0
- package/dist/feeds/configs.d.ts +54 -0
- package/dist/feeds/configs.js +85 -0
- package/dist/feeds/identifier-fields.d.ts +25 -0
- package/dist/feeds/identifier-fields.js +29 -0
- package/dist/feeds/index.d.ts +54 -0
- package/dist/feeds/index.js +46 -0
- package/dist/feeds/preserve.d.ts +38 -0
- package/dist/feeds/preserve.js +74 -0
- package/dist/feeds/rules.d.ts +42 -0
- package/dist/feeds/rules.js +56 -0
- package/dist/feeds/types.d.ts +654 -0
- package/dist/feeds/types.js +135 -0
- package/dist/fulfillments/index.d.ts +437 -0
- package/dist/fulfillments/index.js +534 -0
- package/dist/heads/index.d.ts +60 -0
- package/dist/heads/index.js +45 -0
- package/dist/hooks/assignments.d.ts +90 -0
- package/dist/hooks/assignments.js +53 -0
- package/dist/hooks/index.d.ts +97 -0
- package/dist/hooks/index.js +76 -0
- package/dist/index.d.ts +95 -9
- package/dist/index.js +21 -1
- package/dist/integrations/index.d.ts +14 -0
- package/dist/integrations/index.js +9 -0
- package/dist/integrations/pipelines.d.ts +113 -0
- package/dist/integrations/pipelines.js +62 -0
- package/dist/integrations/types.d.ts +176 -0
- package/dist/integrations/types.js +8 -0
- package/dist/jobs/bulk-action-run.d.ts +74 -0
- package/dist/jobs/bulk-action-run.js +69 -0
- package/dist/locations/index.d.ts +6 -2
- package/dist/meta/index.d.ts +32 -0
- package/dist/orders/bulk.d.ts +75 -0
- package/dist/orders/bulk.js +150 -0
- package/dist/orders/commit-edit.d.ts +123 -0
- package/dist/orders/commit-edit.js +142 -0
- package/dist/orders/deposit-policy.d.ts +103 -0
- package/dist/orders/deposit-policy.js +202 -0
- package/dist/orders/get.d.ts +722 -0
- package/dist/orders/get.js +530 -0
- package/dist/orders/index.d.ts +186 -21
- package/dist/orders/index.js +57 -0
- package/dist/orders/line-price-preview.d.ts +297 -0
- package/dist/orders/line-price-preview.js +50 -0
- package/dist/orders/manual-discounts.d.ts +141 -0
- package/dist/orders/manual-discounts.js +97 -0
- package/dist/orders/manual-pricing.d.ts +192 -0
- package/dist/orders/manual-pricing.js +72 -0
- package/dist/orders/order-entry.d.ts +566 -0
- package/dist/orders/order-entry.js +129 -0
- package/dist/orders/parcel-charge.d.ts +45 -0
- package/dist/orders/parcel-charge.js +55 -0
- package/dist/orders/plan-edit.d.ts +489 -0
- package/dist/orders/plan-edit.js +93 -0
- package/dist/orders/status.d.ts +235 -0
- package/dist/orders/status.js +96 -0
- package/dist/orders/surcharges.d.ts +75 -2
- package/dist/orders/surcharges.js +110 -4
- package/dist/payments/account-credit.d.ts +171 -0
- package/dist/payments/account-credit.js +107 -0
- package/dist/payments/index.d.ts +241 -9
- package/dist/payments/index.js +92 -0
- package/dist/payments/methods.d.ts +37 -0
- package/dist/payments/methods.js +33 -0
- package/dist/platform/catalog.d.ts +148 -0
- package/dist/platform/catalog.js +74 -0
- package/dist/platform/entitlements.d.ts +111 -0
- package/dist/platform/entitlements.js +86 -0
- package/dist/platform/legal-entities.d.ts +139 -0
- package/dist/platform/legal-entities.js +126 -0
- package/dist/platform/legal-entity-numbering.d.ts +103 -0
- package/dist/platform/legal-entity-numbering.js +101 -0
- package/dist/platform/organization-membership.d.ts +80 -0
- package/dist/platform/organization-membership.js +71 -0
- package/dist/platform/organization-settings.d.ts +33 -0
- package/dist/platform/organization-settings.js +42 -0
- package/dist/platform/organization-sharing.d.ts +172 -0
- package/dist/platform/organization-sharing.js +120 -0
- package/dist/platform/organizations.d.ts +205 -0
- package/dist/platform/organizations.js +180 -0
- package/dist/platform/provision-store.d.ts +88 -8
- package/dist/platform/provision-store.js +12 -0
- package/dist/platform/reports.d.ts +46 -0
- package/dist/platform/reports.js +31 -0
- package/dist/platform/server-metrics.d.ts +58 -0
- package/dist/platform/server-metrics.js +11 -0
- package/dist/pos/index.d.ts +567 -0
- package/dist/pos/index.js +205 -0
- package/dist/pricing/index.d.ts +408 -0
- package/dist/pricing/index.js +353 -0
- package/dist/pricing/vouchers.d.ts +103 -0
- package/dist/pricing/vouchers.js +69 -0
- package/dist/provenance/index.d.ts +93 -0
- package/dist/provenance/index.js +53 -0
- package/dist/reports/index.d.ts +199 -0
- package/dist/reports/index.js +154 -0
- package/dist/returns/index.d.ts +225 -0
- package/dist/returns/index.js +160 -0
- package/dist/search/index.d.ts +99 -0
- package/dist/search/index.js +62 -0
- package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
- package/dist/shipping/parcel-count-suggestion.js +73 -0
- package/dist/shipping/parcel-rule.d.ts +48 -0
- package/dist/shipping/parcel-rule.js +91 -0
- package/dist/stock/index.d.ts +166 -0
- package/dist/stock/index.js +115 -0
- package/dist/stock/transfers.d.ts +520 -0
- package/dist/stock/transfers.js +437 -0
- package/dist/stored-value/index.d.ts +154 -0
- package/dist/stored-value/index.js +130 -0
- package/dist/tenant/capabilities.d.ts +10 -0
- package/dist/tenant/entitlements.d.ts +35 -0
- package/dist/tenant/entitlements.js +15 -0
- package/dist/tenant/field-groups.d.ts +64 -0
- package/dist/tenant/field-groups.js +34 -0
- package/dist/tenant/legal-entity.d.ts +32 -0
- package/dist/tenant/legal-entity.js +41 -0
- package/dist/tenant/notifications.d.ts +148 -0
- package/dist/tenant/notifications.js +79 -0
- package/dist/tenant/retention.d.ts +10 -4
- package/dist/tenant/settings-sources.d.ts +103 -0
- package/dist/tenant/settings-sources.js +92 -0
- package/dist/types.d.ts +86 -11
- package/dist/uploads/index.d.ts +11 -7
- package/dist/users/index.d.ts +104 -0
- package/dist/users/index.js +80 -0
- package/package.json +3 -2
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { ValidationError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { bulkProducts, readProductBulkRun, } from './products-bulk.js';
|
|
3
|
+
import { ASSET_REF_FIELDS, assertDeleted, availabilityCustomFields, CATALOGUE_OWNERSHIP_FIELDS, DELETION_FIELDS, FACET_VALUE_REF_FIELDS, fieldOf, unwrapResult, vendureListPage, } from './types.js';
|
|
4
|
+
import { assertPricesStateBasis, } from './variants.js';
|
|
5
|
+
/** The engine's bound on one `exclude` or `include` call (`STORE_EXCLUSION_BATCH_LIMIT`). */
|
|
6
|
+
export const MAX_STORE_EXCLUSION_PRODUCTS = 500;
|
|
7
|
+
/**
|
|
8
|
+
* The engine's bounds on one `setAvailabilityInStores` or `availabilityInStores` call
|
|
9
|
+
* (`AVAILABILITY_*_LIMIT`).
|
|
10
|
+
*/
|
|
11
|
+
export const MAX_AVAILABILITY_PRODUCTS = 100;
|
|
12
|
+
export const MAX_AVAILABILITY_STORES = 50;
|
|
13
|
+
/** The engine's bound on each list of one {@link ProductsModule.setCollections} call. */
|
|
14
|
+
export const MAX_PRODUCT_COLLECTION_IDS = 200;
|
|
15
|
+
export const PRODUCT_FIELDS = /* GraphQL */ `
|
|
16
|
+
id
|
|
17
|
+
${CATALOGUE_OWNERSHIP_FIELDS}
|
|
18
|
+
createdAt
|
|
19
|
+
updatedAt
|
|
20
|
+
languageCode
|
|
21
|
+
name
|
|
22
|
+
slug
|
|
23
|
+
description
|
|
24
|
+
enabled
|
|
25
|
+
featuredAsset { ${ASSET_REF_FIELDS} }
|
|
26
|
+
assets { ${ASSET_REF_FIELDS} }
|
|
27
|
+
facetValues { ${FACET_VALUE_REF_FIELDS} }
|
|
28
|
+
optionGroups {
|
|
29
|
+
id
|
|
30
|
+
code
|
|
31
|
+
name
|
|
32
|
+
options {
|
|
33
|
+
id
|
|
34
|
+
code
|
|
35
|
+
name
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
translations {
|
|
39
|
+
id
|
|
40
|
+
languageCode
|
|
41
|
+
name
|
|
42
|
+
slug
|
|
43
|
+
description
|
|
44
|
+
}
|
|
45
|
+
excludedFromStore
|
|
46
|
+
variantList(options: { take: 1 }) {
|
|
47
|
+
totalItems
|
|
48
|
+
}
|
|
49
|
+
customFields {
|
|
50
|
+
availabilityState
|
|
51
|
+
availableFrom
|
|
52
|
+
preorderLimit
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
export const PRODUCTS_QUERY = /* GraphQL */ `
|
|
56
|
+
query CatalogProducts($options: ProductListOptions) {
|
|
57
|
+
products(options: $options) {
|
|
58
|
+
items { ${PRODUCT_FIELDS} }
|
|
59
|
+
totalItems
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
export const PRODUCT_QUERY = /* GraphQL */ `
|
|
64
|
+
query CatalogProduct($id: ID!) {
|
|
65
|
+
product(id: $id) { ${PRODUCT_FIELDS} }
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
export const CREATE_PRODUCT_MUTATION = /* GraphQL */ `
|
|
69
|
+
mutation CatalogCreateProduct($input: CreateProductInput!) {
|
|
70
|
+
createProduct(input: $input) { ${PRODUCT_FIELDS} }
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
export const UPDATE_PRODUCT_MUTATION = /* GraphQL */ `
|
|
74
|
+
mutation CatalogUpdateProduct($input: UpdateProductInput!) {
|
|
75
|
+
updateProduct(input: $input) { ${PRODUCT_FIELDS} }
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
export const DELETE_PRODUCT_MUTATION = /* GraphQL */ `
|
|
79
|
+
mutation CatalogDeleteProduct($id: ID!) {
|
|
80
|
+
deleteProduct(id: $id) { ${DELETION_FIELDS} }
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
export const CLONE_PRODUCT_MUTATION = /* GraphQL */ `
|
|
84
|
+
mutation CatalogCloneProduct($id: ID!) {
|
|
85
|
+
cloneProduct(id: $id)
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
88
|
+
export const ADD_OPTION_GROUP_MUTATION = /* GraphQL */ `
|
|
89
|
+
mutation CatalogAddOptionGroup($productId: ID!, $optionGroupId: ID!) {
|
|
90
|
+
addOptionGroupToProduct(productId: $productId, optionGroupId: $optionGroupId) { ${PRODUCT_FIELDS} }
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
export const REMOVE_OPTION_GROUP_MUTATION = /* GraphQL */ `
|
|
94
|
+
mutation CatalogRemoveOptionGroup($productId: ID!, $optionGroupId: ID!, $force: Boolean) {
|
|
95
|
+
removeOptionGroupFromProduct(productId: $productId, optionGroupId: $optionGroupId, force: $force) {
|
|
96
|
+
... on Product { ${PRODUCT_FIELDS} }
|
|
97
|
+
... on ErrorResult {
|
|
98
|
+
errorCode
|
|
99
|
+
message
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
export const EXCLUDE_PRODUCTS_FROM_STORE_MUTATION = /* GraphQL */ `
|
|
105
|
+
mutation CatalogExcludeProductsFromStore($productIds: [ID!]!) {
|
|
106
|
+
excludeProductsFromStore(productIds: $productIds) { ${PRODUCT_FIELDS} }
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
export const INCLUDE_PRODUCTS_IN_STORE_MUTATION = /* GraphQL */ `
|
|
110
|
+
mutation CatalogIncludeProductsInStore($productIds: [ID!]!) {
|
|
111
|
+
includeProductsInStore(productIds: $productIds) { ${PRODUCT_FIELDS} }
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
export const SET_ORGANIZATION_PRODUCT_AVAILABILITY_MUTATION = /* GraphQL */ `
|
|
115
|
+
mutation CatalogSetOrganizationProductAvailability(
|
|
116
|
+
$productIds: [ID!]!
|
|
117
|
+
$channelIds: [ID!]!
|
|
118
|
+
$available: Boolean!
|
|
119
|
+
) {
|
|
120
|
+
setOrganizationProductAvailability(
|
|
121
|
+
productIds: $productIds
|
|
122
|
+
channelIds: $channelIds
|
|
123
|
+
available: $available
|
|
124
|
+
) {
|
|
125
|
+
productId
|
|
126
|
+
channelId
|
|
127
|
+
action
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
`;
|
|
131
|
+
export const ORGANIZATION_PRODUCT_AVAILABILITY_QUERY = /* GraphQL */ `
|
|
132
|
+
query CatalogOrganizationProductAvailability($productIds: [ID!]!, $channelIds: [ID!]!) {
|
|
133
|
+
organizationProductAvailability(productIds: $productIds, channelIds: $channelIds) {
|
|
134
|
+
productId
|
|
135
|
+
channelId
|
|
136
|
+
state
|
|
137
|
+
localCopyId
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
export const MAKE_LOCAL_COPY_MUTATION = /* GraphQL */ `
|
|
142
|
+
mutation CatalogMakeLocalCopy($productId: ID!, $skus: [LocalCopySkuInput!]) {
|
|
143
|
+
makeLocalCopy(productId: $productId, skus: $skus) {
|
|
144
|
+
product { ${PRODUCT_FIELDS} }
|
|
145
|
+
originalProductId
|
|
146
|
+
original
|
|
147
|
+
droppedOrganizationPropertyCount
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
export const SET_PRODUCT_COLLECTIONS_MUTATION = /* GraphQL */ `
|
|
152
|
+
mutation CatalogSetProductCollections(
|
|
153
|
+
$productId: ID!
|
|
154
|
+
$collectionIds: [ID!]!
|
|
155
|
+
$fromCollectionIds: [ID!]
|
|
156
|
+
) {
|
|
157
|
+
setProductCollections(
|
|
158
|
+
productId: $productId
|
|
159
|
+
collectionIds: $collectionIds
|
|
160
|
+
fromCollectionIds: $fromCollectionIds
|
|
161
|
+
) {
|
|
162
|
+
added
|
|
163
|
+
removed
|
|
164
|
+
unchanged
|
|
165
|
+
notRemoved {
|
|
166
|
+
collectionId
|
|
167
|
+
reason
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
export const SAVE_PRODUCT_MUTATION = /* GraphQL */ `
|
|
173
|
+
mutation CatalogSaveProduct($input: SaveProductInput!, $idempotencyKey: String) {
|
|
174
|
+
saveProduct(input: $input, idempotencyKey: $idempotencyKey) {
|
|
175
|
+
product { ${PRODUCT_FIELDS} }
|
|
176
|
+
collections {
|
|
177
|
+
added
|
|
178
|
+
removed
|
|
179
|
+
unchanged
|
|
180
|
+
notRemoved {
|
|
181
|
+
collectionId
|
|
182
|
+
reason
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
warnings {
|
|
186
|
+
code
|
|
187
|
+
message
|
|
188
|
+
propertyIds
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
/**
|
|
194
|
+
* Lift the availability custom fields to first-class fields. A server older
|
|
195
|
+
* than the pre-order engine has no such fields and would refuse the document
|
|
196
|
+
* outright, so the defaults below only cover a NULL value, never an absent one.
|
|
197
|
+
*/
|
|
198
|
+
export function toProduct(wire) {
|
|
199
|
+
const { variantList, customFields, ...rest } = wire;
|
|
200
|
+
return {
|
|
201
|
+
...rest,
|
|
202
|
+
variantCount: variantList.totalItems,
|
|
203
|
+
availabilityState: customFields?.availabilityState ?? 'none',
|
|
204
|
+
availableFrom: customFields?.availableFrom ?? null,
|
|
205
|
+
preorderLimit: customFields?.preorderLimit ?? null,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function toWireInput(input) {
|
|
209
|
+
const { availabilityState, availableFrom, preorderLimit, brand, condition, ...rest } = input;
|
|
210
|
+
const customFields = availabilityCustomFields({
|
|
211
|
+
availabilityState,
|
|
212
|
+
availableFrom,
|
|
213
|
+
preorderLimit,
|
|
214
|
+
});
|
|
215
|
+
if (brand !== undefined)
|
|
216
|
+
customFields.brand = brand;
|
|
217
|
+
if (condition !== undefined)
|
|
218
|
+
customFields.condition = condition;
|
|
219
|
+
return Object.keys(customFields).length > 0 ? { ...rest, customFields } : rest;
|
|
220
|
+
}
|
|
221
|
+
export function productFilter(filter) {
|
|
222
|
+
const out = {};
|
|
223
|
+
if (!filter)
|
|
224
|
+
return out;
|
|
225
|
+
if (filter.ids !== undefined)
|
|
226
|
+
out.id = { in: filter.ids };
|
|
227
|
+
if (filter.name !== undefined)
|
|
228
|
+
out.name = { contains: filter.name };
|
|
229
|
+
if (filter.sku !== undefined)
|
|
230
|
+
out.sku = { contains: filter.sku };
|
|
231
|
+
if (filter.enabled !== undefined)
|
|
232
|
+
out.enabled = { eq: filter.enabled };
|
|
233
|
+
if (filter.facetValueIds !== undefined)
|
|
234
|
+
out.facetValueId = { in: filter.facetValueIds };
|
|
235
|
+
if (filter.availabilityState !== undefined) {
|
|
236
|
+
out.availabilityState = { eq: filter.availabilityState };
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
function refuse(code, message) {
|
|
241
|
+
return new ValidationError({ code, message, status: 400, service: 'vendure' });
|
|
242
|
+
}
|
|
243
|
+
/** Distinct ids the way the engine counts them: it stores a uuid lower-case. */
|
|
244
|
+
function distinctCount(ids) {
|
|
245
|
+
return new Set(ids.map((id) => String(id).toLowerCase())).size;
|
|
246
|
+
}
|
|
247
|
+
function assertExclusionBatch(productIds) {
|
|
248
|
+
const count = distinctCount(productIds);
|
|
249
|
+
if (count <= MAX_STORE_EXCLUSION_PRODUCTS)
|
|
250
|
+
return;
|
|
251
|
+
throw refuse('STORE_EXCLUSION_INPUT_INVALID', `Exclude or include at most ${String(MAX_STORE_EXCLUSION_PRODUCTS)} products per call; this one named ${String(count)}. Split it into smaller calls. Nothing was sent.`);
|
|
252
|
+
}
|
|
253
|
+
function assertAvailabilityInput(input, verb = 'set') {
|
|
254
|
+
const bounds = [
|
|
255
|
+
['products', input?.productIds, MAX_AVAILABILITY_PRODUCTS],
|
|
256
|
+
['stores', input?.channelIds, MAX_AVAILABILITY_STORES],
|
|
257
|
+
];
|
|
258
|
+
for (const [noun, ids, limit] of bounds) {
|
|
259
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
260
|
+
throw refuse('ORGANIZATION_AVAILABILITY_INPUT_INVALID', `Name at least one of the ${noun}. Nothing was sent.`);
|
|
261
|
+
}
|
|
262
|
+
const count = distinctCount(ids);
|
|
263
|
+
if (count > limit) {
|
|
264
|
+
throw refuse('ORGANIZATION_AVAILABILITY_INPUT_INVALID', `At most ${String(limit)} ${noun} can be ${verb} in one call; this one named ${String(count)}. Split it into smaller calls. Nothing was sent.`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (verb === 'set' && typeof input.available !== 'boolean') {
|
|
268
|
+
throw refuse('ORGANIZATION_AVAILABILITY_INPUT_INVALID', '`available` must be true (offer the products) or false (stop offering them). Nothing was sent.');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** The engine's check of each list (`PRODUCT_COLLECTIONS_INPUT_INVALID`), before sending. */
|
|
272
|
+
function assertCollectionLists(lists) {
|
|
273
|
+
for (const [argument, ids] of Object.entries(lists)) {
|
|
274
|
+
if (ids === undefined)
|
|
275
|
+
continue;
|
|
276
|
+
if (!Array.isArray(ids)) {
|
|
277
|
+
throw refuse('PRODUCT_COLLECTIONS_INPUT_INVALID', `${argument} must be a list of collection ids. Nothing was sent.`);
|
|
278
|
+
}
|
|
279
|
+
if (ids.length > MAX_PRODUCT_COLLECTION_IDS) {
|
|
280
|
+
throw refuse('PRODUCT_COLLECTIONS_INPUT_INVALID', `Name at most ${String(MAX_PRODUCT_COLLECTION_IDS)} collections in ${argument}; this call named ${String(ids.length)}. Nothing was sent.`);
|
|
281
|
+
}
|
|
282
|
+
if (distinctCount(ids) !== ids.length) {
|
|
283
|
+
throw refuse('PRODUCT_COLLECTIONS_INPUT_INVALID', `${argument} names a collection more than once. Name each collection once. Nothing was sent.`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
export function createProductsModule(transport) {
|
|
288
|
+
const write = async (document, field, variables, options) => {
|
|
289
|
+
return toProduct(await fieldOf(transport, document, field, variables, options));
|
|
290
|
+
};
|
|
291
|
+
return {
|
|
292
|
+
async list(options) {
|
|
293
|
+
const filter = productFilter(options?.filter);
|
|
294
|
+
return vendureListPage(transport, PRODUCTS_QUERY, 'products', (skip, take) => ({
|
|
295
|
+
options: {
|
|
296
|
+
skip,
|
|
297
|
+
take,
|
|
298
|
+
...(Object.keys(filter).length > 0 ? { filter, filterOperator: 'AND' } : {}),
|
|
299
|
+
...(options?.sort ? { sort: { [options.sort.field]: options.sort.direction } } : {}),
|
|
300
|
+
},
|
|
301
|
+
}), options, toProduct);
|
|
302
|
+
},
|
|
303
|
+
async get(id, options) {
|
|
304
|
+
const product = await fieldOf(transport, PRODUCT_QUERY, 'product', { id }, options);
|
|
305
|
+
return product ? toProduct(product) : null;
|
|
306
|
+
},
|
|
307
|
+
// `async` so every refusal below — including `toWireInput`'s datetime
|
|
308
|
+
// check — arrives as a rejection, never as a synchronous throw a caller's
|
|
309
|
+
// `.catch()` would miss.
|
|
310
|
+
async create(input, options) {
|
|
311
|
+
if (!Array.isArray(input.translations) || input.translations.length === 0) {
|
|
312
|
+
throw new ValidationError({
|
|
313
|
+
code: 'INVALID_PRODUCT_INPUT',
|
|
314
|
+
message: 'A product needs at least one translation (languageCode + name).',
|
|
315
|
+
status: 400,
|
|
316
|
+
service: 'vendure',
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return write(CREATE_PRODUCT_MUTATION, 'createProduct', { input: toWireInput(input) }, options);
|
|
320
|
+
},
|
|
321
|
+
async update(input, options) {
|
|
322
|
+
return write(UPDATE_PRODUCT_MUTATION, 'updateProduct', { input: toWireInput(input) }, options);
|
|
323
|
+
},
|
|
324
|
+
async delete(id, options) {
|
|
325
|
+
assertDeleted('PRODUCT_NOT_DELETED', [
|
|
326
|
+
await fieldOf(transport, DELETE_PRODUCT_MUTATION, 'deleteProduct', { id }, options),
|
|
327
|
+
]);
|
|
328
|
+
},
|
|
329
|
+
async clone(id, options) {
|
|
330
|
+
return fieldOf(transport, CLONE_PRODUCT_MUTATION, 'cloneProduct', { id }, options);
|
|
331
|
+
},
|
|
332
|
+
addOptionGroup(productId, optionGroupId, options) {
|
|
333
|
+
return write(ADD_OPTION_GROUP_MUTATION, 'addOptionGroupToProduct', { productId, optionGroupId }, options);
|
|
334
|
+
},
|
|
335
|
+
async removeOptionGroup(productId, optionGroupId, options) {
|
|
336
|
+
const result = await fieldOf(transport, REMOVE_OPTION_GROUP_MUTATION, 'removeOptionGroupFromProduct', { productId, optionGroupId, force: options?.force ?? false }, options);
|
|
337
|
+
return toProduct(unwrapResult(result));
|
|
338
|
+
},
|
|
339
|
+
async exclude(productIds, options) {
|
|
340
|
+
assertExclusionBatch(productIds);
|
|
341
|
+
if (productIds.length === 0)
|
|
342
|
+
return [];
|
|
343
|
+
const rows = await fieldOf(transport, EXCLUDE_PRODUCTS_FROM_STORE_MUTATION, 'excludeProductsFromStore', { productIds }, options);
|
|
344
|
+
return rows.map(toProduct);
|
|
345
|
+
},
|
|
346
|
+
async include(productIds, options) {
|
|
347
|
+
assertExclusionBatch(productIds);
|
|
348
|
+
if (productIds.length === 0)
|
|
349
|
+
return [];
|
|
350
|
+
const rows = await fieldOf(transport, INCLUDE_PRODUCTS_IN_STORE_MUTATION, 'includeProductsInStore', { productIds }, options);
|
|
351
|
+
return rows.map(toProduct);
|
|
352
|
+
},
|
|
353
|
+
async setAvailabilityInStores(input, options) {
|
|
354
|
+
assertAvailabilityInput(input);
|
|
355
|
+
return fieldOf(transport, SET_ORGANIZATION_PRODUCT_AVAILABILITY_MUTATION, 'setOrganizationProductAvailability', {
|
|
356
|
+
productIds: input.productIds,
|
|
357
|
+
channelIds: input.channelIds,
|
|
358
|
+
available: input.available,
|
|
359
|
+
}, options);
|
|
360
|
+
},
|
|
361
|
+
async availabilityInStores(input, options) {
|
|
362
|
+
assertAvailabilityInput(input, 'read');
|
|
363
|
+
return fieldOf(transport, ORGANIZATION_PRODUCT_AVAILABILITY_QUERY, 'organizationProductAvailability', { productIds: input.productIds, channelIds: input.channelIds }, options);
|
|
364
|
+
},
|
|
365
|
+
async makeLocalCopy(productId, options) {
|
|
366
|
+
const { skus, ...call } = options ?? {};
|
|
367
|
+
const result = await fieldOf(transport, MAKE_LOCAL_COPY_MUTATION, 'makeLocalCopy', {
|
|
368
|
+
productId,
|
|
369
|
+
skus: skus === undefined
|
|
370
|
+
? null
|
|
371
|
+
: skus.map(({ productVariantId, sku }) => ({ productVariantId, sku })),
|
|
372
|
+
}, call);
|
|
373
|
+
return { ...result, product: toProduct(result.product) };
|
|
374
|
+
},
|
|
375
|
+
async setCollections(productId, collectionIds, options) {
|
|
376
|
+
const { from, ...call } = options ?? {};
|
|
377
|
+
assertCollectionLists({ collectionIds, fromCollectionIds: from });
|
|
378
|
+
return fieldOf(transport, SET_PRODUCT_COLLECTIONS_MUTATION, 'setProductCollections', {
|
|
379
|
+
productId,
|
|
380
|
+
collectionIds: [...collectionIds],
|
|
381
|
+
fromCollectionIds: from === undefined ? null : [...from],
|
|
382
|
+
}, call);
|
|
383
|
+
},
|
|
384
|
+
async save(input, options) {
|
|
385
|
+
assertCollectionLists({
|
|
386
|
+
collectionIds: input.collectionIds,
|
|
387
|
+
fromCollectionIds: input.fromCollectionIds,
|
|
388
|
+
});
|
|
389
|
+
await assertPricesStateBasis(transport, input.variants ?? [], options);
|
|
390
|
+
const result = await fieldOf(transport, SAVE_PRODUCT_MUTATION, 'saveProduct', { input: toWireSaveInput(input), idempotencyKey: options?.idempotencyKey ?? null }, options);
|
|
391
|
+
return { ...result, product: toProduct(result.product) };
|
|
392
|
+
},
|
|
393
|
+
bulk(input, options) {
|
|
394
|
+
return bulkProducts(transport, input, options);
|
|
395
|
+
},
|
|
396
|
+
bulkRun(runId, options) {
|
|
397
|
+
return readProductBulkRun(transport, runId, options);
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/** The variant fields a save writes as custom fields on the wire. */
|
|
402
|
+
const SAVE_VARIANT_CUSTOM_FIELDS = [
|
|
403
|
+
'minOrderQuantity',
|
|
404
|
+
'salesUnit',
|
|
405
|
+
'cost',
|
|
406
|
+
'packageWeightGrams',
|
|
407
|
+
'packageLengthCm',
|
|
408
|
+
'packageWidthCm',
|
|
409
|
+
'packageHeightCm',
|
|
410
|
+
'parcelCapacity',
|
|
411
|
+
'parcelPacking',
|
|
412
|
+
'backorderPolicy',
|
|
413
|
+
'backorderLimit',
|
|
414
|
+
'gtin',
|
|
415
|
+
'mpn',
|
|
416
|
+
];
|
|
417
|
+
/** The SDK's flat save as the engine's `SaveProductInput`: custom fields nested, no basis. */
|
|
418
|
+
function toWireSaveInput(input) {
|
|
419
|
+
const wire = { id: input.id, languageCode: input.languageCode };
|
|
420
|
+
if (input.product) {
|
|
421
|
+
const { availabilityState, availableFrom, preorderLimit, brand, condition, ...rest } = input.product;
|
|
422
|
+
const customFields = availabilityCustomFields({
|
|
423
|
+
availabilityState,
|
|
424
|
+
availableFrom,
|
|
425
|
+
preorderLimit,
|
|
426
|
+
});
|
|
427
|
+
if (brand !== undefined)
|
|
428
|
+
customFields.brand = brand;
|
|
429
|
+
if (condition !== undefined)
|
|
430
|
+
customFields.condition = condition;
|
|
431
|
+
wire.product = Object.keys(customFields).length > 0 ? { ...rest, customFields } : rest;
|
|
432
|
+
}
|
|
433
|
+
if (input.variants) {
|
|
434
|
+
wire.variants = input.variants.map((variant) => {
|
|
435
|
+
const { availabilityState, availableFrom, preorderLimit, priceBasis, ...rest } = variant;
|
|
436
|
+
void priceBasis;
|
|
437
|
+
const customFields = availabilityCustomFields({
|
|
438
|
+
availabilityState,
|
|
439
|
+
availableFrom,
|
|
440
|
+
preorderLimit,
|
|
441
|
+
});
|
|
442
|
+
const out = { ...rest };
|
|
443
|
+
for (const key of SAVE_VARIANT_CUSTOM_FIELDS) {
|
|
444
|
+
if (key in out) {
|
|
445
|
+
if (out[key] !== undefined)
|
|
446
|
+
customFields[key] = out[key];
|
|
447
|
+
delete out[key];
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return Object.keys(customFields).length > 0 ? { ...out, customFields } : out;
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
for (const key of ['collectionIds', 'fromCollectionIds', 'propertyValues']) {
|
|
454
|
+
if (input[key] !== undefined)
|
|
455
|
+
wire[key] = input[key];
|
|
456
|
+
}
|
|
457
|
+
return wire;
|
|
458
|
+
}
|