@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,353 @@
|
|
|
1
|
+
import { createPage, errorFromErrorResult, isErrorResult, NotFoundError, ValidationError, } from '@sixtyninecommerce/core';
|
|
2
|
+
import { createPricingVouchersModule } from './vouchers.js';
|
|
3
|
+
/**
|
|
4
|
+
* The targeting conditions (incentives sprint 2), encoded as the engine stores
|
|
5
|
+
* them — every argument a string, lists JSON-encoded. Their argument names and
|
|
6
|
+
* shapes are a stored contract.
|
|
7
|
+
*/
|
|
8
|
+
export const promotionConditions = {
|
|
9
|
+
/**
|
|
10
|
+
* `selling_location`: the order is sold from one of these stock locations of
|
|
11
|
+
* the store — a till there, an order `orders.createOrRecover` recorded for it
|
|
12
|
+
* (`location`), or a register sending it as `x-location` on the Admin API. A
|
|
13
|
+
* web order has no location and never matches.
|
|
14
|
+
*/
|
|
15
|
+
sellingLocation(locationIds) {
|
|
16
|
+
return {
|
|
17
|
+
code: 'selling_location',
|
|
18
|
+
arguments: [{ name: 'locationIds', value: JSON.stringify([...locationIds]) }],
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* `time_window`: on one of `days`, from `from` (included) to `until`
|
|
23
|
+
* (excluded), local time — the selling location's profile zone, else the
|
|
24
|
+
* store's. An `until` not after `from` runs into the next day and belongs to
|
|
25
|
+
* the day it opened (`fri 22:00–02:00`); equal times are 24 hours
|
|
26
|
+
* (`00:00`–`00:00`: all day). Judged on the wall clock, so a window in the
|
|
27
|
+
* hour clocks repeat in autumn is open twice, one in the hour they skip in
|
|
28
|
+
* spring never opens. A placed order keeps a window it was placed with.
|
|
29
|
+
*/
|
|
30
|
+
timeWindow(window) {
|
|
31
|
+
return {
|
|
32
|
+
code: 'time_window',
|
|
33
|
+
arguments: [
|
|
34
|
+
{ name: 'daysOfWeek', value: JSON.stringify([...window.days]) },
|
|
35
|
+
{ name: 'startTime', value: window.from },
|
|
36
|
+
{ name: 'endTime', value: window.until },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
/**
|
|
41
|
+
* `first_order`: the customer has no earlier placed order in this store. An
|
|
42
|
+
* order with no customer, or the till's walk-in, never matches.
|
|
43
|
+
*/
|
|
44
|
+
firstOrder() {
|
|
45
|
+
return { code: 'first_order', arguments: [] };
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* `bundle_fixed_price` (incentives sprint 4): each complete set of
|
|
49
|
+
* `components` costs `price` — minor units of the store's DEFAULT currency, in
|
|
50
|
+
* the store's tax basis (as its catalogue prices are entered), converted into
|
|
51
|
+
* an order's currency at the checkout's rate. The discount is spread over the
|
|
52
|
+
* set's lines by value; a set worth `price` or less stays at list price. Pair
|
|
53
|
+
* it with {@link promotionActions}`.bundleFixedPrice()`.
|
|
54
|
+
*
|
|
55
|
+
* `price` is required and must be a whole amount above 0: anything else throws
|
|
56
|
+
* `ValidationError` (`PROMOTION_CONDITION_INVALID`) before a request is sent,
|
|
57
|
+
* as the engine would refuse it. To give sets away use
|
|
58
|
+
* {@link promotionConditions}`.nthSetPercentage` with `discount: 100`.
|
|
59
|
+
*/
|
|
60
|
+
bundleFixedPrice(set) {
|
|
61
|
+
assertSetPrice('bundle_fixed_price', set.price);
|
|
62
|
+
return {
|
|
63
|
+
code: 'bundle_fixed_price',
|
|
64
|
+
arguments: [
|
|
65
|
+
{ name: 'components', value: componentsValue(set.components) },
|
|
66
|
+
{ name: 'price', value: String(set.price) },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* `nth_set_percentage`: of every `nth` complete sets, the cheapest is
|
|
72
|
+
* `discount`% off ("second pair at 50%" is `nth: 2, discount: 50`; `nth: 1`
|
|
73
|
+
* discounts every set). Pair it with {@link promotionActions}`.nthSetPercentage()`.
|
|
74
|
+
*/
|
|
75
|
+
nthSetPercentage(set) {
|
|
76
|
+
return {
|
|
77
|
+
code: 'nth_set_percentage',
|
|
78
|
+
arguments: [
|
|
79
|
+
{ name: 'components', value: componentsValue(set.components) },
|
|
80
|
+
{ name: 'nth', value: String(set.nth) },
|
|
81
|
+
{ name: 'discount', value: String(set.discount) },
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
/**
|
|
86
|
+
* `mix_and_match`: any `quantity` units matching the selector (all of
|
|
87
|
+
* `facetValueIds`, any of `collectionIds`) for `price`, per complete group;
|
|
88
|
+
* leftover units stay at list price. `price` as in `bundleFixedPrice`
|
|
89
|
+
* (required, above 0, refused before sending otherwise). Pair it with
|
|
90
|
+
* {@link promotionActions}`.mixAndMatch()`.
|
|
91
|
+
*/
|
|
92
|
+
mixAndMatch(group) {
|
|
93
|
+
assertSetPrice('mix_and_match', group.price);
|
|
94
|
+
return {
|
|
95
|
+
code: 'mix_and_match',
|
|
96
|
+
arguments: [
|
|
97
|
+
{ name: 'facetValueIds', value: JSON.stringify([...(group.facetValueIds ?? [])]) },
|
|
98
|
+
{ name: 'collectionIds', value: JSON.stringify([...(group.collectionIds ?? [])]) },
|
|
99
|
+
{ name: 'quantity', value: String(group.quantity) },
|
|
100
|
+
{ name: 'price', value: String(group.price) },
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* A set or group price as the engine accepts it: a whole number of minor units above 0. A 0
|
|
107
|
+
* would make every set free (the engine refuses it on save and discounts nothing for one it
|
|
108
|
+
* finds stored), so it is refused here, before a request is sent, with the engine's own code.
|
|
109
|
+
*/
|
|
110
|
+
function assertSetPrice(condition, price) {
|
|
111
|
+
if (Number.isSafeInteger(price) && price >= 1 && price <= 2_147_483_647)
|
|
112
|
+
return;
|
|
113
|
+
const message = `The "${condition}" condition's price must be a whole amount in minor units above 0; got ${String(price)}. To give sets away, use promotionConditions.nthSetPercentage with discount: 100.`;
|
|
114
|
+
throw new ValidationError({
|
|
115
|
+
code: 'PROMOTION_CONDITION_INVALID',
|
|
116
|
+
message,
|
|
117
|
+
status: 400,
|
|
118
|
+
service: 'vendure',
|
|
119
|
+
details: [{ field: 'price', code: 'PROMOTION_CONDITION_INVALID', message }],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function componentsValue(components) {
|
|
123
|
+
return JSON.stringify(components.map((component) => ({
|
|
124
|
+
facetValueIds: [...(component.facetValueIds ?? [])],
|
|
125
|
+
collectionIds: [...(component.collectionIds ?? [])],
|
|
126
|
+
quantity: component.quantity,
|
|
127
|
+
})));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The set actions (incentives sprint 4). Each takes no arguments and applies
|
|
131
|
+
* what its same-code condition computed, so it goes on the promotion together
|
|
132
|
+
* with that condition: the engine refuses one without the other.
|
|
133
|
+
*/
|
|
134
|
+
export const promotionActions = {
|
|
135
|
+
bundleFixedPrice() {
|
|
136
|
+
return { code: 'bundle_fixed_price', arguments: [] };
|
|
137
|
+
},
|
|
138
|
+
nthSetPercentage() {
|
|
139
|
+
return { code: 'nth_set_percentage', arguments: [] };
|
|
140
|
+
},
|
|
141
|
+
mixAndMatch() {
|
|
142
|
+
return { code: 'mix_and_match', arguments: [] };
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
const DISCOUNT_FIELDS = 'promotionId adjustmentSource type description amount amountWithTax';
|
|
146
|
+
/** An order's pricing readout; the manual pricing doors (`orders.setLinePrices`, …) answer it too. */
|
|
147
|
+
export const ORDER_DISCOUNTS_FIELDS = /* GraphQL */ `
|
|
148
|
+
id
|
|
149
|
+
code
|
|
150
|
+
state
|
|
151
|
+
currencyCode
|
|
152
|
+
couponCodes
|
|
153
|
+
subTotal
|
|
154
|
+
subTotalWithTax
|
|
155
|
+
shipping
|
|
156
|
+
shippingWithTax
|
|
157
|
+
total
|
|
158
|
+
totalWithTax
|
|
159
|
+
discounts { ${DISCOUNT_FIELDS} }
|
|
160
|
+
promotions { id name }
|
|
161
|
+
lines {
|
|
162
|
+
id
|
|
163
|
+
quantity
|
|
164
|
+
productVariant { id sku name }
|
|
165
|
+
unitPrice
|
|
166
|
+
unitPriceWithTax
|
|
167
|
+
linePrice
|
|
168
|
+
linePriceWithTax
|
|
169
|
+
discountedLinePrice
|
|
170
|
+
discountedLinePriceWithTax
|
|
171
|
+
proratedLinePrice
|
|
172
|
+
proratedLinePriceWithTax
|
|
173
|
+
discounts { ${DISCOUNT_FIELDS} }
|
|
174
|
+
}
|
|
175
|
+
surcharges { id description sku price priceWithTax }
|
|
176
|
+
packages { promotionId label price priceWithTax lineIds quantity }
|
|
177
|
+
`;
|
|
178
|
+
const OPERATION_FIELDS = 'code args { name value } summary';
|
|
179
|
+
export const ACTIVE_PROMOTIONS_QUERY = /* GraphQL */ `
|
|
180
|
+
query ActivePromotions($locationId: ID, $customerId: ID) {
|
|
181
|
+
activePromotions(locationId: $locationId, customerId: $customerId) {
|
|
182
|
+
id
|
|
183
|
+
name
|
|
184
|
+
description
|
|
185
|
+
startsAt
|
|
186
|
+
endsAt
|
|
187
|
+
requiresCouponCode
|
|
188
|
+
usageLimit
|
|
189
|
+
perCustomerUsageLimit
|
|
190
|
+
conditions { ${OPERATION_FIELDS} }
|
|
191
|
+
actions { ${OPERATION_FIELDS} }
|
|
192
|
+
summary
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
`;
|
|
196
|
+
const PROMOTION_FIELDS = /* GraphQL */ `
|
|
197
|
+
id
|
|
198
|
+
name
|
|
199
|
+
description
|
|
200
|
+
enabled
|
|
201
|
+
couponCode
|
|
202
|
+
startsAt
|
|
203
|
+
endsAt
|
|
204
|
+
usageLimit
|
|
205
|
+
perCustomerUsageLimit
|
|
206
|
+
conditions { code args { name value } }
|
|
207
|
+
actions { code args { name value } }
|
|
208
|
+
customFields { combineWithB2b combineWithOtherPromotions priority }
|
|
209
|
+
`;
|
|
210
|
+
export const CREATE_PROMOTION_MUTATION = /* GraphQL */ `
|
|
211
|
+
mutation CreatePromotion($input: CreatePromotionInput!) {
|
|
212
|
+
createPromotion(input: $input) {
|
|
213
|
+
... on Promotion { ${PROMOTION_FIELDS} }
|
|
214
|
+
... on ErrorResult { errorCode message }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
`;
|
|
218
|
+
export const UPDATE_PROMOTION_MUTATION = /* GraphQL */ `
|
|
219
|
+
mutation UpdatePromotion($input: UpdatePromotionInput!) {
|
|
220
|
+
updatePromotion(input: $input) {
|
|
221
|
+
... on Promotion { ${PROMOTION_FIELDS} }
|
|
222
|
+
... on ErrorResult { errorCode message }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
`;
|
|
226
|
+
/** The condition the dashboard and the admin app put on every promotion they save. */
|
|
227
|
+
export const STACKING_GUARD_CONDITION = 'promotion_stacking_guard';
|
|
228
|
+
function withStackingGuard(conditions) {
|
|
229
|
+
return conditions.some((condition) => condition.code === STACKING_GUARD_CONDITION)
|
|
230
|
+
? conditions
|
|
231
|
+
: [{ code: STACKING_GUARD_CONDITION, arguments: [] }, ...conditions];
|
|
232
|
+
}
|
|
233
|
+
/** The SDK input as the engine's `Create`/`UpdatePromotionInput`. */
|
|
234
|
+
function promotionInputOf(input) {
|
|
235
|
+
const { combineWithB2b, combineWithOtherPromotions, priority, conditions, ...rest } = input;
|
|
236
|
+
const customFields = {
|
|
237
|
+
...(combineWithB2b === undefined ? {} : { combineWithB2b }),
|
|
238
|
+
...(combineWithOtherPromotions === undefined ? {} : { combineWithOtherPromotions }),
|
|
239
|
+
...(priority === undefined ? {} : { priority }),
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
...rest,
|
|
243
|
+
...(conditions === undefined ? {} : { conditions: withStackingGuard(conditions) }),
|
|
244
|
+
...(Object.keys(customFields).length === 0 ? {} : { customFields }),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
export const APPLY_COUPON_CODE_TO_ORDER_MUTATION = /* GraphQL */ `
|
|
248
|
+
mutation ApplyCouponCodeToOrder($orderId: ID!, $couponCode: String!, $idempotencyKey: String) {
|
|
249
|
+
applyCouponCodeToOrder(
|
|
250
|
+
orderId: $orderId
|
|
251
|
+
couponCode: $couponCode
|
|
252
|
+
idempotencyKey: $idempotencyKey
|
|
253
|
+
) {
|
|
254
|
+
${ORDER_DISCOUNTS_FIELDS}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
`;
|
|
258
|
+
export const REMOVE_COUPON_CODE_FROM_ORDER_MUTATION = /* GraphQL */ `
|
|
259
|
+
mutation RemoveCouponCodeFromOrder($orderId: ID!, $couponCode: String!, $idempotencyKey: String) {
|
|
260
|
+
removeCouponCodeFromOrder(
|
|
261
|
+
orderId: $orderId
|
|
262
|
+
couponCode: $couponCode
|
|
263
|
+
idempotencyKey: $idempotencyKey
|
|
264
|
+
) {
|
|
265
|
+
${ORDER_DISCOUNTS_FIELDS}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
export const ORDER_DISCOUNTS_QUERY = /* GraphQL */ `
|
|
270
|
+
query OrderDiscounts($id: ID!) {
|
|
271
|
+
order(id: $id) {
|
|
272
|
+
${ORDER_DISCOUNTS_FIELDS}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
276
|
+
/** `undefined` for an absent or blank filter id: an explicit null is a value the engine reads. */
|
|
277
|
+
function idVariable(value) {
|
|
278
|
+
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
279
|
+
}
|
|
280
|
+
export function createPricingModule(transport) {
|
|
281
|
+
return {
|
|
282
|
+
promotions: {
|
|
283
|
+
async active(filter, options) {
|
|
284
|
+
const data = await transport.graphql({
|
|
285
|
+
api: 'admin',
|
|
286
|
+
query: ACTIVE_PROMOTIONS_QUERY,
|
|
287
|
+
variables: {
|
|
288
|
+
locationId: idVariable(filter?.locationId),
|
|
289
|
+
customerId: idVariable(filter?.customerId),
|
|
290
|
+
},
|
|
291
|
+
}, options);
|
|
292
|
+
const items = data.activePromotions;
|
|
293
|
+
return createPage(items, { nextCursor: null, hasNext: false, total: items.length }, () => {
|
|
294
|
+
throw new Error('activePromotions returns a complete list; there is no next page');
|
|
295
|
+
});
|
|
296
|
+
},
|
|
297
|
+
async create(input, options) {
|
|
298
|
+
const data = await transport.graphql({
|
|
299
|
+
api: 'admin',
|
|
300
|
+
query: CREATE_PROMOTION_MUTATION,
|
|
301
|
+
variables: { input: promotionInputOf(input) },
|
|
302
|
+
}, options);
|
|
303
|
+
// A union at HTTP 200 (`Promotion | MissingConditionsError`). With the guard
|
|
304
|
+
// always sent, core does not answer the error member for this input; should
|
|
305
|
+
// it ever, it is thrown, never handed back as a promotion.
|
|
306
|
+
if (isErrorResult(data.createPromotion))
|
|
307
|
+
throw errorFromErrorResult(data.createPromotion);
|
|
308
|
+
return data.createPromotion;
|
|
309
|
+
},
|
|
310
|
+
async update(input, options) {
|
|
311
|
+
const data = await transport.graphql({
|
|
312
|
+
api: 'admin',
|
|
313
|
+
query: UPDATE_PROMOTION_MUTATION,
|
|
314
|
+
variables: { input: promotionInputOf(input) },
|
|
315
|
+
}, options);
|
|
316
|
+
if (isErrorResult(data.updatePromotion))
|
|
317
|
+
throw errorFromErrorResult(data.updatePromotion);
|
|
318
|
+
return data.updatePromotion;
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
vouchers: createPricingVouchersModule(transport),
|
|
322
|
+
coupons: {
|
|
323
|
+
async apply(orderId, couponCode, options) {
|
|
324
|
+
const data = await transport.graphql({
|
|
325
|
+
api: 'admin',
|
|
326
|
+
query: APPLY_COUPON_CODE_TO_ORDER_MUTATION,
|
|
327
|
+
variables: { orderId, couponCode, idempotencyKey: options?.idempotencyKey },
|
|
328
|
+
}, options);
|
|
329
|
+
return data.applyCouponCodeToOrder;
|
|
330
|
+
},
|
|
331
|
+
async remove(orderId, couponCode, options) {
|
|
332
|
+
const data = await transport.graphql({
|
|
333
|
+
api: 'admin',
|
|
334
|
+
query: REMOVE_COUPON_CODE_FROM_ORDER_MUTATION,
|
|
335
|
+
variables: { orderId, couponCode, idempotencyKey: options?.idempotencyKey },
|
|
336
|
+
}, options);
|
|
337
|
+
return data.removeCouponCodeFromOrder;
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
async discounts(orderId, options) {
|
|
341
|
+
const data = await transport.graphql({ api: 'admin', query: ORDER_DISCOUNTS_QUERY, variables: { id: orderId } }, options);
|
|
342
|
+
if (!data.order) {
|
|
343
|
+
throw new NotFoundError({
|
|
344
|
+
code: 'ORDER_NOT_FOUND',
|
|
345
|
+
message: `This store has no order ${orderId}.`,
|
|
346
|
+
status: 404,
|
|
347
|
+
service: 'vendure',
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return data.order;
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Personal vouchers (incentives sprint 9): a voucher TEMPLATE under a promotion, codes issued one
|
|
5
|
+
* by one to a customer of the store, single use, with an optional expiry and the issuing journey
|
|
6
|
+
* recorded as an opaque `source`. Applied like any coupon (`pricing.coupons.apply`, the till's
|
|
7
|
+
* `pos.sale.applyCouponCode`), which refuse `VOUCHER_NOT_FOR_CUSTOMER`, `VOUCHER_EXPIRED` and
|
|
8
|
+
* `VOUCHER_ALREADY_REDEEMED`. Issuing needs `CreatePromotion`.
|
|
9
|
+
*/
|
|
10
|
+
/** A customer of this store: exactly one of its id or a master system's key (PR #1176). */
|
|
11
|
+
export type VoucherCustomer = {
|
|
12
|
+
id: string;
|
|
13
|
+
} | {
|
|
14
|
+
externalRef: {
|
|
15
|
+
system: string;
|
|
16
|
+
ref: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export interface IssueVoucherInput {
|
|
20
|
+
templateId: string;
|
|
21
|
+
customer: VoucherCustomer;
|
|
22
|
+
/** Usable through the end of the store's local day this many days after today (1 to 3650). */
|
|
23
|
+
validityDays?: number;
|
|
24
|
+
/** Or an explicit instant, ISO-8601. Neither: the template's default, else the promotion's window. */
|
|
25
|
+
expiresAt?: string;
|
|
26
|
+
/** The issuing journey; with a `ref`, an issue is exactly-once per template, customer and source. */
|
|
27
|
+
source: {
|
|
28
|
+
system: string;
|
|
29
|
+
ref?: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface IssueVouchersInput {
|
|
33
|
+
templateId: string;
|
|
34
|
+
sourceSystem: string;
|
|
35
|
+
/** At most 1,000. */
|
|
36
|
+
items: Array<{
|
|
37
|
+
customer: VoucherCustomer;
|
|
38
|
+
validityDays?: number;
|
|
39
|
+
expiresAt?: string;
|
|
40
|
+
sourceRef?: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
export interface IssuedVoucher {
|
|
44
|
+
id: string;
|
|
45
|
+
code: string;
|
|
46
|
+
customerId: string;
|
|
47
|
+
/** The earlier of its own expiry and its promotion's end; null when neither ends. */
|
|
48
|
+
expiresAt: string | null;
|
|
49
|
+
/** False when this template, customer and source had issued it already: the same code. */
|
|
50
|
+
created: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface IssueVouchersItem {
|
|
53
|
+
index: number;
|
|
54
|
+
voucher: IssuedVoucher | null;
|
|
55
|
+
/** A `VOUCHER_*` code or `ENTITY_NOT_FOUND` for a refused item; the others were issued. */
|
|
56
|
+
errorCode: string | null;
|
|
57
|
+
message: string | null;
|
|
58
|
+
}
|
|
59
|
+
export type VoucherStatus = 'OPEN' | 'REDEEMED' | 'EXPIRED' | 'REVOKED';
|
|
60
|
+
export interface Voucher {
|
|
61
|
+
id: string;
|
|
62
|
+
code: string;
|
|
63
|
+
templateId: string;
|
|
64
|
+
promotionId: string;
|
|
65
|
+
customerId: string | null;
|
|
66
|
+
status: VoucherStatus;
|
|
67
|
+
issuedAt: string;
|
|
68
|
+
expiresAt: string | null;
|
|
69
|
+
sourceSystem: string | null;
|
|
70
|
+
sourceRef: string | null;
|
|
71
|
+
/** The order that redeemed it. */
|
|
72
|
+
orderId: string | null;
|
|
73
|
+
}
|
|
74
|
+
export interface VoucherTemplate {
|
|
75
|
+
id: string;
|
|
76
|
+
promotionId: string;
|
|
77
|
+
prefix: string;
|
|
78
|
+
codeLength: number;
|
|
79
|
+
defaultValidityDays: number | null;
|
|
80
|
+
}
|
|
81
|
+
export interface PricingVouchersModule {
|
|
82
|
+
/** A voucher template under a promotion of this store (codes of 12 to 32 random characters). */
|
|
83
|
+
createTemplate(input: {
|
|
84
|
+
promotionId: string;
|
|
85
|
+
prefix: string;
|
|
86
|
+
codeLength?: number;
|
|
87
|
+
defaultValidityDays?: number;
|
|
88
|
+
}, options?: Call): Promise<VoucherTemplate>;
|
|
89
|
+
/**
|
|
90
|
+
* Issue one voucher. `options.idempotencyKey` replays the first answer; the same template,
|
|
91
|
+
* customer and `source.ref` answers the voucher already issued (`created: false`) under any key.
|
|
92
|
+
*/
|
|
93
|
+
issue(input: IssueVoucherInput, options?: Call): Promise<IssuedVoucher>;
|
|
94
|
+
/** Issue up to 1,000 vouchers of one template; a refused item answers in its place. */
|
|
95
|
+
issueMany(input: IssueVouchersInput, options?: Call): Promise<IssueVouchersItem[]>;
|
|
96
|
+
/** A customer's vouchers in this store, newest first; every status when `status` is omitted. */
|
|
97
|
+
forCustomer(customer: VoucherCustomer, status?: VoucherStatus, options?: Call): Promise<Voucher[]>;
|
|
98
|
+
}
|
|
99
|
+
export declare const CREATE_VOUCHER_TEMPLATE_MUTATION = "\n mutation CreateVoucherTemplate($input: CreateVoucherTemplateInput!) {\n createVoucherTemplate(input: $input) {\n id\n promotionId\n prefix\n codeLength\n defaultValidityDays\n }\n }\n";
|
|
100
|
+
export declare const ISSUE_VOUCHER_MUTATION = "\n mutation IssueVoucher($input: IssueVoucherInput!, $idempotencyKey: String) {\n issueVoucher(input: $input, idempotencyKey: $idempotencyKey) { id code customerId expiresAt created }\n }\n";
|
|
101
|
+
export declare const ISSUE_VOUCHERS_MUTATION = "\n mutation IssueVouchers($input: IssueVouchersInput!, $idempotencyKey: String) {\n issueVouchers(input: $input, idempotencyKey: $idempotencyKey) {\n items { index errorCode message voucher { id code customerId expiresAt created } }\n }\n }\n";
|
|
102
|
+
export declare const VOUCHERS_FOR_CUSTOMER_QUERY = "\n query VouchersForCustomer($customer: VoucherCustomerInput!, $status: VoucherStatus) {\n vouchersForCustomer(customer: $customer, status: $status) {\n id\n code\n templateId\n promotionId\n customerId\n status\n issuedAt\n expiresAt\n sourceSystem\n sourceRef\n orderId\n }\n }\n";
|
|
103
|
+
export declare function createPricingVouchersModule(transport: Transport): PricingVouchersModule;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const ISSUED_VOUCHER_FIELDS = `id code customerId expiresAt created`;
|
|
2
|
+
export const CREATE_VOUCHER_TEMPLATE_MUTATION = /* GraphQL */ `
|
|
3
|
+
mutation CreateVoucherTemplate($input: CreateVoucherTemplateInput!) {
|
|
4
|
+
createVoucherTemplate(input: $input) {
|
|
5
|
+
id
|
|
6
|
+
promotionId
|
|
7
|
+
prefix
|
|
8
|
+
codeLength
|
|
9
|
+
defaultValidityDays
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
export const ISSUE_VOUCHER_MUTATION = /* GraphQL */ `
|
|
14
|
+
mutation IssueVoucher($input: IssueVoucherInput!, $idempotencyKey: String) {
|
|
15
|
+
issueVoucher(input: $input, idempotencyKey: $idempotencyKey) { ${ISSUED_VOUCHER_FIELDS} }
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
export const ISSUE_VOUCHERS_MUTATION = /* GraphQL */ `
|
|
19
|
+
mutation IssueVouchers($input: IssueVouchersInput!, $idempotencyKey: String) {
|
|
20
|
+
issueVouchers(input: $input, idempotencyKey: $idempotencyKey) {
|
|
21
|
+
items { index errorCode message voucher { ${ISSUED_VOUCHER_FIELDS} } }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
export const VOUCHERS_FOR_CUSTOMER_QUERY = /* GraphQL */ `
|
|
26
|
+
query VouchersForCustomer($customer: VoucherCustomerInput!, $status: VoucherStatus) {
|
|
27
|
+
vouchersForCustomer(customer: $customer, status: $status) {
|
|
28
|
+
id
|
|
29
|
+
code
|
|
30
|
+
templateId
|
|
31
|
+
promotionId
|
|
32
|
+
customerId
|
|
33
|
+
status
|
|
34
|
+
issuedAt
|
|
35
|
+
expiresAt
|
|
36
|
+
sourceSystem
|
|
37
|
+
sourceRef
|
|
38
|
+
orderId
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
export function createPricingVouchersModule(transport) {
|
|
43
|
+
return {
|
|
44
|
+
async createTemplate(input, options) {
|
|
45
|
+
const data = await transport.graphql({ api: 'admin', query: CREATE_VOUCHER_TEMPLATE_MUTATION, variables: { input } }, options);
|
|
46
|
+
return data.createVoucherTemplate;
|
|
47
|
+
},
|
|
48
|
+
async issue(input, options) {
|
|
49
|
+
const data = await transport.graphql({
|
|
50
|
+
api: 'admin',
|
|
51
|
+
query: ISSUE_VOUCHER_MUTATION,
|
|
52
|
+
variables: { input, idempotencyKey: options?.idempotencyKey },
|
|
53
|
+
}, options);
|
|
54
|
+
return data.issueVoucher;
|
|
55
|
+
},
|
|
56
|
+
async issueMany(input, options) {
|
|
57
|
+
const data = await transport.graphql({
|
|
58
|
+
api: 'admin',
|
|
59
|
+
query: ISSUE_VOUCHERS_MUTATION,
|
|
60
|
+
variables: { input, idempotencyKey: options?.idempotencyKey },
|
|
61
|
+
}, options);
|
|
62
|
+
return data.issueVouchers.items;
|
|
63
|
+
},
|
|
64
|
+
async forCustomer(customer, status, options) {
|
|
65
|
+
const data = await transport.graphql({ api: 'admin', query: VOUCHERS_FOR_CUSTOMER_QUERY, variables: { customer, status } }, options);
|
|
66
|
+
return data.vouchersForCustomer;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/** The records provenance is kept for. An address change is recorded on its customer. */
|
|
4
|
+
export type ProvenanceEntityType = 'PRODUCT' | 'VARIANT' | 'CUSTOMER';
|
|
5
|
+
/**
|
|
6
|
+
* The field groups a row can be about: the four groups an app can be granted (spec §8.10).
|
|
7
|
+
*/
|
|
8
|
+
export type ProvenanceFieldGroup = 'price' | 'stock' | 'product_details' | 'customer_details';
|
|
9
|
+
/** Who made the last change of a field group. */
|
|
10
|
+
export interface ProvenanceActor {
|
|
11
|
+
/**
|
|
12
|
+
* `user` (a staff member), `customer` (the shopper, on their own record), `api_token`,
|
|
13
|
+
* `app_installation`, `system` (a platform job), `service`, `anonymous` (a guest), or
|
|
14
|
+
* `unknown` when nothing could name the writer.
|
|
15
|
+
*/
|
|
16
|
+
type: string;
|
|
17
|
+
/**
|
|
18
|
+
* A User id for `user` and `customer`, an API token or app installation id, or
|
|
19
|
+
* `system:<job>`. Null for a guest, an unknown writer, or a customer since erased.
|
|
20
|
+
*/
|
|
21
|
+
id: string | null;
|
|
22
|
+
/**
|
|
23
|
+
* The app's name, the API token's name, the job (`system:<job>`), or a staff member's name for
|
|
24
|
+
* a caller holding `ReadAdministrator` who can see that staff member. Null otherwise, and never
|
|
25
|
+
* a customer's name.
|
|
26
|
+
*/
|
|
27
|
+
label: string | null;
|
|
28
|
+
}
|
|
29
|
+
/** The app or API key whose change a field group last carries: the "Managed by" badge. */
|
|
30
|
+
export interface ProvenanceManagedBy {
|
|
31
|
+
kind: 'app_installation' | 'api_token';
|
|
32
|
+
/** The app installation's or API token's id. */
|
|
33
|
+
id: string;
|
|
34
|
+
/** The app's name or the API token's name. */
|
|
35
|
+
label: string;
|
|
36
|
+
}
|
|
37
|
+
/** The last change of one field group of one record, in the store the client acts on. */
|
|
38
|
+
export interface EntityProvenance {
|
|
39
|
+
entityType: ProvenanceEntityType;
|
|
40
|
+
entityId: string;
|
|
41
|
+
/** A {@link ProvenanceFieldGroup}; a group a newer engine adds reaches you as it is. */
|
|
42
|
+
fieldGroup: string;
|
|
43
|
+
actor: ProvenanceActor;
|
|
44
|
+
/** The platform job that carried a change made for someone else (a bulk import), or null. */
|
|
45
|
+
via: string | null;
|
|
46
|
+
/** When the change committed, ISO-8601 UTC. */
|
|
47
|
+
writtenAt: string;
|
|
48
|
+
/** The trace of the change: the key into `admin.audit.correlation` and the event log. */
|
|
49
|
+
traceId: string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Non-null only when the last change of THIS group was made by an app or an API key of this
|
|
52
|
+
* store: the rule for showing "Managed by <name>". A staff, customer or job change clears it.
|
|
53
|
+
*/
|
|
54
|
+
managedBy: ProvenanceManagedBy | null;
|
|
55
|
+
}
|
|
56
|
+
/** The most records one read may name; more is refused with `PROVENANCE_REQUEST_INVALID`. */
|
|
57
|
+
export declare const PROVENANCE_MAX_IDS = 250;
|
|
58
|
+
export declare const ENTITY_PROVENANCE_QUERY = "\n query EntityProvenance($entityType: ProvenanceEntityType!, $ids: [ID!]!) {\n entityProvenance(entityType: $entityType, ids: $ids) {\n entityType\n entityId\n fieldGroup\n actor {\n type\n id\n label\n }\n via\n writtenAt\n traceId\n managedBy {\n kind\n id\n label\n }\n }\n }\n";
|
|
59
|
+
/**
|
|
60
|
+
* Who last changed each field group of a product, variant or customer (spec §8.10): the price, the
|
|
61
|
+
* stock, the product details, the customer's details. Recorded for every change, made by staff,
|
|
62
|
+
* an app, an API key, an import or the shopper, in the change's own transaction.
|
|
63
|
+
*
|
|
64
|
+
* - **Last writer only, per group.** One row per (record, group), replaced by the next write
|
|
65
|
+
* that NAMES a field of that group, an unchanged value re-sent included (the engine sees no
|
|
66
|
+
* prior value, as with `product_variant.price_changed`). What changed and when, in order, is the platform event log filtered by the
|
|
67
|
+
* record (`GET /event-log?aggregate_type=&aggregate_id=` in the plugins service, which needs
|
|
68
|
+
* `ReadPlatformEvents`); it is not here.
|
|
69
|
+
* - **`managedBy` is the badge rule.** Show "Managed by <label>" only when it is non-null.
|
|
70
|
+
* - **Only the store you act on.** A record the store does not hold, and one it holds with
|
|
71
|
+
* nothing recorded since provenance began, answer the same way: no rows.
|
|
72
|
+
* - **Permissions:** products and variants need `ReadCatalog` or `ReadProduct`, customers
|
|
73
|
+
* `ReadCustomer` (`FORBIDDEN` → `PermissionDeniedError`). Staff names need `ReadAdministrator`.
|
|
74
|
+
*
|
|
75
|
+
* Refusals: `PROVENANCE_REQUEST_INVALID` (`ValidationError`) for more than
|
|
76
|
+
* {@link PROVENANCE_MAX_IDS} ids; `ENTITY_NOT_FOUND` (`NotFoundError`) for an id that is not
|
|
77
|
+
* a record id at all, and from `get` for a record with nothing recorded.
|
|
78
|
+
*/
|
|
79
|
+
export interface ProvenanceModule {
|
|
80
|
+
/**
|
|
81
|
+
* Every recorded group of each named record, in the order the ids were given, then by group.
|
|
82
|
+
* A `Page` because every list in this SDK is one; it is always complete (one round trip, at
|
|
83
|
+
* most {@link PROVENANCE_MAX_IDS} ids).
|
|
84
|
+
*/
|
|
85
|
+
list(entityType: ProvenanceEntityType, ids: readonly string[], options?: Call): Promise<Page<EntityProvenance>>;
|
|
86
|
+
/**
|
|
87
|
+
* The recorded groups of one record. Throws `NotFoundError` (`ENTITY_NOT_FOUND`) when the
|
|
88
|
+
* store holds none: the record is not this store's, or nothing has changed it since
|
|
89
|
+
* provenance began. The two are deliberately indistinguishable.
|
|
90
|
+
*/
|
|
91
|
+
get(entityType: ProvenanceEntityType, id: string, options?: Call): Promise<EntityProvenance[]>;
|
|
92
|
+
}
|
|
93
|
+
export declare function createProvenanceModule(transport: Transport): ProvenanceModule;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createPage, NotFoundError } from '@sixtyninecommerce/core';
|
|
2
|
+
/** The most records one read may name; more is refused with `PROVENANCE_REQUEST_INVALID`. */
|
|
3
|
+
export const PROVENANCE_MAX_IDS = 250;
|
|
4
|
+
export const ENTITY_PROVENANCE_QUERY = /* GraphQL */ `
|
|
5
|
+
query EntityProvenance($entityType: ProvenanceEntityType!, $ids: [ID!]!) {
|
|
6
|
+
entityProvenance(entityType: $entityType, ids: $ids) {
|
|
7
|
+
entityType
|
|
8
|
+
entityId
|
|
9
|
+
fieldGroup
|
|
10
|
+
actor {
|
|
11
|
+
type
|
|
12
|
+
id
|
|
13
|
+
label
|
|
14
|
+
}
|
|
15
|
+
via
|
|
16
|
+
writtenAt
|
|
17
|
+
traceId
|
|
18
|
+
managedBy {
|
|
19
|
+
kind
|
|
20
|
+
id
|
|
21
|
+
label
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
export function createProvenanceModule(transport) {
|
|
27
|
+
async function read(entityType, ids, options) {
|
|
28
|
+
const data = await transport.graphql({ api: 'admin', query: ENTITY_PROVENANCE_QUERY, variables: { entityType, ids: [...ids] } }, options);
|
|
29
|
+
return data.entityProvenance;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
async list(entityType, ids, options) {
|
|
33
|
+
const items = await read(entityType, ids, options);
|
|
34
|
+
return createPage(items, { nextCursor: null, hasNext: false, total: items.length }, () => {
|
|
35
|
+
// Unreachable while `hasNext` is false — `Page.next()` short-circuits — and a rejection
|
|
36
|
+
// rather than a silent re-fetch if that changes.
|
|
37
|
+
throw new Error('entityProvenance returns a complete list; there is no next page');
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
async get(entityType, id, options) {
|
|
41
|
+
const rows = await read(entityType, [id], options);
|
|
42
|
+
if (rows.length === 0) {
|
|
43
|
+
throw new NotFoundError({
|
|
44
|
+
code: 'ENTITY_NOT_FOUND',
|
|
45
|
+
message: `This store has no recorded change of ${entityType.toLowerCase()} ${id}.`,
|
|
46
|
+
status: 404,
|
|
47
|
+
service: 'vendure',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return rows;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|