@sixtyninecommerce/admin 0.11.0 → 0.13.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 +1509 -28
- package/dist/auth/index.d.ts +7 -1
- package/dist/auth/index.js +8 -1
- package/dist/catalog/documents.d.ts +142 -0
- package/dist/catalog/documents.js +114 -0
- package/dist/catalog/index.d.ts +48 -7
- package/dist/catalog/index.js +6 -1
- package/dist/catalog/metadata.d.ts +244 -0
- package/dist/catalog/metadata.js +220 -0
- package/dist/catalog/properties.d.ts +38 -0
- package/dist/catalog/properties.js +52 -1
- package/dist/catalog/recommendations.d.ts +33 -6
- package/dist/catalog/recommendations.js +94 -2
- package/dist/catalog/reviews.d.ts +17 -4
- package/dist/catalog/reviews.js +7 -0
- package/dist/catalog/variants.d.ts +60 -0
- package/dist/catalog/variants.js +39 -0
- package/dist/catalog/visibility.d.ts +18 -2
- package/dist/client.d.ts +29 -3
- package/dist/client.js +19 -8
- package/dist/content/assets.d.ts +3 -4
- package/dist/content/forms.d.ts +332 -1
- package/dist/content/forms.js +140 -4
- package/dist/content/items.d.ts +31 -8
- package/dist/content/items.js +1 -1
- package/dist/content/menus.d.ts +254 -0
- package/dist/content/menus.js +70 -0
- package/dist/content/strings.d.ts +137 -0
- package/dist/content/strings.js +82 -0
- package/dist/customers/delivery-terms.d.ts +44 -0
- package/dist/customers/delivery-terms.js +32 -0
- package/dist/customers/groups.d.ts +16 -5
- package/dist/customers/groups.js +6 -2
- package/dist/customers/index.d.ts +15 -0
- package/dist/customers/index.js +4 -0
- package/dist/documents/emails.d.ts +154 -0
- package/dist/documents/emails.js +220 -0
- package/dist/documents/index.d.ts +13 -0
- package/dist/documents/index.js +2 -0
- package/dist/fulfillments/create.d.ts +1 -1
- package/dist/fulfillments/create.js +5 -2
- package/dist/fulfillments/index.d.ts +119 -1
- package/dist/fulfillments/index.js +124 -4
- package/dist/fulfillments/warehouse.d.ts +29 -17
- package/dist/fulfillments/warehouse.js +32 -1
- package/dist/index.d.ts +43 -23
- package/dist/index.js +11 -5
- package/dist/integrations/exact-online-types.d.ts +542 -0
- package/dist/integrations/exact-online.d.ts +541 -5
- package/dist/integrations/exact-online.js +714 -1
- package/dist/integrations/index.d.ts +23 -8
- package/dist/integrations/index.js +9 -4
- package/dist/integrations/pipeline-discoveries.d.ts +65 -0
- package/dist/integrations/pipeline-discoveries.js +65 -0
- package/dist/integrations/pipeline-runs.d.ts +119 -0
- package/dist/integrations/pipeline-runs.js +87 -0
- package/dist/integrations/pipelines.d.ts +73 -2
- package/dist/integrations/pipelines.js +36 -1
- package/dist/integrations/price-import.d.ts +223 -0
- package/dist/integrations/price-import.js +124 -0
- package/dist/integrations/types.d.ts +221 -3
- package/dist/integrations/types.js +4 -3
- package/dist/meta/index.d.ts +10 -0
- package/dist/metafields/index.d.ts +8 -1
- package/dist/metafields/index.js +10 -0
- package/dist/orders/cancel-with-money.d.ts +10 -0
- package/dist/orders/cancel-with-money.js +2 -0
- package/dist/orders/commit-edit.d.ts +16 -0
- package/dist/orders/commit-edit.js +14 -1
- package/dist/orders/deposit-policy.d.ts +3 -1
- package/dist/orders/index.d.ts +22 -11
- package/dist/orders/index.js +5 -2
- package/dist/orders/list.d.ts +26 -5
- package/dist/orders/list.js +123 -13
- package/dist/orders/manual-discounts.d.ts +13 -5
- package/dist/orders/money-view.d.ts +149 -0
- package/dist/orders/money-view.js +99 -0
- package/dist/orders/plan-edit.d.ts +34 -2
- package/dist/orders/plan-edit.js +110 -12
- package/dist/orders/surcharges.d.ts +12 -0
- package/dist/orders/surcharges.js +34 -9
- package/dist/payments/account-credit.d.ts +17 -1
- package/dist/payments/account-credit.js +25 -2
- package/dist/payments/credit-noted-fields.d.ts +8 -1
- package/dist/payments/credit-noted-fields.js +34 -2
- package/dist/payments/customer-consent.d.ts +30 -0
- package/dist/payments/customer-consent.js +49 -0
- package/dist/payments/index.d.ts +89 -14
- package/dist/payments/index.js +22 -2
- package/dist/payments/requests.d.ts +6 -3
- package/dist/payments/requests.js +1 -0
- package/dist/pickups/index.d.ts +5 -3
- package/dist/pickups/index.js +5 -2
- package/dist/platform/email-gateways.d.ts +165 -0
- package/dist/platform/email-gateways.js +78 -0
- package/dist/platform/tax.d.ts +11 -3
- package/dist/platform/tax.js +30 -0
- package/dist/pos/exchanges.d.ts +6 -1
- package/dist/pos/index.d.ts +229 -10
- package/dist/pos/index.js +113 -4
- package/dist/pos/offline-sales.d.ts +44 -3
- package/dist/pos/offline-sales.js +57 -3
- package/dist/pos/returns.d.ts +28 -1
- package/dist/pos/returns.js +36 -6
- package/dist/pricing/price-lists.d.ts +3 -1
- package/dist/purchasing/alerts.d.ts +19 -4
- package/dist/purchasing/alerts.js +29 -3
- package/dist/purchasing/backorders.d.ts +61 -0
- package/dist/purchasing/backorders.js +92 -0
- package/dist/purchasing/index.d.ts +21 -3
- package/dist/purchasing/index.js +8 -0
- package/dist/purchasing/links.d.ts +263 -0
- package/dist/purchasing/links.js +192 -0
- package/dist/purchasing/purchase-orders.d.ts +4 -1
- package/dist/purchasing/receipts.d.ts +396 -0
- package/dist/purchasing/receipts.js +287 -0
- package/dist/purchasing/returns.d.ts +273 -0
- package/dist/purchasing/returns.js +229 -0
- package/dist/quotes/index.d.ts +20 -3
- package/dist/returns/index.d.ts +62 -1
- package/dist/returns/index.js +61 -0
- package/dist/seo/config.d.ts +128 -0
- package/dist/seo/config.js +95 -0
- package/dist/seo/redirects.d.ts +125 -0
- package/dist/seo/redirects.js +60 -1
- package/dist/seo/settings.d.ts +147 -0
- package/dist/seo/settings.js +209 -0
- package/dist/shipping/index.d.ts +6 -0
- package/dist/shipping/index.js +2 -0
- package/dist/shipping/methods.d.ts +8 -1
- package/dist/shipping/myparcel.d.ts +415 -1
- package/dist/shipping/myparcel.js +291 -3
- package/dist/shipping/rate-tables.d.ts +149 -4
- package/dist/shipping/rate-tables.js +229 -23
- package/dist/shipping/ship-to-countries.d.ts +75 -0
- package/dist/shipping/ship-to-countries.js +47 -0
- package/dist/stock/by-location.d.ts +86 -0
- package/dist/stock/by-location.js +94 -0
- package/dist/stock/index.d.ts +8 -1
- package/dist/stock/index.js +4 -0
- package/dist/stock/settings.d.ts +106 -0
- package/dist/stock/settings.js +103 -0
- package/dist/stock/transfers.d.ts +171 -10
- package/dist/stock/transfers.js +82 -4
- package/dist/stored-value/index.d.ts +98 -7
- package/dist/stored-value/index.js +71 -4
- package/dist/tax/display-policy.d.ts +105 -0
- package/dist/tax/display-policy.js +84 -0
- package/dist/tax/index.d.ts +3 -0
- package/dist/tax/index.js +2 -0
- package/dist/tenant/email-templates.d.ts +222 -1
- package/dist/tenant/email-templates.js +227 -10
- package/package.json +2 -2
|
@@ -9,30 +9,34 @@ const SUMMARY_FIELDS = /* GraphQL */ `
|
|
|
9
9
|
createdAt
|
|
10
10
|
updatedAt
|
|
11
11
|
`;
|
|
12
|
+
const MEMBER_FIELDS = /* GraphQL */ `
|
|
13
|
+
id
|
|
14
|
+
countryCode
|
|
15
|
+
postalCodeFrom
|
|
16
|
+
postalCodeTo
|
|
17
|
+
`;
|
|
18
|
+
const RATE_FIELDS = /* GraphQL */ `
|
|
19
|
+
id
|
|
20
|
+
minWeightGrams
|
|
21
|
+
maxWeightGrams
|
|
22
|
+
price
|
|
23
|
+
currencyCode
|
|
24
|
+
`;
|
|
25
|
+
/** One selection for a zone wherever it is read or answered, so every door answers the same zone. */
|
|
26
|
+
const ZONE_FIELDS = /* GraphQL */ `
|
|
27
|
+
id
|
|
28
|
+
name
|
|
29
|
+
type
|
|
30
|
+
position
|
|
31
|
+
members { ${MEMBER_FIELDS} }
|
|
32
|
+
rates { ${RATE_FIELDS} }
|
|
33
|
+
createdAt
|
|
34
|
+
updatedAt
|
|
35
|
+
`;
|
|
12
36
|
/** One selection for the single read and both writes, so all three answer the same table. */
|
|
13
37
|
const TABLE_FIELDS = /* GraphQL */ `
|
|
14
38
|
${SUMMARY_FIELDS}
|
|
15
|
-
zones {
|
|
16
|
-
id
|
|
17
|
-
name
|
|
18
|
-
type
|
|
19
|
-
position
|
|
20
|
-
members {
|
|
21
|
-
id
|
|
22
|
-
countryCode
|
|
23
|
-
postalCodeFrom
|
|
24
|
-
postalCodeTo
|
|
25
|
-
}
|
|
26
|
-
rates {
|
|
27
|
-
id
|
|
28
|
-
minWeightGrams
|
|
29
|
-
maxWeightGrams
|
|
30
|
-
price
|
|
31
|
-
currencyCode
|
|
32
|
-
}
|
|
33
|
-
createdAt
|
|
34
|
-
updatedAt
|
|
35
|
-
}
|
|
39
|
+
zones { ${ZONE_FIELDS} }
|
|
36
40
|
`;
|
|
37
41
|
export const RATE_TABLES_QUERY = /* GraphQL */ `
|
|
38
42
|
query StoreRateTables {
|
|
@@ -62,6 +66,39 @@ export const DELETE_RATE_TABLE_MUTATION = /* GraphQL */ `
|
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
`;
|
|
69
|
+
export const CREATE_RATE_TABLE_ZONE_MUTATION = /* GraphQL */ `
|
|
70
|
+
mutation CreateStoreRateTableZone($configId: ID!, $input: CreateShippingZoneInput!) {
|
|
71
|
+
createShippingZone(configId: $configId, input: $input) { ${ZONE_FIELDS} }
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
export const UPDATE_RATE_TABLE_ZONE_MUTATION = /* GraphQL */ `
|
|
75
|
+
mutation UpdateStoreRateTableZone($id: ID!, $input: UpdateShippingZoneInput!) {
|
|
76
|
+
updateShippingZone(id: $id, input: $input) { ${ZONE_FIELDS} }
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
export const DELETE_RATE_TABLE_ZONE_MUTATION = /* GraphQL */ `
|
|
80
|
+
mutation DeleteStoreRateTableZone($id: ID!) {
|
|
81
|
+
deleteShippingZone(id: $id) {
|
|
82
|
+
result
|
|
83
|
+
message
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
export const REORDER_RATE_TABLE_ZONES_MUTATION = /* GraphQL */ `
|
|
88
|
+
mutation ReorderStoreRateTableZones($configId: ID!, $zoneIds: [ID!]!) {
|
|
89
|
+
reorderShippingZones(configId: $configId, zoneIds: $zoneIds) { ${ZONE_FIELDS} }
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
export const SET_RATE_TABLE_ZONE_MEMBERS_MUTATION = /* GraphQL */ `
|
|
93
|
+
mutation SetStoreRateTableZoneMembers($zoneId: ID!, $members: [ShippingZoneMemberInput!]!) {
|
|
94
|
+
setShippingZoneMembers(zoneId: $zoneId, members: $members) { ${MEMBER_FIELDS} }
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
export const SET_RATE_TABLE_ZONE_RATES_MUTATION = /* GraphQL */ `
|
|
98
|
+
mutation SetStoreRateTableZoneRates($zoneId: ID!, $rates: [ShippingRateInput!]!) {
|
|
99
|
+
setShippingRates(zoneId: $zoneId, rates: $rates) { ${RATE_FIELDS} }
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
65
102
|
/** GraphQL's `Int`: a 32-bit signed integer. A wider number cannot be sent at all. */
|
|
66
103
|
const INT_MIN = -2_147_483_648;
|
|
67
104
|
const INT_MAX = 2_147_483_647;
|
|
@@ -152,14 +189,182 @@ function tableInput(input, mode) {
|
|
|
152
189
|
problems.throwIfAny();
|
|
153
190
|
return out;
|
|
154
191
|
}
|
|
155
|
-
|
|
192
|
+
/** `field` is the operation's own argument (`/id`, `/configId`, `/zoneId`). */
|
|
193
|
+
const requireId = (id, field = '/id', what = 'a rate table id') => {
|
|
156
194
|
if (typeof id !== 'string' || id === '') {
|
|
157
195
|
const problems = new Problems();
|
|
158
|
-
problems.add(
|
|
196
|
+
problems.add(field, 'type', `${field.slice(1)} must be ${what}.`);
|
|
159
197
|
problems.throwIfAny();
|
|
160
198
|
}
|
|
161
199
|
return id;
|
|
162
200
|
};
|
|
201
|
+
/**
|
|
202
|
+
* A zone's input as the engine takes it, with only the fields the caller named. A create's name
|
|
203
|
+
* and type are `String!`, so a missing one cannot be sent; every other `null` goes through as
|
|
204
|
+
* sent (a create reads a null position as "after the last zone", an update refuses it).
|
|
205
|
+
*/
|
|
206
|
+
function zoneInput(input, mode) {
|
|
207
|
+
const problems = new Problems();
|
|
208
|
+
if (!isObject(input)) {
|
|
209
|
+
problems.add('/input', 'type', mode === 'create'
|
|
210
|
+
? 'A new zone must be an object.'
|
|
211
|
+
: 'The change to a zone must be an object.');
|
|
212
|
+
problems.throwIfAny();
|
|
213
|
+
}
|
|
214
|
+
const fields = input;
|
|
215
|
+
const out = {};
|
|
216
|
+
const texts = [
|
|
217
|
+
['name', 'Give the zone a name.', 'The zone name must be text.'],
|
|
218
|
+
[
|
|
219
|
+
'type',
|
|
220
|
+
'Give the zone a type: "domestic" (it matches postal-code ranges) or "international" (it matches countries).',
|
|
221
|
+
'The zone type must be text.',
|
|
222
|
+
],
|
|
223
|
+
];
|
|
224
|
+
for (const [key, missing, notText] of texts) {
|
|
225
|
+
const value = fields[key];
|
|
226
|
+
if (value == null && mode === 'create') {
|
|
227
|
+
problems.add(`/input/${key}`, 'required', missing);
|
|
228
|
+
}
|
|
229
|
+
else if (value !== undefined) {
|
|
230
|
+
if (value !== null && typeof value !== 'string') {
|
|
231
|
+
problems.add(`/input/${key}`, 'type', notText);
|
|
232
|
+
}
|
|
233
|
+
out[key] = value;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (fields.position !== undefined) {
|
|
237
|
+
if (fields.position !== null) {
|
|
238
|
+
intInput(problems, '/input/position', 'The position', fields.position);
|
|
239
|
+
}
|
|
240
|
+
out.position = fields.position;
|
|
241
|
+
}
|
|
242
|
+
problems.throwIfAny();
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
const MEMBER_TEXTS = [
|
|
246
|
+
['countryCode', 'country'],
|
|
247
|
+
['postalCodeFrom', 'postal-code start'],
|
|
248
|
+
['postalCodeTo', 'postal-code end'],
|
|
249
|
+
];
|
|
250
|
+
/** Only the three member fields, each text or null; whether the zone matches it is the engine's. */
|
|
251
|
+
function membersInput(members) {
|
|
252
|
+
const problems = new Problems();
|
|
253
|
+
if (!Array.isArray(members)) {
|
|
254
|
+
problems.add('/members', 'type', 'The members must be a list.');
|
|
255
|
+
problems.throwIfAny();
|
|
256
|
+
}
|
|
257
|
+
const out = members.map((member, index) => {
|
|
258
|
+
const at = `/members/${String(index)}`;
|
|
259
|
+
const row = {};
|
|
260
|
+
if (!isObject(member)) {
|
|
261
|
+
problems.add(at, 'type', `Member ${String(index + 1)} must be an object.`);
|
|
262
|
+
return row;
|
|
263
|
+
}
|
|
264
|
+
for (const [key, label] of MEMBER_TEXTS) {
|
|
265
|
+
const value = member[key];
|
|
266
|
+
if (value === undefined)
|
|
267
|
+
continue;
|
|
268
|
+
if (value !== null && typeof value !== 'string') {
|
|
269
|
+
problems.add(`${at}/${key}`, 'type', `Member ${String(index + 1)}'s ${label} must be text.`);
|
|
270
|
+
}
|
|
271
|
+
row[key] = value;
|
|
272
|
+
}
|
|
273
|
+
return row;
|
|
274
|
+
});
|
|
275
|
+
problems.throwIfAny();
|
|
276
|
+
return out;
|
|
277
|
+
}
|
|
278
|
+
const RATE_NUMBERS = [
|
|
279
|
+
['minWeightGrams', 'a start weight'],
|
|
280
|
+
['maxWeightGrams', 'an end weight'],
|
|
281
|
+
['price', 'a price'],
|
|
282
|
+
];
|
|
283
|
+
/**
|
|
284
|
+
* Only the four tier fields. Each is non-null on the wire (`Int!`, `String!`), so a missing one
|
|
285
|
+
* cannot be sent; the tier's values (negative, ending before it starts, overlapping another, an
|
|
286
|
+
* unknown currency) are the engine's to judge.
|
|
287
|
+
*/
|
|
288
|
+
function ratesInput(rates) {
|
|
289
|
+
const problems = new Problems();
|
|
290
|
+
if (!Array.isArray(rates)) {
|
|
291
|
+
problems.add('/rates', 'type', 'The rates must be a list of weight tiers.');
|
|
292
|
+
problems.throwIfAny();
|
|
293
|
+
}
|
|
294
|
+
const out = rates.map((rate, index) => {
|
|
295
|
+
const at = `/rates/${String(index)}`;
|
|
296
|
+
const tier = `Tier ${String(index + 1)}`;
|
|
297
|
+
if (!isObject(rate)) {
|
|
298
|
+
problems.add(at, 'type', `${tier} must be an object.`);
|
|
299
|
+
return {};
|
|
300
|
+
}
|
|
301
|
+
for (const [key, label] of RATE_NUMBERS) {
|
|
302
|
+
const value = rate[key];
|
|
303
|
+
if (value == null)
|
|
304
|
+
problems.add(`${at}/${key}`, 'required', `${tier} needs ${label}.`);
|
|
305
|
+
else
|
|
306
|
+
intInput(problems, `${at}/${key}`, `${tier}'s ${label.replace(/^an? /, '')}`, value);
|
|
307
|
+
}
|
|
308
|
+
if (rate.currencyCode == null) {
|
|
309
|
+
problems.add(`${at}/currencyCode`, 'required', `${tier} needs a currency.`);
|
|
310
|
+
}
|
|
311
|
+
else if (typeof rate.currencyCode !== 'string') {
|
|
312
|
+
problems.add(`${at}/currencyCode`, 'type', `${tier}'s currency must be text (ISO 4217, e.g. EUR).`);
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
minWeightGrams: rate.minWeightGrams,
|
|
316
|
+
maxWeightGrams: rate.maxWeightGrams,
|
|
317
|
+
price: rate.price,
|
|
318
|
+
currencyCode: rate.currencyCode,
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
problems.throwIfAny();
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
/** A list of zone ids; whether it names every zone of the table once is the engine's. */
|
|
325
|
+
function zoneIdsInput(zoneIds) {
|
|
326
|
+
const problems = new Problems();
|
|
327
|
+
if (!Array.isArray(zoneIds)) {
|
|
328
|
+
problems.add('/zoneIds', 'type', 'The zone order must be a list of zone ids.');
|
|
329
|
+
problems.throwIfAny();
|
|
330
|
+
}
|
|
331
|
+
zoneIds.forEach((id, index) => {
|
|
332
|
+
if (typeof id !== 'string' || id === '') {
|
|
333
|
+
problems.add(`/zoneIds/${String(index)}`, 'type', `Place ${String(index + 1)} must be a zone id.`);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
problems.throwIfAny();
|
|
337
|
+
return zoneIds;
|
|
338
|
+
}
|
|
339
|
+
function createZonesModule(transport) {
|
|
340
|
+
return {
|
|
341
|
+
async create(tableId, input, options) {
|
|
342
|
+
const configId = requireId(tableId, '/configId');
|
|
343
|
+
return fieldOf(transport, CREATE_RATE_TABLE_ZONE_MUTATION, 'createShippingZone', { configId, input: zoneInput(input, 'create') }, options);
|
|
344
|
+
},
|
|
345
|
+
async update(zoneId, patch, options) {
|
|
346
|
+
const id = requireId(zoneId, '/id', 'a zone id');
|
|
347
|
+
return fieldOf(transport, UPDATE_RATE_TABLE_ZONE_MUTATION, 'updateShippingZone', { id, input: zoneInput(patch, 'update') }, options);
|
|
348
|
+
},
|
|
349
|
+
async delete(zoneId, options) {
|
|
350
|
+
assertDeleted('SHIPPING_ZONE_NOT_DELETED', [
|
|
351
|
+
await fieldOf(transport, DELETE_RATE_TABLE_ZONE_MUTATION, 'deleteShippingZone', { id: requireId(zoneId, '/id', 'a zone id') }, options),
|
|
352
|
+
]);
|
|
353
|
+
},
|
|
354
|
+
async reorder(tableId, zoneIds, options) {
|
|
355
|
+
const configId = requireId(tableId, '/configId');
|
|
356
|
+
return fieldOf(transport, REORDER_RATE_TABLE_ZONES_MUTATION, 'reorderShippingZones', { configId, zoneIds: zoneIdsInput(zoneIds) }, options);
|
|
357
|
+
},
|
|
358
|
+
async setMembers(zoneId, members, options) {
|
|
359
|
+
const id = requireId(zoneId, '/zoneId', 'a zone id');
|
|
360
|
+
return fieldOf(transport, SET_RATE_TABLE_ZONE_MEMBERS_MUTATION, 'setShippingZoneMembers', { zoneId: id, members: membersInput(members) }, options);
|
|
361
|
+
},
|
|
362
|
+
async setRates(zoneId, rates, options) {
|
|
363
|
+
const id = requireId(zoneId, '/zoneId', 'a zone id');
|
|
364
|
+
return fieldOf(transport, SET_RATE_TABLE_ZONE_RATES_MUTATION, 'setShippingRates', { zoneId: id, rates: ratesInput(rates) }, options);
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
163
368
|
export function createRateTablesModule(transport) {
|
|
164
369
|
return {
|
|
165
370
|
async list(options) {
|
|
@@ -180,5 +385,6 @@ export function createRateTablesModule(transport) {
|
|
|
180
385
|
await fieldOf(transport, DELETE_RATE_TABLE_MUTATION, 'deleteShippingZoneConfig', { id: requireId(id) }, options),
|
|
181
386
|
]);
|
|
182
387
|
},
|
|
388
|
+
zones: createZonesModule(transport),
|
|
183
389
|
};
|
|
184
390
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* How a store's ship-to list reads: `ALL` (no list: every enabled country is offered), `SUBSET`
|
|
5
|
+
* (only the listed countries are offered, and an order to any other cannot be placed), or
|
|
6
|
+
* `MALFORMED` (the stored value cannot be read; checkout offers every enabled country and logs
|
|
7
|
+
* it, and saving a list replaces it).
|
|
8
|
+
*/
|
|
9
|
+
export type ShipToCountriesGate = 'ALL' | 'SUBSET' | 'MALFORMED';
|
|
10
|
+
/** One row of a ship-to picker. */
|
|
11
|
+
export interface ShipToCountryOption {
|
|
12
|
+
/** ISO-3166 alpha-2 code, upper-case. */
|
|
13
|
+
code: string;
|
|
14
|
+
/** The country's name; a selected code that is not an enabled country shows as its code. */
|
|
15
|
+
name: string;
|
|
16
|
+
/** False for a selected code that is not an enabled country: no shopper can choose it. */
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** The countries a store ships to, as checkout applies them. */
|
|
20
|
+
export interface ShipToCountries {
|
|
21
|
+
gate: ShipToCountriesGate;
|
|
22
|
+
/** Why the stored value cannot be read (`MALFORMED` only); else null. */
|
|
23
|
+
reason: string | null;
|
|
24
|
+
/** The selected codes, trimmed, upper-cased and de-duplicated (`SUBSET` only); else empty. */
|
|
25
|
+
selected: string[];
|
|
26
|
+
/** The codes the storefront offers at checkout (Shop `availableCountries`), in its order. */
|
|
27
|
+
served: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Selected codes that are not enabled countries: selected, but no shopper can choose them.
|
|
30
|
+
* Only a platform administrator enables a country (`platform.countries`).
|
|
31
|
+
*/
|
|
32
|
+
unservable: string[];
|
|
33
|
+
/** The enabled countries, then every selected code that is not one, so it can be removed. */
|
|
34
|
+
options: ShipToCountryOption[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The ACTIVE store's ship-to countries (`admin.shipping.shipToCountries`): which countries its
|
|
38
|
+
* storefront offers at checkout, and to which an order may be addressed. Reading needs any
|
|
39
|
+
* administrator permission on the store; `set` needs `UpdateChannel`.
|
|
40
|
+
*
|
|
41
|
+
* The engine owns every rule (how codes are read, which are offered, which are unservable): ask
|
|
42
|
+
* {@link preview} for a list that is not saved.
|
|
43
|
+
*/
|
|
44
|
+
export interface ShipToCountriesModule {
|
|
45
|
+
/**
|
|
46
|
+
* The list as checkout applies it.
|
|
47
|
+
*
|
|
48
|
+
* @effect read
|
|
49
|
+
*/
|
|
50
|
+
get(options?: Call): Promise<ShipToCountries>;
|
|
51
|
+
/**
|
|
52
|
+
* How a list WOULD read once stored, without saving anything. Refused as {@link set} refuses.
|
|
53
|
+
*
|
|
54
|
+
* @effect read
|
|
55
|
+
*/
|
|
56
|
+
preview(countryCodes: readonly string[], options?: Call): Promise<ShipToCountries>;
|
|
57
|
+
/**
|
|
58
|
+
* Replace the list, and answer it as checkout now applies it. Codes are trimmed, upper-cased
|
|
59
|
+
* and de-duplicated. An empty list means every enabled country, never "ships nowhere".
|
|
60
|
+
*
|
|
61
|
+
* Refused `SHIP_TO_COUNTRIES_INVALID` (`ValidationError`, one `details[]` entry per offending
|
|
62
|
+
* entry, `field` = `countryCodes.<index>`) for an entry that is not a two-letter ISO-3166 code,
|
|
63
|
+
* or more than 300 entries; `FORBIDDEN` / `CHANNEL_ACCESS_DENIED` without `UpdateChannel`.
|
|
64
|
+
* A two-letter code that is not an enabled country is stored and reported in `unservable`.
|
|
65
|
+
*
|
|
66
|
+
* Narrowing the list changes which countries shoppers may order to.
|
|
67
|
+
*
|
|
68
|
+
* @effect write
|
|
69
|
+
*/
|
|
70
|
+
set(countryCodes: readonly string[], options?: Call): Promise<ShipToCountries>;
|
|
71
|
+
}
|
|
72
|
+
export declare const SHIP_TO_COUNTRIES_QUERY = "\n query ShipToCountries {\n shipToCountries { \n gate\n reason\n selected\n served\n unservable\n options { code name enabled }\n }\n }\n";
|
|
73
|
+
export declare const PREVIEW_SHIP_TO_COUNTRIES_QUERY = "\n query PreviewShipToCountries($countryCodes: [String!]!) {\n previewShipToCountries(countryCodes: $countryCodes) { \n gate\n reason\n selected\n served\n unservable\n options { code name enabled }\n }\n }\n";
|
|
74
|
+
export declare const SET_SHIP_TO_COUNTRIES_MUTATION = "\n mutation SetShipToCountries($countryCodes: [String!]!) {\n setShipToCountries(countryCodes: $countryCodes) { \n gate\n reason\n selected\n served\n unservable\n options { code name enabled }\n }\n }\n";
|
|
75
|
+
export declare function createShipToCountriesModule(transport: Transport): ShipToCountriesModule;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const VIEW_FIELDS = /* GraphQL */ `
|
|
2
|
+
gate
|
|
3
|
+
reason
|
|
4
|
+
selected
|
|
5
|
+
served
|
|
6
|
+
unservable
|
|
7
|
+
options { code name enabled }
|
|
8
|
+
`;
|
|
9
|
+
export const SHIP_TO_COUNTRIES_QUERY = /* GraphQL */ `
|
|
10
|
+
query ShipToCountries {
|
|
11
|
+
shipToCountries { ${VIEW_FIELDS} }
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
export const PREVIEW_SHIP_TO_COUNTRIES_QUERY = /* GraphQL */ `
|
|
15
|
+
query PreviewShipToCountries($countryCodes: [String!]!) {
|
|
16
|
+
previewShipToCountries(countryCodes: $countryCodes) { ${VIEW_FIELDS} }
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
export const SET_SHIP_TO_COUNTRIES_MUTATION = /* GraphQL */ `
|
|
20
|
+
mutation SetShipToCountries($countryCodes: [String!]!) {
|
|
21
|
+
setShipToCountries(countryCodes: $countryCodes) { ${VIEW_FIELDS} }
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
export function createShipToCountriesModule(transport) {
|
|
25
|
+
return {
|
|
26
|
+
async get(options) {
|
|
27
|
+
const data = await transport.graphql({ api: 'admin', query: SHIP_TO_COUNTRIES_QUERY }, options);
|
|
28
|
+
return data.shipToCountries;
|
|
29
|
+
},
|
|
30
|
+
async preview(countryCodes, options) {
|
|
31
|
+
const data = await transport.graphql({
|
|
32
|
+
api: 'admin',
|
|
33
|
+
query: PREVIEW_SHIP_TO_COUNTRIES_QUERY,
|
|
34
|
+
variables: { countryCodes: [...countryCodes] },
|
|
35
|
+
}, options);
|
|
36
|
+
return data.previewShipToCountries;
|
|
37
|
+
},
|
|
38
|
+
async set(countryCodes, options) {
|
|
39
|
+
const data = await transport.graphql({
|
|
40
|
+
api: 'admin',
|
|
41
|
+
query: SET_SHIP_TO_COUNTRIES_MUTATION,
|
|
42
|
+
variables: { countryCodes: [...countryCodes] },
|
|
43
|
+
}, options);
|
|
44
|
+
return data.setShipToCountries;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { ListOptions } from '../types.js';
|
|
3
|
+
import type { StockMovementType } from './index.js';
|
|
4
|
+
/** One variant's stock level at one location. */
|
|
5
|
+
export interface LocationStockLevel {
|
|
6
|
+
/** The stock level's own id. */
|
|
7
|
+
id: string;
|
|
8
|
+
variantId: string;
|
|
9
|
+
sku: string;
|
|
10
|
+
variantName: string;
|
|
11
|
+
/** The variant's product, for its name and picture. */
|
|
12
|
+
product: {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
featuredAsset: {
|
|
16
|
+
id: string;
|
|
17
|
+
preview: string;
|
|
18
|
+
} | null;
|
|
19
|
+
};
|
|
20
|
+
/** Units the location holds. */
|
|
21
|
+
onHand: number;
|
|
22
|
+
/** Of those, the units reserved for orders. */
|
|
23
|
+
allocated: number;
|
|
24
|
+
/** When the level last changed. */
|
|
25
|
+
updatedAt: string;
|
|
26
|
+
}
|
|
27
|
+
export interface LocationStockLevelFilter extends ListOptions {
|
|
28
|
+
/** Matches the SKU or the variant's name, ignoring case. Blank searches nothing. */
|
|
29
|
+
search?: string;
|
|
30
|
+
/** By units on hand: `DESC` (most first, the default) or `ASC`. */
|
|
31
|
+
sort?: 'ASC' | 'DESC';
|
|
32
|
+
}
|
|
33
|
+
/** One line of a location's stock ledger. */
|
|
34
|
+
export interface LocationStockMovement {
|
|
35
|
+
id: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
type: StockMovementType;
|
|
38
|
+
/**
|
|
39
|
+
* The movement's own signed quantity as the engine stores it. A `SALE` is negative; a
|
|
40
|
+
* `RELEASE` is stored positive although it lowers `allocated`.
|
|
41
|
+
*/
|
|
42
|
+
quantity: number;
|
|
43
|
+
variantId: string;
|
|
44
|
+
sku: string;
|
|
45
|
+
variantName: string;
|
|
46
|
+
/** The order the movement was made for (an allocation, a sale, a release, …), else null. */
|
|
47
|
+
order: {
|
|
48
|
+
id: string;
|
|
49
|
+
code: string;
|
|
50
|
+
} | null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* What one stock location holds and what moved there, a page at a time. The location must be
|
|
54
|
+
* the store's own: another store's, or an id that does not exist, is refused
|
|
55
|
+
* `LOCATION_NOT_IN_CHANNEL` (→ `ValidationError`), never read; a credential restricted with
|
|
56
|
+
* `locationIds` is refused `LOCATION_NOT_ALLOWED` (→ `PermissionDeniedError`) at a location it
|
|
57
|
+
* does not hold. A location several stores share lists only this store's variants.
|
|
58
|
+
*/
|
|
59
|
+
export interface StockByLocationReads {
|
|
60
|
+
/**
|
|
61
|
+
* The variants stocked at `locationId`, by units on hand (most first unless `sort` says
|
|
62
|
+
* `ASC`); `limit` defaults to 25 and the engine answers at most 200 per page. A variant whose
|
|
63
|
+
* level row was never written there is not listed.
|
|
64
|
+
*
|
|
65
|
+
* @effect read
|
|
66
|
+
* @permission ReadStockLocation
|
|
67
|
+
*/
|
|
68
|
+
levelsAt(locationId: string, filter?: LocationStockLevelFilter): Promise<Page<LocationStockLevel>>;
|
|
69
|
+
/**
|
|
70
|
+
* The stock ledger at `locationId`, newest first; `limit` defaults to 25. It lists the
|
|
71
|
+
* movements the engine records (allocations, sales, releases, cancellations, returns and
|
|
72
|
+
* adjustments); a bulk stock import writes the levels directly and is not on it.
|
|
73
|
+
*
|
|
74
|
+
* Known limit: at a location several stores share, a variant that more than one of them sells
|
|
75
|
+
* also carries the other stores' order movements, whose order this store cannot read. The
|
|
76
|
+
* engine then fails the whole page (`GRAPHQL_ERROR`, "Cannot return null for non-nullable
|
|
77
|
+
* field") instead of leaving the row out; the levels read is not affected.
|
|
78
|
+
*
|
|
79
|
+
* @effect read
|
|
80
|
+
* @permission ReadStockLocation
|
|
81
|
+
*/
|
|
82
|
+
movementsAt(locationId: string, filter?: ListOptions): Promise<Page<LocationStockMovement>>;
|
|
83
|
+
}
|
|
84
|
+
export declare const STOCK_LEVELS_AT_LOCATION_QUERY = "\n query StockLevelsAtLocation($locationId: ID!, $options: StockLevelListOptions) {\n stockLevelsByLocation(locationId: $locationId, options: $options) {\n items {\n id\n stockOnHand\n stockAllocated\n updatedAt\n productVariant {\n id\n name\n sku\n product { id name featuredAsset { id preview } }\n }\n }\n totalItems\n }\n }\n";
|
|
85
|
+
export declare const STOCK_MOVEMENTS_AT_LOCATION_QUERY = "\n query StockMovementsAtLocation($locationId: ID!, $options: StockMovementListOptions) {\n stockMovementsByLocation(locationId: $locationId, options: $options) {\n items {\n id\n createdAt\n type\n quantity\n productVariant { id name sku }\n orderLine { order { id code } }\n }\n totalItems\n }\n }\n";
|
|
86
|
+
export declare function createStockByLocationReads(transport: Transport): StockByLocationReads;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
|
|
2
|
+
export const STOCK_LEVELS_AT_LOCATION_QUERY = /* GraphQL */ `
|
|
3
|
+
query StockLevelsAtLocation($locationId: ID!, $options: StockLevelListOptions) {
|
|
4
|
+
stockLevelsByLocation(locationId: $locationId, options: $options) {
|
|
5
|
+
items {
|
|
6
|
+
id
|
|
7
|
+
stockOnHand
|
|
8
|
+
stockAllocated
|
|
9
|
+
updatedAt
|
|
10
|
+
productVariant {
|
|
11
|
+
id
|
|
12
|
+
name
|
|
13
|
+
sku
|
|
14
|
+
product { id name featuredAsset { id preview } }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
totalItems
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
export const STOCK_MOVEMENTS_AT_LOCATION_QUERY = /* GraphQL */ `
|
|
22
|
+
query StockMovementsAtLocation($locationId: ID!, $options: StockMovementListOptions) {
|
|
23
|
+
stockMovementsByLocation(locationId: $locationId, options: $options) {
|
|
24
|
+
items {
|
|
25
|
+
id
|
|
26
|
+
createdAt
|
|
27
|
+
type
|
|
28
|
+
quantity
|
|
29
|
+
productVariant { id name sku }
|
|
30
|
+
orderLine { order { id code } }
|
|
31
|
+
}
|
|
32
|
+
totalItems
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
const toLevel = ({ productVariant, ...level }) => ({
|
|
37
|
+
id: level.id,
|
|
38
|
+
variantId: productVariant.id,
|
|
39
|
+
sku: productVariant.sku,
|
|
40
|
+
variantName: productVariant.name,
|
|
41
|
+
product: {
|
|
42
|
+
id: productVariant.product.id,
|
|
43
|
+
name: productVariant.product.name,
|
|
44
|
+
featuredAsset: productVariant.product.featuredAsset ?? null,
|
|
45
|
+
},
|
|
46
|
+
onHand: level.stockOnHand,
|
|
47
|
+
allocated: level.stockAllocated,
|
|
48
|
+
updatedAt: level.updatedAt,
|
|
49
|
+
});
|
|
50
|
+
const toMovement = ({ productVariant, orderLine, ...movement }) => ({
|
|
51
|
+
...movement,
|
|
52
|
+
variantId: productVariant.id,
|
|
53
|
+
sku: productVariant.sku,
|
|
54
|
+
variantName: productVariant.name,
|
|
55
|
+
order: orderLine?.order ?? null,
|
|
56
|
+
});
|
|
57
|
+
const DEFAULT_PAGE_SIZE = 25;
|
|
58
|
+
const firstSkip = (filter) => filter?.after === undefined ? 0 : readOffsetCursor(filter.after);
|
|
59
|
+
export function createStockByLocationReads(transport) {
|
|
60
|
+
const levelsPage = async (locationId, skip, take, filter) => {
|
|
61
|
+
const search = filter?.search?.trim();
|
|
62
|
+
const data = await transport.graphql({
|
|
63
|
+
api: 'admin',
|
|
64
|
+
query: STOCK_LEVELS_AT_LOCATION_QUERY,
|
|
65
|
+
variables: {
|
|
66
|
+
locationId,
|
|
67
|
+
options: { skip, take, sort: filter?.sort ?? 'DESC', ...(search ? { search } : {}) },
|
|
68
|
+
},
|
|
69
|
+
}, filter);
|
|
70
|
+
return pageFromVendureList({
|
|
71
|
+
items: data.stockLevelsByLocation.items.map(toLevel),
|
|
72
|
+
totalItems: data.stockLevelsByLocation.totalItems,
|
|
73
|
+
}, skip, take, (cursor) => levelsPage(locationId, readOffsetCursor(cursor), take, filter));
|
|
74
|
+
};
|
|
75
|
+
const movementsPage = async (locationId, skip, take, filter) => {
|
|
76
|
+
const data = await transport.graphql({
|
|
77
|
+
api: 'admin',
|
|
78
|
+
query: STOCK_MOVEMENTS_AT_LOCATION_QUERY,
|
|
79
|
+
variables: { locationId, options: { skip, take } },
|
|
80
|
+
}, filter);
|
|
81
|
+
return pageFromVendureList({
|
|
82
|
+
items: data.stockMovementsByLocation.items.map(toMovement),
|
|
83
|
+
totalItems: data.stockMovementsByLocation.totalItems,
|
|
84
|
+
}, skip, take, (cursor) => movementsPage(locationId, readOffsetCursor(cursor), take, filter));
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
levelsAt(locationId, filter) {
|
|
88
|
+
return levelsPage(locationId, firstSkip(filter), filter?.limit ?? DEFAULT_PAGE_SIZE, filter);
|
|
89
|
+
},
|
|
90
|
+
movementsAt(locationId, filter) {
|
|
91
|
+
return movementsPage(locationId, firstSkip(filter), filter?.limit ?? DEFAULT_PAGE_SIZE, filter);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
package/dist/stock/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
2
|
import type { Call, ListOptions } from '../types.js';
|
|
3
|
+
import { type StockByLocationReads } from './by-location.js';
|
|
3
4
|
import { type StockLocationsModule } from './locations.js';
|
|
5
|
+
import { type StockSettingsModule } from './settings.js';
|
|
4
6
|
import { type StockTransfersModule } from './transfers.js';
|
|
5
7
|
/**
|
|
6
8
|
* One variant's stock at one location, in integer units.
|
|
@@ -133,7 +135,7 @@ export interface StockAllocationsModule {
|
|
|
133
135
|
* narrowed to that list instead); `STOCK_LOCATION_REQUIRED` and
|
|
134
136
|
* `STOCK_WOULD_GO_NEGATIVE` (→ `BusinessRuleError`).
|
|
135
137
|
*/
|
|
136
|
-
export interface StockModule {
|
|
138
|
+
export interface StockModule extends StockByLocationReads {
|
|
137
139
|
/** The variant's stock at every location of this store (or one). A location that never held it has no entry. */
|
|
138
140
|
position(variantId: string, options?: StockPositionOptions): Promise<StockPosition[]>;
|
|
139
141
|
/** The variant's ledger at this store's locations, with each movement's reason. */
|
|
@@ -165,6 +167,11 @@ export interface StockModule {
|
|
|
165
167
|
allocations: StockAllocationsModule;
|
|
166
168
|
/** The store's stock locations themselves: list, read, create, change, delete. */
|
|
167
169
|
locations: StockLocationsModule;
|
|
170
|
+
/**
|
|
171
|
+
* The store's stock settings as the engine reads them, saved or previewed: tracking, the
|
|
172
|
+
* threshold, exposure to the storefront, the storefront warehouses and the backorder default.
|
|
173
|
+
*/
|
|
174
|
+
settings: StockSettingsModule;
|
|
168
175
|
}
|
|
169
176
|
export declare const STOCK_POSITION_QUERY = "\n query StockPosition($variantId: ID!, $locationId: ID) {\n stockPosition(variantId: $variantId, locationId: $locationId) { \n locationId\n locationName\n onHand\n allocated\n available\n incoming\n onOrder\n reserved\n trackInventory\n }\n }\n";
|
|
170
177
|
export declare const STOCK_MOVEMENTS_QUERY: string;
|
package/dist/stock/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { errorFromErrorResult, isErrorResult, pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
|
|
2
|
+
import { createStockByLocationReads } from './by-location.js';
|
|
2
3
|
import { createStockLocationsModule } from './locations.js';
|
|
4
|
+
import { createStockSettingsModule } from './settings.js';
|
|
3
5
|
import { createStockTransfersModule } from './transfers.js';
|
|
4
6
|
const POSITION_FIELDS = /* GraphQL */ `
|
|
5
7
|
locationId
|
|
@@ -104,6 +106,8 @@ export function createStockModule(transport) {
|
|
|
104
106
|
},
|
|
105
107
|
transfers: createStockTransfersModule(transport),
|
|
106
108
|
locations: createStockLocationsModule(transport),
|
|
109
|
+
settings: createStockSettingsModule(transport),
|
|
110
|
+
...createStockByLocationReads(transport),
|
|
107
111
|
allocations: {
|
|
108
112
|
async release(orderId, options) {
|
|
109
113
|
const data = await transport.graphql({ api: 'admin', query: RELEASE_ORDER_ALLOCATION_MUTATION, variables: { orderId } }, options);
|