@sixtyninecommerce/admin 0.8.0 → 0.9.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 +2445 -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 +489 -0
- package/dist/catalog/products.js +365 -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 +274 -0
- package/dist/catalog/variants.js +258 -0
- package/dist/catalog/visibility.d.ts +57 -0
- package/dist/catalog/visibility.js +59 -0
- package/dist/client.d.ts +171 -3
- package/dist/client.js +81 -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 +360 -0
- package/dist/fulfillments/index.js +425 -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 +84 -9
- package/dist/index.js +16 -1
- package/dist/jobs/bulk-action-run.d.ts +65 -0
- package/dist/jobs/bulk-action-run.js +70 -0
- package/dist/locations/index.d.ts +6 -2
- package/dist/meta/index.d.ts +32 -0
- package/dist/orders/commit-edit.d.ts +123 -0
- package/dist/orders/commit-edit.js +142 -0
- package/dist/orders/get.d.ts +722 -0
- package/dist/orders/get.js +530 -0
- package/dist/orders/index.d.ts +105 -20
- package/dist/orders/index.js +44 -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 +132 -0
- package/dist/orders/manual-discounts.js +95 -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 +234 -9
- package/dist/payments/index.js +90 -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 +405 -0
- package/dist/pricing/index.js +351 -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/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,274 @@
|
|
|
1
|
+
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call, ListOptions } from '../types.js';
|
|
3
|
+
import { type AssetRef, type Availability, type AvailabilityInput, type FacetValueRef, type LanguageCode, type PriceBasis, type TranslationInput, type VariantAvailabilityState } from './types.js';
|
|
4
|
+
export interface StockLevel {
|
|
5
|
+
stockLocationId: string;
|
|
6
|
+
stockOnHand: number;
|
|
7
|
+
stockAllocated: number;
|
|
8
|
+
}
|
|
9
|
+
export interface StockLevelInput {
|
|
10
|
+
stockLocationId: string;
|
|
11
|
+
/** Absolute, not a delta. Refused below zero by the engine ("stockOnHand cannot be a negative value"). */
|
|
12
|
+
stockOnHand: number;
|
|
13
|
+
}
|
|
14
|
+
export type InventoryFlag = 'TRUE' | 'FALSE' | 'INHERIT';
|
|
15
|
+
/**
|
|
16
|
+
* How a variant takes parcel space (ADR 2026-09-17 parcel-count rule):
|
|
17
|
+
* `shared` packs with the rest of the order up to the items per parcel,
|
|
18
|
+
* `separate` never shares a parcel with other items (one unit per parcel unless
|
|
19
|
+
* `parcelCapacity` is set), `none` never adds one.
|
|
20
|
+
*/
|
|
21
|
+
export type ParcelPacking = 'shared' | 'separate' | 'none';
|
|
22
|
+
/**
|
|
23
|
+
* A product variant — the thing that has a SKU, a price and stock.
|
|
24
|
+
*
|
|
25
|
+
* Money is integer minor units of `currencyCode`. On READ, `price` is ALWAYS
|
|
26
|
+
* net and `priceWithTax` ALWAYS gross, whatever the store's basis — core
|
|
27
|
+
* derives whichever side it does not store, at the rate of the store's default
|
|
28
|
+
* tax zone. On WRITE, `price` is in the STORE's basis (`priceBasis`). So a
|
|
29
|
+
* read-modify-write on a gross store (`pricesIncludeTax: true`) must start
|
|
30
|
+
* from `priceWithTax`: writing back the `price` it read would drop the shelf
|
|
31
|
+
* price by the whole VAT amount.
|
|
32
|
+
*/
|
|
33
|
+
export interface ProductVariant extends Availability<VariantAvailabilityState> {
|
|
34
|
+
id: string;
|
|
35
|
+
productId: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt: string;
|
|
38
|
+
languageCode: LanguageCode;
|
|
39
|
+
name: string;
|
|
40
|
+
sku: string;
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
/** Net, always — never the figure to write back on a gross store. */
|
|
43
|
+
price: number;
|
|
44
|
+
/** Gross, always, at the store's default tax zone. */
|
|
45
|
+
priceWithTax: number;
|
|
46
|
+
currencyCode: string;
|
|
47
|
+
taxCategory: {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The store's tax class this variant belongs to (spec §8.6), or null. When
|
|
53
|
+
* set, `taxCategory` is the category that class meant for the store's own
|
|
54
|
+
* country at the moment the variant was last written with it — editing the
|
|
55
|
+
* class's mappings later does not re-price it.
|
|
56
|
+
*
|
|
57
|
+
* A write that sets `taxCategoryId` WITHOUT naming the key (the dashboard's
|
|
58
|
+
* variant form does this) to a category the class does not mean DETACHES the
|
|
59
|
+
* class: the key is cleared in the same write, and this reads null.
|
|
60
|
+
*/
|
|
61
|
+
taxClassKey: string | null;
|
|
62
|
+
trackInventory: InventoryFlag;
|
|
63
|
+
outOfStockThreshold: number;
|
|
64
|
+
useGlobalOutOfStockThreshold: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Units of this variant that fill one parcel. Null uses the store's items per parcel, or 1
|
|
67
|
+
* when `parcelPacking` is `separate`.
|
|
68
|
+
*/
|
|
69
|
+
parcelCapacity: number | null;
|
|
70
|
+
/** Never null on read: an unset packing is `shared`, the engine's default. */
|
|
71
|
+
parcelPacking: ParcelPacking;
|
|
72
|
+
stockLevels: StockLevel[];
|
|
73
|
+
options: Array<{
|
|
74
|
+
id: string;
|
|
75
|
+
code: string;
|
|
76
|
+
name: string;
|
|
77
|
+
groupId: string;
|
|
78
|
+
}>;
|
|
79
|
+
featuredAsset: AssetRef | null;
|
|
80
|
+
assets: AssetRef[];
|
|
81
|
+
facetValues: FacetValueRef[];
|
|
82
|
+
translations: Array<{
|
|
83
|
+
id: string;
|
|
84
|
+
languageCode: LanguageCode;
|
|
85
|
+
name: string;
|
|
86
|
+
}>;
|
|
87
|
+
}
|
|
88
|
+
export interface VariantTranslationInput extends TranslationInput {
|
|
89
|
+
name?: string;
|
|
90
|
+
}
|
|
91
|
+
interface VariantFields extends AvailabilityInput<VariantAvailabilityState> {
|
|
92
|
+
enabled?: boolean;
|
|
93
|
+
translations?: VariantTranslationInput[];
|
|
94
|
+
/** One option from EACH of the product's option groups, and a combination no other variant has. */
|
|
95
|
+
optionIds?: string[];
|
|
96
|
+
/**
|
|
97
|
+
* Integer minor units in the store's default currency, stated on
|
|
98
|
+
* `priceBasis`. The two travel together: a price without its basis is
|
|
99
|
+
* refused (`PRICE_BASIS_REQUIRED`), and a basis other than the store's is
|
|
100
|
+
* refused (`PRICE_BASIS_MISMATCH`) — see {@link PriceBasis}.
|
|
101
|
+
*/
|
|
102
|
+
price?: number;
|
|
103
|
+
priceBasis?: PriceBasis;
|
|
104
|
+
/**
|
|
105
|
+
* Set the category directly. Mutually exclusive with a `taxClassKey` naming
|
|
106
|
+
* a different category (`INVALID_TAX_CATEGORY_FOR_CLASS`).
|
|
107
|
+
*/
|
|
108
|
+
taxCategoryId?: string;
|
|
109
|
+
/**
|
|
110
|
+
* The store's tax class (spec §8.6). The engine resolves it for the store's
|
|
111
|
+
* own business country and writes the resulting `taxCategoryId` in the same
|
|
112
|
+
* write. Refusals, in the engine's words: `TAX_CLASS_NOT_FOUND`
|
|
113
|
+
* (`NotFoundError`) — "This store has no tax class with the key "…"." — and
|
|
114
|
+
* `TAX_CLASS_UNMAPPED` (`ValidationError`) — "The tax class "…" has no tax
|
|
115
|
+
* category for NL and no "*" fallback. Map it before using it here."
|
|
116
|
+
*
|
|
117
|
+
* Null clears the key and leaves the category as it is.
|
|
118
|
+
*/
|
|
119
|
+
taxClassKey?: string | null;
|
|
120
|
+
/**
|
|
121
|
+
* A whole number from 1 to 10,000, or null for the default (the store's items per
|
|
122
|
+
* parcel, or 1 when `parcelPacking` is `separate`). Anything else is refused before sending (`INVALID_PARCEL_CAPACITY`).
|
|
123
|
+
*/
|
|
124
|
+
parcelCapacity?: number | null;
|
|
125
|
+
/**
|
|
126
|
+
* `shared` and null both store null — the one way "shared" is stored, so a
|
|
127
|
+
* read never tells them apart. Another value is refused before sending
|
|
128
|
+
* (`INVALID_PARCEL_PACKING`).
|
|
129
|
+
*/
|
|
130
|
+
parcelPacking?: ParcelPacking | null;
|
|
131
|
+
stockLevels?: StockLevelInput[];
|
|
132
|
+
trackInventory?: InventoryFlag;
|
|
133
|
+
outOfStockThreshold?: number;
|
|
134
|
+
useGlobalOutOfStockThreshold?: boolean;
|
|
135
|
+
featuredAssetId?: string | null;
|
|
136
|
+
assetIds?: string[];
|
|
137
|
+
facetValueIds?: string[];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Where a store's price in one currency comes from (multi-region decision 59):
|
|
141
|
+
* - `OWN` — a price the store stored itself, or its own change of its organization's price.
|
|
142
|
+
* - `INHERITED` — the organization's stored price, copied to the store and unchanged since; it
|
|
143
|
+
* follows the organization's later changes.
|
|
144
|
+
* - `CONVERTED` — nothing stored: the storefront converts the store's EUR price at the store's
|
|
145
|
+
* exchange rate.
|
|
146
|
+
*
|
|
147
|
+
* A string on the wire: a newer engine may answer a source this SDK does not name yet.
|
|
148
|
+
*/
|
|
149
|
+
export type VariantPriceSourceKind = 'OWN' | 'INHERITED' | 'CONVERTED';
|
|
150
|
+
/** One currency the active store sells, and where the variant's price in it comes from. */
|
|
151
|
+
export interface VariantPriceSource {
|
|
152
|
+
currencyCode: string;
|
|
153
|
+
/**
|
|
154
|
+
* What the store charges in this currency, in minor units, on the STORE's basis: gross on a
|
|
155
|
+
* store with `pricesIncludeTax`, net otherwise — the basis a variant write takes, unlike
|
|
156
|
+
* {@link ProductVariant.price}, which reads net everywhere. Null when nothing prices it: no
|
|
157
|
+
* stored price and no EUR price or exchange rate to convert from, or a currency the store
|
|
158
|
+
* marked not for sale.
|
|
159
|
+
*/
|
|
160
|
+
price: number | null;
|
|
161
|
+
source: VariantPriceSourceKind | (string & {});
|
|
162
|
+
/**
|
|
163
|
+
* The organization's stored price in this currency, on the same basis (an organization and its
|
|
164
|
+
* stores enter prices the same way). Null when no organization shares the variant with the
|
|
165
|
+
* store, or the organization stores no price in this currency.
|
|
166
|
+
*/
|
|
167
|
+
organizationPrice: number | null;
|
|
168
|
+
/** The organization that shares the variant with the store; null for the store's own variant. */
|
|
169
|
+
organizationName: string | null;
|
|
170
|
+
}
|
|
171
|
+
/** The engine's bound on one `revertPrice` call (`MAX_REVERT_VARIANTS`). */
|
|
172
|
+
export declare const MAX_REVERT_PRICE_VARIANTS = 500;
|
|
173
|
+
export interface CreateProductVariantInput extends VariantFields {
|
|
174
|
+
productId: string;
|
|
175
|
+
sku: string;
|
|
176
|
+
translations: VariantTranslationInput[];
|
|
177
|
+
}
|
|
178
|
+
/** A PATCH: omitted fields are left alone. */
|
|
179
|
+
export interface UpdateProductVariantInput extends VariantFields {
|
|
180
|
+
id: string;
|
|
181
|
+
sku?: string;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Variant writes. Reading needs `ReadCatalog`, creating `CreateCatalog`,
|
|
185
|
+
* updating (stock included) `UpdateCatalog`, deleting `DeleteCatalog`.
|
|
186
|
+
*
|
|
187
|
+
* Refusals, in the engine's words: `optionIds` that do not name one option per
|
|
188
|
+
* group — "ProductVariant optionIds must include one optionId from each of the
|
|
189
|
+
* groups: …" — or repeat another variant's combination — "A ProductVariant with
|
|
190
|
+
* the selected options already exists: …" — and a negative stock level —
|
|
191
|
+
* "stockOnHand cannot be a negative value" — all arrive as `ValidationError`
|
|
192
|
+
* (`USER_INPUT_ERROR`). An id this store does not have is a `NotFoundError`
|
|
193
|
+
* (`ENTITY_NOT_FOUND`). A product may have ONE variant without options; a
|
|
194
|
+
* second is refused the same way.
|
|
195
|
+
*
|
|
196
|
+
* While an app or API token of the store owns a field group (`admin.tenant.fieldGroupOwners()`),
|
|
197
|
+
* an update or delete of a variant that would change its Price, Stock or
|
|
198
|
+
* Product details by anyone else is refused `FIELD_GROUP_LOCKED`
|
|
199
|
+
* (`BusinessRuleError`, 422) and nothing is written; `error.details` names each refused field
|
|
200
|
+
* (`field` is a JSON pointer into the call, e.g. `/input/0/price`). A value equal to the stored
|
|
201
|
+
* one passes and is left out of the write. See the README, "Field groups: what an owned group
|
|
202
|
+
* refuses".
|
|
203
|
+
*/
|
|
204
|
+
export interface VariantsModule {
|
|
205
|
+
list(productId: string, options?: ListOptions): Promise<Page<ProductVariant>>;
|
|
206
|
+
get(id: string, options?: Call): Promise<ProductVariant | null>;
|
|
207
|
+
/** All or nothing: the engine creates the batch in one transaction. */
|
|
208
|
+
create(inputs: CreateProductVariantInput[], options?: Call): Promise<ProductVariant[]>;
|
|
209
|
+
update(inputs: UpdateProductVariantInput[], options?: Call): Promise<ProductVariant[]>;
|
|
210
|
+
delete(ids: string[], options?: Call): Promise<void>;
|
|
211
|
+
/**
|
|
212
|
+
* Set stock on hand per stock location — absolute values, one update. A
|
|
213
|
+
* location not named keeps its level. Stock MOVEMENTS with a reason are the
|
|
214
|
+
* `stock` module's (spec §9.2), not this.
|
|
215
|
+
*/
|
|
216
|
+
setStockLevels(variantId: string, levels: StockLevelInput[], options?: Call): Promise<ProductVariant>;
|
|
217
|
+
/**
|
|
218
|
+
* Where each of the variant's prices in the ACTIVE store comes from: one entry per currency the
|
|
219
|
+
* store sells, in the store's currency order. A store outside any organization, and a variant
|
|
220
|
+
* of the store's own, read `OWN` or `CONVERTED` only. Needs `ReadCatalog`. An id this store
|
|
221
|
+
* does not have is a `NotFoundError` (`VARIANT_NOT_FOUND`).
|
|
222
|
+
*/
|
|
223
|
+
priceSources(variantId: string, options?: Call): Promise<VariantPriceSource[]>;
|
|
224
|
+
/**
|
|
225
|
+
* The active store's prices of variants its organization shares with it become the
|
|
226
|
+
* organization's stored prices again (`INHERITED`), and follow its later changes. Without
|
|
227
|
+
* `currencyCodes`: every currency the store sells and the organization prices, bar one the store
|
|
228
|
+
* marked not for sale and stores no price in (a stored price sells the currency whatever its
|
|
229
|
+
* mark, so it is reverted); an empty list reverts nothing. Needs `UpdateCatalog` (or
|
|
230
|
+
* `UpdateProduct`), what a variant price write needs. All or nothing, and audited per variant.
|
|
231
|
+
* Answers the variants as the store sees them. An Admin API mutation: an idempotency key
|
|
232
|
+
* deduplicates nothing, and a repeat answers the same prices.
|
|
233
|
+
*
|
|
234
|
+
* Refusals (nothing is written), GraphQL errors mapped by `core`:
|
|
235
|
+
* - `ORGANIZATION_PRICE_LOCAL_VARIANT` → `BusinessRuleError` (422): a variant of the store's
|
|
236
|
+
* own, or the organization does not share its catalog.
|
|
237
|
+
* - `ORGANIZATION_PRICE_MISSING` → `BusinessRuleError` (422): the organization stores no price
|
|
238
|
+
* in a currency named (or in any the store sells).
|
|
239
|
+
* - `ORGANIZATION_PRICE_NOT_REVERTIBLE` → `BusinessRuleError` (422): a named currency the store
|
|
240
|
+
* does not sell, or marked not for sale for the variant with no price stored in it.
|
|
241
|
+
* - `ORGANIZATION_WORKSPACE_NOT_A_STORE` → `BusinessRuleError` (422): called in an
|
|
242
|
+
* organization's workspace, whose prices are the organization's own.
|
|
243
|
+
* - `ORGANIZATION_PRICE_REVERT_INVALID` → `ValidationError` (400): more than
|
|
244
|
+
* {@link MAX_REVERT_PRICE_VARIANTS} distinct ids; refused before sending too.
|
|
245
|
+
* - `ENTITY_NOT_FOUND` → `NotFoundError` (404): an id this store does not have.
|
|
246
|
+
*/
|
|
247
|
+
revertPrice(variantIds: string[], options?: Call & {
|
|
248
|
+
currencyCodes?: readonly string[];
|
|
249
|
+
}): Promise<ProductVariant[]>;
|
|
250
|
+
}
|
|
251
|
+
export declare const VARIANT_FIELDS = "\n id\n productId\n createdAt\n updatedAt\n languageCode\n name\n sku\n enabled\n price\n priceWithTax\n currencyCode\n taxCategory {\n id\n name\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n }\n options {\n id\n code\n name\n groupId\n }\n featuredAsset { \n id\n name\n preview\n source\n }\n assets { \n id\n name\n preview\n source\n }\n facetValues { \n id\n code\n name\n facet {\n id\n code\n name\n }\n }\n translations {\n id\n languageCode\n name\n }\n customFields {\n taxClassKey\n availabilityState\n availableFrom\n preorderLimit\n parcelCapacity\n parcelPacking\n }\n";
|
|
252
|
+
export declare const VARIANTS_QUERY = "\n query CatalogVariants($productId: ID!, $options: ProductVariantListOptions) {\n productVariants(productId: $productId, options: $options) {\n items { \n id\n productId\n createdAt\n updatedAt\n languageCode\n name\n sku\n enabled\n price\n priceWithTax\n currencyCode\n taxCategory {\n id\n name\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n }\n options {\n id\n code\n name\n groupId\n }\n featuredAsset { \n id\n name\n preview\n source\n }\n assets { \n id\n name\n preview\n source\n }\n facetValues { \n id\n code\n name\n facet {\n id\n code\n name\n }\n }\n translations {\n id\n languageCode\n name\n }\n customFields {\n taxClassKey\n availabilityState\n availableFrom\n preorderLimit\n parcelCapacity\n parcelPacking\n }\n }\n totalItems\n }\n }\n";
|
|
253
|
+
export declare const VARIANT_QUERY = "\n query CatalogVariant($id: ID!) {\n productVariant(id: $id) { \n id\n productId\n createdAt\n updatedAt\n languageCode\n name\n sku\n enabled\n price\n priceWithTax\n currencyCode\n taxCategory {\n id\n name\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n }\n options {\n id\n code\n name\n groupId\n }\n featuredAsset { \n id\n name\n preview\n source\n }\n assets { \n id\n name\n preview\n source\n }\n facetValues { \n id\n code\n name\n facet {\n id\n code\n name\n }\n }\n translations {\n id\n languageCode\n name\n }\n customFields {\n taxClassKey\n availabilityState\n availableFrom\n preorderLimit\n parcelCapacity\n parcelPacking\n }\n }\n }\n";
|
|
254
|
+
export declare const CREATE_VARIANTS_MUTATION = "\n mutation CatalogCreateVariants($input: [CreateProductVariantInput!]!) {\n createProductVariants(input: $input) { \n id\n productId\n createdAt\n updatedAt\n languageCode\n name\n sku\n enabled\n price\n priceWithTax\n currencyCode\n taxCategory {\n id\n name\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n }\n options {\n id\n code\n name\n groupId\n }\n featuredAsset { \n id\n name\n preview\n source\n }\n assets { \n id\n name\n preview\n source\n }\n facetValues { \n id\n code\n name\n facet {\n id\n code\n name\n }\n }\n translations {\n id\n languageCode\n name\n }\n customFields {\n taxClassKey\n availabilityState\n availableFrom\n preorderLimit\n parcelCapacity\n parcelPacking\n }\n }\n }\n";
|
|
255
|
+
export declare const UPDATE_VARIANTS_MUTATION = "\n mutation CatalogUpdateVariants($input: [UpdateProductVariantInput!]!) {\n updateProductVariants(input: $input) { \n id\n productId\n createdAt\n updatedAt\n languageCode\n name\n sku\n enabled\n price\n priceWithTax\n currencyCode\n taxCategory {\n id\n name\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n }\n options {\n id\n code\n name\n groupId\n }\n featuredAsset { \n id\n name\n preview\n source\n }\n assets { \n id\n name\n preview\n source\n }\n facetValues { \n id\n code\n name\n facet {\n id\n code\n name\n }\n }\n translations {\n id\n languageCode\n name\n }\n customFields {\n taxClassKey\n availabilityState\n availableFrom\n preorderLimit\n parcelCapacity\n parcelPacking\n }\n }\n }\n";
|
|
256
|
+
export declare const DELETE_VARIANTS_MUTATION = "\n mutation CatalogDeleteVariants($ids: [ID!]!) {\n deleteProductVariants(ids: $ids) { \n result\n message\n }\n }\n";
|
|
257
|
+
/**
|
|
258
|
+
* A document of its own, never a field of `VARIANT_FIELDS`: every other variant read keeps
|
|
259
|
+
* working against an engine older than the field, and pays nothing for it.
|
|
260
|
+
*/
|
|
261
|
+
export declare const VARIANT_PRICE_SOURCES_QUERY = "\n query CatalogVariantPriceSources($id: ID!) {\n productVariant(id: $id) {\n id\n priceSources {\n currencyCode\n price\n source\n organizationPrice\n organizationName\n }\n }\n }\n";
|
|
262
|
+
export declare const REVERT_TO_ORGANIZATION_PRICE_MUTATION = "\n mutation CatalogRevertToOrganizationPrice($variantIds: [ID!]!, $currencyCodes: [CurrencyCode!]) {\n revertToOrganizationPrice(variantIds: $variantIds, currencyCodes: $currencyCodes) { \n id\n productId\n createdAt\n updatedAt\n languageCode\n name\n sku\n enabled\n price\n priceWithTax\n currencyCode\n taxCategory {\n id\n name\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n }\n options {\n id\n code\n name\n groupId\n }\n featuredAsset { \n id\n name\n preview\n source\n }\n assets { \n id\n name\n preview\n source\n }\n facetValues { \n id\n code\n name\n facet {\n id\n code\n name\n }\n }\n translations {\n id\n languageCode\n name\n }\n customFields {\n taxClassKey\n availabilityState\n availableFrom\n preorderLimit\n parcelCapacity\n parcelPacking\n }\n }\n }\n";
|
|
263
|
+
/** The price basis the store stores, read on the SAME tenant as the write. */
|
|
264
|
+
export declare const PRICE_BASIS_QUERY = "\n query CatalogPriceBasis {\n activeChannel {\n pricesIncludeTax\n }\n }\n";
|
|
265
|
+
interface WireVariant extends Omit<ProductVariant, keyof Availability<string> | 'taxClassKey' | 'parcelCapacity' | 'parcelPacking'> {
|
|
266
|
+
customFields: (Partial<Availability<VariantAvailabilityState>> & {
|
|
267
|
+
taxClassKey?: string | null;
|
|
268
|
+
parcelCapacity?: number | null;
|
|
269
|
+
parcelPacking?: string | null;
|
|
270
|
+
}) | null;
|
|
271
|
+
}
|
|
272
|
+
export declare function toVariant(wire: WireVariant): ProductVariant;
|
|
273
|
+
export declare function createVariantsModule(transport: Transport): VariantsModule;
|
|
274
|
+
export {};
|
|
@@ -0,0 +1,258 @@
|
|
|
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, ...rest } = input;
|
|
148
|
+
void priceBasis;
|
|
149
|
+
const customFields = availabilityCustomFields({
|
|
150
|
+
availabilityState,
|
|
151
|
+
availableFrom,
|
|
152
|
+
preorderLimit,
|
|
153
|
+
});
|
|
154
|
+
if (taxClassKey !== undefined)
|
|
155
|
+
customFields.taxClassKey = taxClassKey;
|
|
156
|
+
if (parcelCapacity !== undefined)
|
|
157
|
+
customFields.parcelCapacity = parcelCapacity;
|
|
158
|
+
if (parcelPacking !== undefined) {
|
|
159
|
+
customFields.parcelPacking = parcelPacking === 'shared' ? null : parcelPacking;
|
|
160
|
+
}
|
|
161
|
+
return Object.keys(customFields).length > 0 ? { ...rest, customFields } : rest;
|
|
162
|
+
}
|
|
163
|
+
function refuse(code, message) {
|
|
164
|
+
return new ValidationError({ code, message, status: 400, service: 'vendure' });
|
|
165
|
+
}
|
|
166
|
+
export function createVariantsModule(transport) {
|
|
167
|
+
/**
|
|
168
|
+
* Every priced input must state its basis, and the basis must be the store's.
|
|
169
|
+
* Read per call, with the call's own options, so a per-call tenant is checked
|
|
170
|
+
* against ITS store rather than the client's default one.
|
|
171
|
+
*/
|
|
172
|
+
const assertPriceBasis = async (inputs, options) => {
|
|
173
|
+
const priced = inputs.filter((input) => input.price !== undefined);
|
|
174
|
+
if (priced.length === 0)
|
|
175
|
+
return;
|
|
176
|
+
for (const input of priced) {
|
|
177
|
+
if (!Number.isInteger(input.price)) {
|
|
178
|
+
throw refuse('INVALID_PRICE', `price must be a whole number of minor units; got ${String(input.price)}.`);
|
|
179
|
+
}
|
|
180
|
+
if (input.priceBasis !== 'net' && input.priceBasis !== 'gross') {
|
|
181
|
+
throw refuse('PRICE_BASIS_REQUIRED', 'A variant price must state its basis: priceBasis "net" or "gross".');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const channel = await fieldOf(transport, PRICE_BASIS_QUERY, 'activeChannel', {}, options);
|
|
185
|
+
const storeBasis = channel.pricesIncludeTax ? 'gross' : 'net';
|
|
186
|
+
const wrong = priced.find((input) => input.priceBasis !== storeBasis);
|
|
187
|
+
if (wrong) {
|
|
188
|
+
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.`);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const mapAll = (rows, what) => rows.map((row) => {
|
|
192
|
+
if (row === null) {
|
|
193
|
+
// `[ProductVariant]!` allows a null item; a write that answers one has
|
|
194
|
+
// lost track of a row it just wrote, and pretending otherwise would
|
|
195
|
+
// hand the caller fewer variants than it sent.
|
|
196
|
+
throw new UpstreamError({
|
|
197
|
+
code: 'VARIANT_WRITE_UNCONFIRMED',
|
|
198
|
+
message: `The engine answered ${what} without one of the variants it wrote. Read them back with variants.list(productId) before retrying.`,
|
|
199
|
+
status: 502,
|
|
200
|
+
service: 'vendure',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return toVariant(row);
|
|
204
|
+
});
|
|
205
|
+
const update = async (inputs, options) => {
|
|
206
|
+
inputs.forEach(assertParcelRule);
|
|
207
|
+
await assertPriceBasis(inputs, options);
|
|
208
|
+
const rows = await fieldOf(transport, UPDATE_VARIANTS_MUTATION, 'updateProductVariants', { input: inputs.map((input) => toWireInput({ ...input })) }, options);
|
|
209
|
+
return mapAll(rows, 'updateProductVariants');
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
list(productId, options) {
|
|
213
|
+
return vendureListPage(transport, VARIANTS_QUERY, 'productVariants', (skip, take) => ({ productId, options: { skip, take } }), options, toVariant);
|
|
214
|
+
},
|
|
215
|
+
async get(id, options) {
|
|
216
|
+
const variant = await fieldOf(transport, VARIANT_QUERY, 'productVariant', { id }, options);
|
|
217
|
+
return variant ? toVariant(variant) : null;
|
|
218
|
+
},
|
|
219
|
+
async create(inputs, options) {
|
|
220
|
+
inputs.forEach(assertParcelRule);
|
|
221
|
+
await assertPriceBasis(inputs, options);
|
|
222
|
+
const rows = await fieldOf(transport, CREATE_VARIANTS_MUTATION, 'createProductVariants', { input: inputs.map((input) => toWireInput({ ...input })) }, options);
|
|
223
|
+
return mapAll(rows, 'createProductVariants');
|
|
224
|
+
},
|
|
225
|
+
update,
|
|
226
|
+
async delete(ids, options) {
|
|
227
|
+
assertDeleted('VARIANT_NOT_DELETED', await fieldOf(transport, DELETE_VARIANTS_MUTATION, 'deleteProductVariants', { ids }, options));
|
|
228
|
+
},
|
|
229
|
+
async setStockLevels(variantId, levels, options) {
|
|
230
|
+
const [variant] = await update([{ id: variantId, stockLevels: levels }], options);
|
|
231
|
+
return variant;
|
|
232
|
+
},
|
|
233
|
+
async priceSources(variantId, options) {
|
|
234
|
+
const variant = await fieldOf(transport, VARIANT_PRICE_SOURCES_QUERY, 'productVariant', { id: variantId }, options);
|
|
235
|
+
if (!variant) {
|
|
236
|
+
throw new NotFoundError({
|
|
237
|
+
code: 'VARIANT_NOT_FOUND',
|
|
238
|
+
message: `This store has no variant ${variantId}.`,
|
|
239
|
+
status: 404,
|
|
240
|
+
service: 'vendure',
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return variant.priceSources;
|
|
244
|
+
},
|
|
245
|
+
async revertPrice(variantIds, options) {
|
|
246
|
+
const { currencyCodes, ...call } = options ?? {};
|
|
247
|
+
// The engine counts distinct ids the way it stores them (lower case).
|
|
248
|
+
const distinct = new Set(variantIds.map((id) => id.toLowerCase())).size;
|
|
249
|
+
if (distinct > MAX_REVERT_PRICE_VARIANTS) {
|
|
250
|
+
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.`);
|
|
251
|
+
}
|
|
252
|
+
if (variantIds.length === 0)
|
|
253
|
+
return [];
|
|
254
|
+
const rows = await fieldOf(transport, REVERT_TO_ORGANIZATION_PRICE_MUTATION, 'revertToOrganizationPrice', { variantIds, currencyCodes: currencyCodes === undefined ? null : [...currencyCodes] }, call);
|
|
255
|
+
return mapAll(rows, 'revertToOrganizationPrice');
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
@@ -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
|
+
}
|