@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,288 @@
|
|
|
1
|
+
import { NotFoundError, UpstreamError, ValidationError, } from '@sixtyninecommerce/core';
|
|
2
|
+
import { ASSET_REF_FIELDS, assertDeleted, availabilityCustomFields, DELETION_FIELDS, FACET_VALUE_REF_FIELDS, fieldOf, vendureListPage, } from './types.js';
|
|
3
|
+
const PARCEL_PACKING_MODES = ['shared', 'separate', 'none'];
|
|
4
|
+
/** The engine's bounds for `parcelCapacity` (shipping-calculator's `MAX_PARCEL_CAPACITY`). */
|
|
5
|
+
const PARCEL_CAPACITY_MIN = 1;
|
|
6
|
+
const PARCEL_CAPACITY_MAX = 10_000;
|
|
7
|
+
/** The engine's bound on one `revertPrice` call (`MAX_REVERT_VARIANTS`). */
|
|
8
|
+
export const MAX_REVERT_PRICE_VARIANTS = 500;
|
|
9
|
+
export const VARIANT_FIELDS = /* GraphQL */ `
|
|
10
|
+
id
|
|
11
|
+
productId
|
|
12
|
+
createdAt
|
|
13
|
+
updatedAt
|
|
14
|
+
languageCode
|
|
15
|
+
name
|
|
16
|
+
sku
|
|
17
|
+
enabled
|
|
18
|
+
price
|
|
19
|
+
priceWithTax
|
|
20
|
+
currencyCode
|
|
21
|
+
taxCategory {
|
|
22
|
+
id
|
|
23
|
+
name
|
|
24
|
+
}
|
|
25
|
+
trackInventory
|
|
26
|
+
outOfStockThreshold
|
|
27
|
+
useGlobalOutOfStockThreshold
|
|
28
|
+
stockLevels {
|
|
29
|
+
stockLocationId
|
|
30
|
+
stockOnHand
|
|
31
|
+
stockAllocated
|
|
32
|
+
}
|
|
33
|
+
options {
|
|
34
|
+
id
|
|
35
|
+
code
|
|
36
|
+
name
|
|
37
|
+
groupId
|
|
38
|
+
}
|
|
39
|
+
featuredAsset { ${ASSET_REF_FIELDS} }
|
|
40
|
+
assets { ${ASSET_REF_FIELDS} }
|
|
41
|
+
facetValues { ${FACET_VALUE_REF_FIELDS} }
|
|
42
|
+
translations {
|
|
43
|
+
id
|
|
44
|
+
languageCode
|
|
45
|
+
name
|
|
46
|
+
}
|
|
47
|
+
customFields {
|
|
48
|
+
taxClassKey
|
|
49
|
+
availabilityState
|
|
50
|
+
availableFrom
|
|
51
|
+
preorderLimit
|
|
52
|
+
parcelCapacity
|
|
53
|
+
parcelPacking
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
export const VARIANTS_QUERY = /* GraphQL */ `
|
|
57
|
+
query CatalogVariants($productId: ID!, $options: ProductVariantListOptions) {
|
|
58
|
+
productVariants(productId: $productId, options: $options) {
|
|
59
|
+
items { ${VARIANT_FIELDS} }
|
|
60
|
+
totalItems
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
export const VARIANT_QUERY = /* GraphQL */ `
|
|
65
|
+
query CatalogVariant($id: ID!) {
|
|
66
|
+
productVariant(id: $id) { ${VARIANT_FIELDS} }
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
export const CREATE_VARIANTS_MUTATION = /* GraphQL */ `
|
|
70
|
+
mutation CatalogCreateVariants($input: [CreateProductVariantInput!]!) {
|
|
71
|
+
createProductVariants(input: $input) { ${VARIANT_FIELDS} }
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
export const UPDATE_VARIANTS_MUTATION = /* GraphQL */ `
|
|
75
|
+
mutation CatalogUpdateVariants($input: [UpdateProductVariantInput!]!) {
|
|
76
|
+
updateProductVariants(input: $input) { ${VARIANT_FIELDS} }
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
export const DELETE_VARIANTS_MUTATION = /* GraphQL */ `
|
|
80
|
+
mutation CatalogDeleteVariants($ids: [ID!]!) {
|
|
81
|
+
deleteProductVariants(ids: $ids) { ${DELETION_FIELDS} }
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
/**
|
|
85
|
+
* A document of its own, never a field of `VARIANT_FIELDS`: every other variant read keeps
|
|
86
|
+
* working against an engine older than the field, and pays nothing for it.
|
|
87
|
+
*/
|
|
88
|
+
export const VARIANT_PRICE_SOURCES_QUERY = /* GraphQL */ `
|
|
89
|
+
query CatalogVariantPriceSources($id: ID!) {
|
|
90
|
+
productVariant(id: $id) {
|
|
91
|
+
id
|
|
92
|
+
priceSources {
|
|
93
|
+
currencyCode
|
|
94
|
+
price
|
|
95
|
+
source
|
|
96
|
+
organizationPrice
|
|
97
|
+
organizationName
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
export const REVERT_TO_ORGANIZATION_PRICE_MUTATION = /* GraphQL */ `
|
|
103
|
+
mutation CatalogRevertToOrganizationPrice($variantIds: [ID!]!, $currencyCodes: [CurrencyCode!]) {
|
|
104
|
+
revertToOrganizationPrice(variantIds: $variantIds, currencyCodes: $currencyCodes) { ${VARIANT_FIELDS} }
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
/** The price basis the store stores, read on the SAME tenant as the write. */
|
|
108
|
+
export const PRICE_BASIS_QUERY = /* GraphQL */ `
|
|
109
|
+
query CatalogPriceBasis {
|
|
110
|
+
activeChannel {
|
|
111
|
+
pricesIncludeTax
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
function isParcelPacking(value) {
|
|
116
|
+
return PARCEL_PACKING_MODES.includes(value);
|
|
117
|
+
}
|
|
118
|
+
export function toVariant(wire) {
|
|
119
|
+
const { customFields, ...rest } = wire;
|
|
120
|
+
return {
|
|
121
|
+
...rest,
|
|
122
|
+
taxClassKey: customFields?.taxClassKey ?? null,
|
|
123
|
+
availabilityState: customFields?.availabilityState ?? 'inherit',
|
|
124
|
+
availableFrom: customFields?.availableFrom ?? null,
|
|
125
|
+
preorderLimit: customFields?.preorderLimit ?? null,
|
|
126
|
+
parcelCapacity: customFields?.parcelCapacity ?? null,
|
|
127
|
+
parcelPacking: isParcelPacking(customFields?.parcelPacking)
|
|
128
|
+
? customFields.parcelPacking
|
|
129
|
+
: 'shared',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Refuse a parcel rule the engine would refuse, before anything is sent. */
|
|
133
|
+
function assertParcelRule(input) {
|
|
134
|
+
const { parcelCapacity, parcelPacking } = input;
|
|
135
|
+
if (parcelCapacity !== undefined &&
|
|
136
|
+
parcelCapacity !== null &&
|
|
137
|
+
!(Number.isInteger(parcelCapacity) &&
|
|
138
|
+
parcelCapacity >= PARCEL_CAPACITY_MIN &&
|
|
139
|
+
parcelCapacity <= PARCEL_CAPACITY_MAX)) {
|
|
140
|
+
throw refuse('INVALID_PARCEL_CAPACITY', `parcelCapacity must be a whole number from ${String(PARCEL_CAPACITY_MIN)} to ${String(PARCEL_CAPACITY_MAX)}, or null for the default (the store's items per parcel, or 1 when parcelPacking is separate); got ${JSON.stringify(parcelCapacity)}.`);
|
|
141
|
+
}
|
|
142
|
+
if (parcelPacking !== undefined && parcelPacking !== null && !isParcelPacking(parcelPacking)) {
|
|
143
|
+
throw refuse('INVALID_PARCEL_PACKING', `parcelPacking must be "shared", "separate", "none" or null; got ${JSON.stringify(parcelPacking)}.`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function toWireInput(input) {
|
|
147
|
+
const { availabilityState, availableFrom, preorderLimit, taxClassKey, parcelCapacity, parcelPacking, priceBasis, minOrderQuantity, salesUnit, cost, packageWeightGrams, packageLengthCm, packageWidthCm, packageHeightCm, backorderPolicy, backorderLimit, gtin, mpn, ...rest } = input;
|
|
148
|
+
void priceBasis;
|
|
149
|
+
const customFields = availabilityCustomFields({
|
|
150
|
+
availabilityState,
|
|
151
|
+
availableFrom,
|
|
152
|
+
preorderLimit,
|
|
153
|
+
});
|
|
154
|
+
const fields = {
|
|
155
|
+
minOrderQuantity,
|
|
156
|
+
salesUnit,
|
|
157
|
+
cost,
|
|
158
|
+
packageWeightGrams,
|
|
159
|
+
packageLengthCm,
|
|
160
|
+
packageWidthCm,
|
|
161
|
+
packageHeightCm,
|
|
162
|
+
backorderPolicy,
|
|
163
|
+
backorderLimit,
|
|
164
|
+
gtin,
|
|
165
|
+
mpn,
|
|
166
|
+
};
|
|
167
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
168
|
+
if (value !== undefined)
|
|
169
|
+
customFields[key] = value;
|
|
170
|
+
}
|
|
171
|
+
if (taxClassKey !== undefined)
|
|
172
|
+
customFields.taxClassKey = taxClassKey;
|
|
173
|
+
if (parcelCapacity !== undefined)
|
|
174
|
+
customFields.parcelCapacity = parcelCapacity;
|
|
175
|
+
if (parcelPacking !== undefined) {
|
|
176
|
+
customFields.parcelPacking = parcelPacking === 'shared' ? null : parcelPacking;
|
|
177
|
+
}
|
|
178
|
+
return Object.keys(customFields).length > 0 ? { ...rest, customFields } : rest;
|
|
179
|
+
}
|
|
180
|
+
function refuse(code, message) {
|
|
181
|
+
return new ValidationError({ code, message, status: 400, service: 'vendure' });
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Every priced input (`price`, or any `prices` entry) must be whole minor units and state its
|
|
185
|
+
* basis, and the basis must be the store's. Read per call, with the call's own options, so a
|
|
186
|
+
* per-call tenant is checked against ITS store rather than the client's default one.
|
|
187
|
+
*/
|
|
188
|
+
export async function assertPricesStateBasis(transport, inputs, options) {
|
|
189
|
+
const priced = inputs.filter((input) => input.price !== undefined || input.prices !== undefined);
|
|
190
|
+
if (priced.length === 0)
|
|
191
|
+
return;
|
|
192
|
+
for (const input of priced) {
|
|
193
|
+
const amounts = [
|
|
194
|
+
...(input.price !== undefined ? [input.price] : []),
|
|
195
|
+
...(input.prices ?? []).map((entry) => entry.price),
|
|
196
|
+
];
|
|
197
|
+
const fractional = amounts.find((amount) => !Number.isInteger(amount));
|
|
198
|
+
if (fractional !== undefined) {
|
|
199
|
+
throw refuse('INVALID_PRICE', `price must be a whole number of minor units; got ${String(fractional)}.`);
|
|
200
|
+
}
|
|
201
|
+
if (input.priceBasis !== 'net' && input.priceBasis !== 'gross') {
|
|
202
|
+
throw refuse('PRICE_BASIS_REQUIRED', 'A variant price must state its basis: priceBasis "net" or "gross".');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const channel = await fieldOf(transport, PRICE_BASIS_QUERY, 'activeChannel', {}, options);
|
|
206
|
+
const storeBasis = channel.pricesIncludeTax ? 'gross' : 'net';
|
|
207
|
+
const wrong = priced.find((input) => input.priceBasis !== storeBasis);
|
|
208
|
+
if (wrong) {
|
|
209
|
+
throw refuse('PRICE_BASIS_MISMATCH', `This store states prices ${storeBasis === 'gross' ? 'including' : 'excluding'} tax, so a price must be sent with priceBasis "${storeBasis}"; got "${String(wrong.priceBasis)}". Convert it before sending — the SDK does not, because the rate depends on each order's zone.`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export function createVariantsModule(transport) {
|
|
213
|
+
/**
|
|
214
|
+
* Every priced input must state its basis, and the basis must be the store's.
|
|
215
|
+
* Read per call, with the call's own options, so a per-call tenant is checked
|
|
216
|
+
* against ITS store rather than the client's default one.
|
|
217
|
+
*/
|
|
218
|
+
const assertPriceBasis = async (inputs, options) => {
|
|
219
|
+
await assertPricesStateBasis(transport, inputs, options);
|
|
220
|
+
};
|
|
221
|
+
const mapAll = (rows, what) => rows.map((row) => {
|
|
222
|
+
if (row === null) {
|
|
223
|
+
// `[ProductVariant]!` allows a null item; a write that answers one has
|
|
224
|
+
// lost track of a row it just wrote, and pretending otherwise would
|
|
225
|
+
// hand the caller fewer variants than it sent.
|
|
226
|
+
throw new UpstreamError({
|
|
227
|
+
code: 'VARIANT_WRITE_UNCONFIRMED',
|
|
228
|
+
message: `The engine answered ${what} without one of the variants it wrote. Read them back with variants.list(productId) before retrying.`,
|
|
229
|
+
status: 502,
|
|
230
|
+
service: 'vendure',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return toVariant(row);
|
|
234
|
+
});
|
|
235
|
+
const update = async (inputs, options) => {
|
|
236
|
+
inputs.forEach(assertParcelRule);
|
|
237
|
+
await assertPriceBasis(inputs, options);
|
|
238
|
+
const rows = await fieldOf(transport, UPDATE_VARIANTS_MUTATION, 'updateProductVariants', { input: inputs.map((input) => toWireInput({ ...input })) }, options);
|
|
239
|
+
return mapAll(rows, 'updateProductVariants');
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
list(productId, options) {
|
|
243
|
+
return vendureListPage(transport, VARIANTS_QUERY, 'productVariants', (skip, take) => ({ productId, options: { skip, take } }), options, toVariant);
|
|
244
|
+
},
|
|
245
|
+
async get(id, options) {
|
|
246
|
+
const variant = await fieldOf(transport, VARIANT_QUERY, 'productVariant', { id }, options);
|
|
247
|
+
return variant ? toVariant(variant) : null;
|
|
248
|
+
},
|
|
249
|
+
async create(inputs, options) {
|
|
250
|
+
inputs.forEach(assertParcelRule);
|
|
251
|
+
await assertPriceBasis(inputs, options);
|
|
252
|
+
const rows = await fieldOf(transport, CREATE_VARIANTS_MUTATION, 'createProductVariants', { input: inputs.map((input) => toWireInput({ ...input })) }, options);
|
|
253
|
+
return mapAll(rows, 'createProductVariants');
|
|
254
|
+
},
|
|
255
|
+
update,
|
|
256
|
+
async delete(ids, options) {
|
|
257
|
+
assertDeleted('VARIANT_NOT_DELETED', await fieldOf(transport, DELETE_VARIANTS_MUTATION, 'deleteProductVariants', { ids }, options));
|
|
258
|
+
},
|
|
259
|
+
async setStockLevels(variantId, levels, options) {
|
|
260
|
+
const [variant] = await update([{ id: variantId, stockLevels: levels }], options);
|
|
261
|
+
return variant;
|
|
262
|
+
},
|
|
263
|
+
async priceSources(variantId, options) {
|
|
264
|
+
const variant = await fieldOf(transport, VARIANT_PRICE_SOURCES_QUERY, 'productVariant', { id: variantId }, options);
|
|
265
|
+
if (!variant) {
|
|
266
|
+
throw new NotFoundError({
|
|
267
|
+
code: 'VARIANT_NOT_FOUND',
|
|
268
|
+
message: `This store has no variant ${variantId}.`,
|
|
269
|
+
status: 404,
|
|
270
|
+
service: 'vendure',
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return variant.priceSources;
|
|
274
|
+
},
|
|
275
|
+
async revertPrice(variantIds, options) {
|
|
276
|
+
const { currencyCodes, ...call } = options ?? {};
|
|
277
|
+
// The engine counts distinct ids the way it stores them (lower case).
|
|
278
|
+
const distinct = new Set(variantIds.map((id) => id.toLowerCase())).size;
|
|
279
|
+
if (distinct > MAX_REVERT_PRICE_VARIANTS) {
|
|
280
|
+
throw refuse('ORGANIZATION_PRICE_REVERT_INVALID', `Revert at most ${String(MAX_REVERT_PRICE_VARIANTS)} variants per call; this one named ${String(distinct)}. Nothing was sent.`);
|
|
281
|
+
}
|
|
282
|
+
if (variantIds.length === 0)
|
|
283
|
+
return [];
|
|
284
|
+
const rows = await fieldOf(transport, REVERT_TO_ORGANIZATION_PRICE_MUTATION, 'revertToOrganizationPrice', { variantIds, currencyCodes: currencyCodes === undefined ? null : [...currencyCodes] }, call);
|
|
285
|
+
return mapAll(rows, 'revertToOrganizationPrice');
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
export type ProductVisibilityMode = 'public' | 'private';
|
|
4
|
+
/**
|
|
5
|
+
* Who a product is shown to on the storefront. A product with no row is
|
|
6
|
+
* public; `private` limits it to the listed customer groups (none listed: to
|
|
7
|
+
* nobody). Enforced on the Shop API's product, product list and search
|
|
8
|
+
* resolvers — the Admin API always sees every product.
|
|
9
|
+
*/
|
|
10
|
+
export interface ProductVisibility {
|
|
11
|
+
productId: string;
|
|
12
|
+
mode: ProductVisibilityMode;
|
|
13
|
+
/** `manual` for anything written here; `imported` for rows the import pipeline wrote. */
|
|
14
|
+
source: 'manual' | 'imported';
|
|
15
|
+
allowedCustomerGroupIds: string[];
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SetProductVisibilityInput {
|
|
20
|
+
productId: string;
|
|
21
|
+
mode: ProductVisibilityMode;
|
|
22
|
+
/** Ignored under `public`. */
|
|
23
|
+
allowedCustomerGroupIds?: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Per-product storefront visibility. Needs `ReadCatalog` / `UpdateCatalog`.
|
|
27
|
+
*
|
|
28
|
+
* `set` on a product of another store is refused with the engine's
|
|
29
|
+
* `USER_INPUT_ERROR` (`ValidationError`): "Product {id} not found in this
|
|
30
|
+
* channel". A write from here is always `source: manual`, which a later
|
|
31
|
+
* pipeline import leaves alone.
|
|
32
|
+
*
|
|
33
|
+
* The Admin API's `productsByVisibility` list is deliberately NOT exposed: it
|
|
34
|
+
* counts and pages visibility rows across every store before narrowing to this
|
|
35
|
+
* one, so its totals and pages are wrong (recorded as a finding).
|
|
36
|
+
*/
|
|
37
|
+
export interface VisibilityModule {
|
|
38
|
+
/** Null when the product has no override (it is public). `NotFoundError` for an unknown product. */
|
|
39
|
+
get(productId: string, options?: Call): Promise<ProductVisibility | null>;
|
|
40
|
+
set(input: SetProductVisibilityInput, options?: Call): Promise<ProductVisibility>;
|
|
41
|
+
/** Apply one setting to many products; answers how many rows changed. */
|
|
42
|
+
bulkSet(input: {
|
|
43
|
+
productIds: string[];
|
|
44
|
+
mode: ProductVisibilityMode;
|
|
45
|
+
allowedCustomerGroupIds?: string[];
|
|
46
|
+
}, options?: Call): Promise<number>;
|
|
47
|
+
/**
|
|
48
|
+
* Remove the override (the product becomes public). Resolves the same way
|
|
49
|
+
* whether or not there was one: the engine does not say.
|
|
50
|
+
*/
|
|
51
|
+
clear(productId: string, options?: Call): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
export declare const PRODUCT_VISIBILITY_QUERY = "\n query CatalogProductVisibility($id: ID!) {\n product(id: $id) {\n id\n visibility { \n productId\n mode\n source\n allowedCustomerGroupIds\n createdAt\n updatedAt\n }\n }\n }\n";
|
|
54
|
+
export declare const SET_VISIBILITY_MUTATION = "\n mutation CatalogSetVisibility($input: SetProductVisibilityInput!) {\n setProductVisibility(input: $input) { \n productId\n mode\n source\n allowedCustomerGroupIds\n createdAt\n updatedAt\n }\n }\n";
|
|
55
|
+
export declare const BULK_SET_VISIBILITY_MUTATION = "\n mutation CatalogBulkSetVisibility($input: BulkSetProductVisibilityInput!) {\n bulkSetProductVisibility(input: $input) {\n updated\n }\n }\n";
|
|
56
|
+
export declare const CLEAR_VISIBILITY_MUTATION = "\n mutation CatalogClearVisibility($productId: ID!) {\n clearProductVisibility(productId: $productId)\n }\n";
|
|
57
|
+
export declare function createVisibilityModule(transport: Transport): VisibilityModule;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { NotFoundError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { fieldOf } from './types.js';
|
|
3
|
+
const VISIBILITY_FIELDS = /* GraphQL */ `
|
|
4
|
+
productId
|
|
5
|
+
mode
|
|
6
|
+
source
|
|
7
|
+
allowedCustomerGroupIds
|
|
8
|
+
createdAt
|
|
9
|
+
updatedAt
|
|
10
|
+
`;
|
|
11
|
+
export const PRODUCT_VISIBILITY_QUERY = /* GraphQL */ `
|
|
12
|
+
query CatalogProductVisibility($id: ID!) {
|
|
13
|
+
product(id: $id) {
|
|
14
|
+
id
|
|
15
|
+
visibility { ${VISIBILITY_FIELDS} }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
export const SET_VISIBILITY_MUTATION = /* GraphQL */ `
|
|
20
|
+
mutation CatalogSetVisibility($input: SetProductVisibilityInput!) {
|
|
21
|
+
setProductVisibility(input: $input) { ${VISIBILITY_FIELDS} }
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
export const BULK_SET_VISIBILITY_MUTATION = /* GraphQL */ `
|
|
25
|
+
mutation CatalogBulkSetVisibility($input: BulkSetProductVisibilityInput!) {
|
|
26
|
+
bulkSetProductVisibility(input: $input) {
|
|
27
|
+
updated
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
export const CLEAR_VISIBILITY_MUTATION = /* GraphQL */ `
|
|
32
|
+
mutation CatalogClearVisibility($productId: ID!) {
|
|
33
|
+
clearProductVisibility(productId: $productId)
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
export function createVisibilityModule(transport) {
|
|
37
|
+
return {
|
|
38
|
+
async get(productId, options) {
|
|
39
|
+
const product = await fieldOf(transport, PRODUCT_VISIBILITY_QUERY, 'product', { id: productId }, options);
|
|
40
|
+
if (!product) {
|
|
41
|
+
throw new NotFoundError({
|
|
42
|
+
code: 'PRODUCT_NOT_FOUND',
|
|
43
|
+
message: `This store has no product ${productId}.`,
|
|
44
|
+
status: 404,
|
|
45
|
+
service: 'vendure',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return product.visibility;
|
|
49
|
+
},
|
|
50
|
+
set: (input, options) => fieldOf(transport, SET_VISIBILITY_MUTATION, 'setProductVisibility', { input }, options),
|
|
51
|
+
async bulkSet(input, options) {
|
|
52
|
+
const result = await fieldOf(transport, BULK_SET_VISIBILITY_MUTATION, 'bulkSetProductVisibility', { input }, options);
|
|
53
|
+
return result.updated;
|
|
54
|
+
},
|
|
55
|
+
async clear(productId, options) {
|
|
56
|
+
await fieldOf(transport, CLEAR_VISIBILITY_MUTATION, 'clearProductVisibility', { productId }, options);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|