@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,160 @@
|
|
|
1
|
+
/** A return's state: the latest row of its status log. */
|
|
2
|
+
export const RETURN_STATES = [
|
|
3
|
+
'requested',
|
|
4
|
+
'approved',
|
|
5
|
+
'rejected',
|
|
6
|
+
'cancelled',
|
|
7
|
+
'items_received',
|
|
8
|
+
'settlement_pending',
|
|
9
|
+
'settlement_failed',
|
|
10
|
+
'settled',
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* How a return is settled. `refund` gives money back to an original payment.
|
|
14
|
+
* `store_credit` issues a credit note: on a store with an accounts-receivable
|
|
15
|
+
* ledger it credits the customer's account, elsewhere it credits the order's
|
|
16
|
+
* invoice and moves no money.
|
|
17
|
+
*/
|
|
18
|
+
export const RETURN_SETTLEMENT_METHODS = ['refund', 'store_credit'];
|
|
19
|
+
/** Why a return cannot be settled now, in the order the engine checks them. */
|
|
20
|
+
export const RETURN_SETTLEMENT_BLOCKERS = [
|
|
21
|
+
'OUTCOME_UNKNOWN',
|
|
22
|
+
'ALREADY_SETTLED',
|
|
23
|
+
'IN_PROGRESS',
|
|
24
|
+
'RECEIPT_REQUIRED',
|
|
25
|
+
'STATE',
|
|
26
|
+
];
|
|
27
|
+
/** The fields of an `OrderReturn` every read selects (a selection, spliced into each document). */
|
|
28
|
+
const RETURN_FIELDS = /* GraphQL */ `
|
|
29
|
+
id
|
|
30
|
+
code
|
|
31
|
+
channelId
|
|
32
|
+
orderId
|
|
33
|
+
orderCode
|
|
34
|
+
customerId
|
|
35
|
+
currencyCode
|
|
36
|
+
state
|
|
37
|
+
initiator
|
|
38
|
+
customerNote
|
|
39
|
+
adminNote
|
|
40
|
+
preferredSettlementMethod
|
|
41
|
+
settlementMethod
|
|
42
|
+
shippingRefundAmount
|
|
43
|
+
settledAmount
|
|
44
|
+
refundId
|
|
45
|
+
creditNoteId
|
|
46
|
+
creditNoteNumber
|
|
47
|
+
requestedAt
|
|
48
|
+
resolvedAt
|
|
49
|
+
createdAt
|
|
50
|
+
updatedAt
|
|
51
|
+
lines {
|
|
52
|
+
id
|
|
53
|
+
returnId
|
|
54
|
+
orderLineId
|
|
55
|
+
productVariantId
|
|
56
|
+
productVariantName
|
|
57
|
+
productVariantSku
|
|
58
|
+
requestedQty
|
|
59
|
+
approvedQty
|
|
60
|
+
receivedQty
|
|
61
|
+
restockQty
|
|
62
|
+
writeOffQty
|
|
63
|
+
restockLocationId
|
|
64
|
+
reasonCode
|
|
65
|
+
reasonNote
|
|
66
|
+
unitPriceWithTax
|
|
67
|
+
unitPriceWithTaxUnrounded
|
|
68
|
+
refundAmount
|
|
69
|
+
createdAt
|
|
70
|
+
updatedAt
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
export const RETURN_QUERY = /* GraphQL */ `
|
|
74
|
+
query SdkOrderReturn($id: ID!) {
|
|
75
|
+
orderReturn(id: $id) {
|
|
76
|
+
${RETURN_FIELDS}
|
|
77
|
+
history {
|
|
78
|
+
state
|
|
79
|
+
actor
|
|
80
|
+
actorUserId
|
|
81
|
+
reason
|
|
82
|
+
createdAt
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
export const RETURNS_FOR_ORDER_QUERY = /* GraphQL */ `
|
|
88
|
+
query SdkReturnsForOrder($orderId: ID!) {
|
|
89
|
+
returnsForOrder(orderId: $orderId) {
|
|
90
|
+
${RETURN_FIELDS}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
export const RETURN_ELIGIBILITY_QUERY = /* GraphQL */ `
|
|
95
|
+
query SdkReturnEligibility($orderId: ID!) {
|
|
96
|
+
returnEligibilityForOrder(orderId: $orderId) {
|
|
97
|
+
orderId
|
|
98
|
+
eligible
|
|
99
|
+
reason
|
|
100
|
+
returnWindowEndsAt
|
|
101
|
+
lines {
|
|
102
|
+
orderLineId
|
|
103
|
+
productVariantId
|
|
104
|
+
productVariantName
|
|
105
|
+
productVariantSku
|
|
106
|
+
fulfilledQty
|
|
107
|
+
returnableQty
|
|
108
|
+
unitPriceWithTax
|
|
109
|
+
unitPriceWithTaxUnrounded
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
export const RETURN_SETTLEMENT_OPTIONS_QUERY = /* GraphQL */ `
|
|
115
|
+
query SdkReturnSettlementOptions($returnId: ID!) {
|
|
116
|
+
returnSettlementOptions(returnId: $returnId) {
|
|
117
|
+
returnId
|
|
118
|
+
currencyCode
|
|
119
|
+
settleable
|
|
120
|
+
blockedBy
|
|
121
|
+
refund {
|
|
122
|
+
available
|
|
123
|
+
reasonCode
|
|
124
|
+
payments {
|
|
125
|
+
paymentId
|
|
126
|
+
method
|
|
127
|
+
refundable
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
creditNote {
|
|
131
|
+
available
|
|
132
|
+
ledger
|
|
133
|
+
reasonCode
|
|
134
|
+
invoiceNumber
|
|
135
|
+
}
|
|
136
|
+
initialMethod
|
|
137
|
+
defaultAmount
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
export function createReturnsModule(transport) {
|
|
142
|
+
return {
|
|
143
|
+
async get(id, options) {
|
|
144
|
+
const data = await transport.graphql({ api: 'admin', query: RETURN_QUERY, variables: { id } }, options);
|
|
145
|
+
return data.orderReturn;
|
|
146
|
+
},
|
|
147
|
+
async forOrder(orderId, options) {
|
|
148
|
+
const data = await transport.graphql({ api: 'admin', query: RETURNS_FOR_ORDER_QUERY, variables: { orderId } }, options);
|
|
149
|
+
return data.returnsForOrder;
|
|
150
|
+
},
|
|
151
|
+
async eligibility(orderId, options) {
|
|
152
|
+
const data = await transport.graphql({ api: 'admin', query: RETURN_ELIGIBILITY_QUERY, variables: { orderId } }, options);
|
|
153
|
+
return data.returnEligibilityForOrder;
|
|
154
|
+
},
|
|
155
|
+
async settlementOptions(returnId, options) {
|
|
156
|
+
const data = await transport.graphql({ api: 'admin', query: RETURN_SETTLEMENT_OPTIONS_QUERY, variables: { returnId } }, options);
|
|
157
|
+
return data.returnSettlementOptions;
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/** What `admin.search.entities` searches. */
|
|
4
|
+
export type SearchEntityType = 'PRODUCTS' | 'ORDERS' | 'CUSTOMERS' | 'CONTENT';
|
|
5
|
+
/**
|
|
6
|
+
* Why a type was not searched. A string on the wire: a newer engine may add a reason, so read an
|
|
7
|
+
* unknown one as "not searched".
|
|
8
|
+
*
|
|
9
|
+
* - `PERMISSION`: the credential holds none of the type's read permissions in the store
|
|
10
|
+
* (`ReadCatalog` or `ReadProduct` for products, `ReadOrder` for orders, `ReadCustomer` for
|
|
11
|
+
* customers).
|
|
12
|
+
* - `CUSTOMER_DATA_NOT_GRANTED`: the credential reads customer data below `full` (an app installed
|
|
13
|
+
* at `none` or `identity`). Orders and customers are matched on customers' names, email
|
|
14
|
+
* addresses and companies, so they are not searched for it.
|
|
15
|
+
* - `UNAVAILABLE`: the search service, or for content the content system, did not answer. Try
|
|
16
|
+
* again shortly.
|
|
17
|
+
*/
|
|
18
|
+
export type SearchSkipReason = 'PERMISSION' | 'CUSTOMER_DATA_NOT_GRANTED' | 'UNAVAILABLE' | (string & {});
|
|
19
|
+
export interface SearchProductHit {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
slug: string;
|
|
23
|
+
/** The featured image's preview URL; null when the product has none. */
|
|
24
|
+
previewUrl: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface SearchOrderHit {
|
|
27
|
+
id: string;
|
|
28
|
+
code: string;
|
|
29
|
+
state: string;
|
|
30
|
+
/** ISO-8601; null for an order not placed yet. */
|
|
31
|
+
orderPlacedAt: string | null;
|
|
32
|
+
/** Minor units. */
|
|
33
|
+
totalWithTax: number;
|
|
34
|
+
currencyCode: string;
|
|
35
|
+
/** The order's lifecycle (`open`, `completed`, …); null until the engine sets it. */
|
|
36
|
+
lifecycleStatus: string | null;
|
|
37
|
+
}
|
|
38
|
+
export interface SearchCustomerHit {
|
|
39
|
+
id: string;
|
|
40
|
+
firstName: string;
|
|
41
|
+
lastName: string;
|
|
42
|
+
emailAddress: string;
|
|
43
|
+
/** The company of the default billing address, else the default shipping one, else any. */
|
|
44
|
+
company: string | null;
|
|
45
|
+
}
|
|
46
|
+
/** A content item of the store's content system. */
|
|
47
|
+
export interface SearchContentHit {
|
|
48
|
+
id: string;
|
|
49
|
+
collectionId: string;
|
|
50
|
+
collectionSlug: string;
|
|
51
|
+
slug: string | null;
|
|
52
|
+
/** `draft`, `published` or `archived`. */
|
|
53
|
+
status: string;
|
|
54
|
+
title: string | null;
|
|
55
|
+
}
|
|
56
|
+
export interface SearchSkip {
|
|
57
|
+
type: SearchEntityType | (string & {});
|
|
58
|
+
reason: SearchSkipReason;
|
|
59
|
+
}
|
|
60
|
+
/** Each list holds the type's best matches, most relevant first. */
|
|
61
|
+
export interface EntitySearchResult {
|
|
62
|
+
products: SearchProductHit[];
|
|
63
|
+
orders: SearchOrderHit[];
|
|
64
|
+
customers: SearchCustomerHit[];
|
|
65
|
+
content: SearchContentHit[];
|
|
66
|
+
/** The types that were not searched, and why; their lists are empty. */
|
|
67
|
+
skipped: SearchSkip[];
|
|
68
|
+
}
|
|
69
|
+
export interface SearchEntitiesOptions extends Call {
|
|
70
|
+
/** The types to search; all four when omitted. An empty list is refused. */
|
|
71
|
+
types?: readonly SearchEntityType[];
|
|
72
|
+
/** Results per type, from 1 to {@link SEARCH_ENTITIES_MAX_TAKE}; 5 when omitted. */
|
|
73
|
+
take?: number;
|
|
74
|
+
}
|
|
75
|
+
/** The engine's bounds, as `searchAcrossEntities` documents them. */
|
|
76
|
+
export declare const SEARCH_ENTITIES_MIN_TERM_LENGTH = 2;
|
|
77
|
+
export declare const SEARCH_ENTITIES_MAX_TAKE = 10;
|
|
78
|
+
export declare const SEARCH_ACROSS_ENTITIES_QUERY = "\n query SearchAcrossEntities($input: CommandSearchInput!) {\n searchAcrossEntities(input: $input) {\n products {\n id\n name\n slug\n previewUrl\n }\n orders {\n id\n code\n state\n orderPlacedAt\n totalWithTax\n currencyCode\n lifecycleStatus\n }\n customers {\n id\n firstName\n lastName\n emailAddress\n company\n }\n content {\n id\n collectionId\n collectionSlug\n slug\n status\n title\n }\n skipped {\n type\n reason\n }\n }\n }\n";
|
|
79
|
+
/**
|
|
80
|
+
* One search across the store (spec §7 `search.entities`): products, orders, customers and
|
|
81
|
+
* content items matching a term, in one call.
|
|
82
|
+
*
|
|
83
|
+
* - **Relevance order.** Each list is that type's best matches, most relevant first, every one
|
|
84
|
+
* already loaded in the store you act on; a match the store no longer holds is left out, so a
|
|
85
|
+
* list can be shorter than `take`.
|
|
86
|
+
* - **A type you may not read is skipped and named**, never a refusal: its list is empty and
|
|
87
|
+
* `skipped` says why (see {@link SearchSkipReason}).
|
|
88
|
+
* - **Content may be missing when the content system is down**: the other types still answer,
|
|
89
|
+
* and content is skipped `UNAVAILABLE`.
|
|
90
|
+
* - **Content** is published items, plus drafts and archived items for a `ReadCmsContent` holder.
|
|
91
|
+
*
|
|
92
|
+
* Refused before anything is searched: `SEARCH_INPUT_INVALID` (`ValidationError`, 400) for a term
|
|
93
|
+
* under {@link SEARCH_ENTITIES_MIN_TERM_LENGTH} or over 500 characters once trimmed, a `take`
|
|
94
|
+
* outside 1..{@link SEARCH_ENTITIES_MAX_TAKE}, or an empty `types` list.
|
|
95
|
+
*/
|
|
96
|
+
export interface SearchModule {
|
|
97
|
+
entities(term: string, options?: SearchEntitiesOptions): Promise<EntitySearchResult>;
|
|
98
|
+
}
|
|
99
|
+
export declare function createSearchModule(transport: Transport): SearchModule;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** The engine's bounds, as `searchAcrossEntities` documents them. */
|
|
2
|
+
export const SEARCH_ENTITIES_MIN_TERM_LENGTH = 2;
|
|
3
|
+
export const SEARCH_ENTITIES_MAX_TAKE = 10;
|
|
4
|
+
export const SEARCH_ACROSS_ENTITIES_QUERY = /* GraphQL */ `
|
|
5
|
+
query SearchAcrossEntities($input: CommandSearchInput!) {
|
|
6
|
+
searchAcrossEntities(input: $input) {
|
|
7
|
+
products {
|
|
8
|
+
id
|
|
9
|
+
name
|
|
10
|
+
slug
|
|
11
|
+
previewUrl
|
|
12
|
+
}
|
|
13
|
+
orders {
|
|
14
|
+
id
|
|
15
|
+
code
|
|
16
|
+
state
|
|
17
|
+
orderPlacedAt
|
|
18
|
+
totalWithTax
|
|
19
|
+
currencyCode
|
|
20
|
+
lifecycleStatus
|
|
21
|
+
}
|
|
22
|
+
customers {
|
|
23
|
+
id
|
|
24
|
+
firstName
|
|
25
|
+
lastName
|
|
26
|
+
emailAddress
|
|
27
|
+
company
|
|
28
|
+
}
|
|
29
|
+
content {
|
|
30
|
+
id
|
|
31
|
+
collectionId
|
|
32
|
+
collectionSlug
|
|
33
|
+
slug
|
|
34
|
+
status
|
|
35
|
+
title
|
|
36
|
+
}
|
|
37
|
+
skipped {
|
|
38
|
+
type
|
|
39
|
+
reason
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
export function createSearchModule(transport) {
|
|
45
|
+
return {
|
|
46
|
+
async entities(term, options = {}) {
|
|
47
|
+
const { types, take, ...call } = options;
|
|
48
|
+
const data = await transport.graphql({
|
|
49
|
+
api: 'admin',
|
|
50
|
+
query: SEARCH_ACROSS_ENTITIES_QUERY,
|
|
51
|
+
variables: {
|
|
52
|
+
input: {
|
|
53
|
+
term,
|
|
54
|
+
...(types === undefined ? {} : { types: [...types] }),
|
|
55
|
+
...(take === undefined ? {} : { take }),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}, call);
|
|
59
|
+
return data.searchAcrossEntities;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
export declare const PARCEL_SUGGESTION_MAX_ITEMS = 500;
|
|
4
|
+
export declare const PARCEL_SUGGESTION_MAX_QUANTITY = 100000;
|
|
5
|
+
export interface ParcelCountSuggestionItem {
|
|
6
|
+
productVariantId: string;
|
|
7
|
+
/** Whole units, 1..100,000. Items of one variant are summed by the engine. */
|
|
8
|
+
quantity: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ParcelCountSuggestionInput {
|
|
11
|
+
/** 1..500 items, from any number of the active channel's orders. */
|
|
12
|
+
items: ParcelCountSuggestionItem[];
|
|
13
|
+
/** ISO 3166-1 alpha-2. Only sets `destinationRequiresCustoms`; the count ignores it. */
|
|
14
|
+
destinationCountryCode?: string | null;
|
|
15
|
+
}
|
|
16
|
+
/** The active channel's parcel-count rule applied to the items. */
|
|
17
|
+
export interface ParcelCountSuggestion {
|
|
18
|
+
/** 1..50. */
|
|
19
|
+
count: number;
|
|
20
|
+
/** The unclamped count; 0 when nothing was counted. */
|
|
21
|
+
raw: number;
|
|
22
|
+
/** False only when no rule applied (then `count` is 1 and `reason` is `NO_PARCEL_RULE`). */
|
|
23
|
+
resolvable: boolean;
|
|
24
|
+
/** Every counted item had all the data the rule asks for. */
|
|
25
|
+
exact: boolean;
|
|
26
|
+
/** `raw` exceeded 50. */
|
|
27
|
+
capped: boolean;
|
|
28
|
+
/** The channel's items per parcel, when set. */
|
|
29
|
+
capacity: number | null;
|
|
30
|
+
/** The channel's maximum parcel weight in grams, when set. */
|
|
31
|
+
maxWeightGrams: number | null;
|
|
32
|
+
/** Variants counted without a capacity: `count` is a lower bound. */
|
|
33
|
+
unratedLines: number;
|
|
34
|
+
/** Variants counted without a package weight under a maximum: `count` is a lower bound. */
|
|
35
|
+
unweighedLines: number;
|
|
36
|
+
/** `NO_PARCEL_RULE`, or null. A code: the caller owns the wording, and may meet new codes. */
|
|
37
|
+
reason: string | null;
|
|
38
|
+
/** `UNIT_OVER_MAX_WEIGHT` and/or `CAPPED`; may gain codes. */
|
|
39
|
+
warnings: string[];
|
|
40
|
+
/** Whether a parcel there needs a customs declaration; null without a destination. */
|
|
41
|
+
destinationRequiresCustoms: boolean | null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* `admin.shipping.parcelCountSuggestion`: how many parcels the active
|
|
45
|
+
* channel's rule gives a set of variant + quantity items. Needs `ReadCatalog`
|
|
46
|
+
* or `ReadFulfillment`. A malformed input is refused before any request with
|
|
47
|
+
* `PARCEL_COUNT_INPUT_INVALID` ({@link ValidationError}); an unknown variant,
|
|
48
|
+
* or another store's, is `PARCEL_COUNT_VARIANT_NOT_FOUND` (`NotFoundError`).
|
|
49
|
+
*/
|
|
50
|
+
export type ParcelCountSuggestionMethod = (input: ParcelCountSuggestionInput, options?: Call) => Promise<ParcelCountSuggestion>;
|
|
51
|
+
export declare const PARCEL_COUNT_SUGGESTION_QUERY = "\n query ParcelCountSuggestion($input: ParcelCountSuggestionInput!) {\n parcelCountSuggestion(input: $input) {\n count\n raw\n resolvable\n exact\n capped\n capacity\n maxWeightGrams\n unratedLines\n unweighedLines\n reason\n warnings\n destinationRequiresCustoms\n }\n }\n";
|
|
52
|
+
export declare function createParcelCountSuggestion(transport: Transport): ParcelCountSuggestionMethod;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ValidationError } from '@sixtyninecommerce/core';
|
|
2
|
+
// Mirror the engine's bounds (`services/vendure/src/parcel-count/`), which are
|
|
3
|
+
// the authority; these only refuse a bad input before it leaves the process.
|
|
4
|
+
export const PARCEL_SUGGESTION_MAX_ITEMS = 500;
|
|
5
|
+
export const PARCEL_SUGGESTION_MAX_QUANTITY = 100_000;
|
|
6
|
+
export const PARCEL_COUNT_SUGGESTION_QUERY = /* GraphQL */ `
|
|
7
|
+
query ParcelCountSuggestion($input: ParcelCountSuggestionInput!) {
|
|
8
|
+
parcelCountSuggestion(input: $input) {
|
|
9
|
+
count
|
|
10
|
+
raw
|
|
11
|
+
resolvable
|
|
12
|
+
exact
|
|
13
|
+
capped
|
|
14
|
+
capacity
|
|
15
|
+
maxWeightGrams
|
|
16
|
+
unratedLines
|
|
17
|
+
unweighedLines
|
|
18
|
+
reason
|
|
19
|
+
warnings
|
|
20
|
+
destinationRequiresCustoms
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
function invalid(field, message) {
|
|
25
|
+
return new ValidationError({
|
|
26
|
+
code: 'PARCEL_COUNT_INPUT_INVALID',
|
|
27
|
+
message,
|
|
28
|
+
status: 400,
|
|
29
|
+
service: 'vendure',
|
|
30
|
+
details: [{ field, code: 'PARCEL_COUNT_INPUT_INVALID', message }],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function assertInput(input) {
|
|
34
|
+
const items = input?.items;
|
|
35
|
+
if (!Array.isArray(items) || items.length < 1 || items.length > PARCEL_SUGGESTION_MAX_ITEMS) {
|
|
36
|
+
throw invalid('items', `Pass between 1 and ${PARCEL_SUGGESTION_MAX_ITEMS} items.`);
|
|
37
|
+
}
|
|
38
|
+
items.forEach((item, index) => {
|
|
39
|
+
if (typeof item?.productVariantId !== 'string' || item.productVariantId === '') {
|
|
40
|
+
throw invalid(`items[${index}].productVariantId`, 'productVariantId is required.');
|
|
41
|
+
}
|
|
42
|
+
const quantity = item.quantity;
|
|
43
|
+
if (typeof quantity !== 'number' ||
|
|
44
|
+
!Number.isInteger(quantity) ||
|
|
45
|
+
quantity < 1 ||
|
|
46
|
+
quantity > PARCEL_SUGGESTION_MAX_QUANTITY) {
|
|
47
|
+
throw invalid(`items[${index}].quantity`, `quantity must be a whole number from 1 to ${PARCEL_SUGGESTION_MAX_QUANTITY.toLocaleString('en')}; got ${JSON.stringify(quantity)}.`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
const country = input.destinationCountryCode;
|
|
51
|
+
if (country !== undefined && country !== null && !/^[A-Za-z]{2}$/.test(country.trim())) {
|
|
52
|
+
throw invalid('destinationCountryCode', 'destinationCountryCode must be an ISO 3166-1 alpha-2 code, such as NL.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function createParcelCountSuggestion(transport) {
|
|
56
|
+
return async (input, options) => {
|
|
57
|
+
assertInput(input);
|
|
58
|
+
const data = await transport.graphql({
|
|
59
|
+
api: 'admin',
|
|
60
|
+
query: PARCEL_COUNT_SUGGESTION_QUERY,
|
|
61
|
+
variables: {
|
|
62
|
+
input: {
|
|
63
|
+
items: input.items.map(({ productVariantId, quantity }) => ({
|
|
64
|
+
productVariantId,
|
|
65
|
+
quantity,
|
|
66
|
+
})),
|
|
67
|
+
destinationCountryCode: input.destinationCountryCode ?? null,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}, options);
|
|
71
|
+
return data.parcelCountSuggestion;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
export declare const PARCEL_CAPACITY_MAX = 10000;
|
|
4
|
+
export declare const PARCEL_WEIGHT_GRAMS_MAX = 70000;
|
|
5
|
+
/** How the active channel counts parcels. `null` means "not set". */
|
|
6
|
+
export interface ParcelRule {
|
|
7
|
+
channelId: string;
|
|
8
|
+
/**
|
|
9
|
+
* Units that fill one parcel unless a variant sets its own. `null` is no
|
|
10
|
+
* rule: every order counts as one parcel.
|
|
11
|
+
*/
|
|
12
|
+
defaultParcelCapacity: number | null;
|
|
13
|
+
/** Extra lower bound from known package weights, in grams. `null` is no limit. */
|
|
14
|
+
maxParcelWeightGrams: number | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A PATCH: a key left out is unchanged, `null` clears it. At least one key is
|
|
18
|
+
* required.
|
|
19
|
+
*/
|
|
20
|
+
export interface SetParcelRuleInput {
|
|
21
|
+
/** Integer 1..10,000, or `null` to remove the rule. */
|
|
22
|
+
defaultParcelCapacity?: number | null;
|
|
23
|
+
/** Integer grams 1..70,000, or `null` to remove the limit. */
|
|
24
|
+
maxParcelWeightGrams?: number | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The ACTIVE channel's parcel-count rule (`admin.shipping.parcelRule`),
|
|
28
|
+
* stored as Channel custom fields and written through core `updateChannel`.
|
|
29
|
+
*
|
|
30
|
+
* Reading needs an authenticated administrator on the channel; writing needs
|
|
31
|
+
* `UpdateChannel`. A value out of range is refused here with
|
|
32
|
+
* `PARCEL_RULE_INVALID` ({@link ValidationError}) before any request, and by
|
|
33
|
+
* the engine's own custom-field validation for any other writer.
|
|
34
|
+
*
|
|
35
|
+
* The engine's storefront pricing reads the rule from its channel cache, so a
|
|
36
|
+
* change reaches another process after that cache refreshes.
|
|
37
|
+
*/
|
|
38
|
+
export interface ParcelRuleModule {
|
|
39
|
+
get(options?: Call): Promise<ParcelRule>;
|
|
40
|
+
set(input: SetParcelRuleInput, options?: Call): Promise<ParcelRule>;
|
|
41
|
+
}
|
|
42
|
+
/** `admin.shipping` (spec §9.2 `shipping`). */
|
|
43
|
+
export interface ShippingModule {
|
|
44
|
+
parcelRule: ParcelRuleModule;
|
|
45
|
+
}
|
|
46
|
+
export declare const PARCEL_RULE_QUERY = "\n query ParcelRule {\n activeChannel { \n id\n customFields {\n defaultParcelCapacity\n maxParcelWeightGrams\n }\n }\n }\n";
|
|
47
|
+
export declare const SET_PARCEL_RULE_MUTATION = "\n mutation SetParcelRule($input: UpdateChannelInput!) {\n updateChannel(input: $input) {\n ... on Channel { \n id\n customFields {\n defaultParcelCapacity\n maxParcelWeightGrams\n }\n }\n ... on ErrorResult { errorCode message }\n }\n }\n";
|
|
48
|
+
export declare function createShippingModule(transport: Transport): ShippingModule;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { errorFromErrorResult, isErrorResult, ValidationError, } from '@sixtyninecommerce/core';
|
|
2
|
+
// Mirrors `MAX_PARCEL_CAPACITY` / `MAX_PARCEL_WEIGHT_GRAMS` in
|
|
3
|
+
// `vendure-plugin-shipping-calculator/src/parcels/parcel-rule-fields.ts`. The
|
|
4
|
+
// engine's custom-field `min`/`max` is the authority; these only refuse a bad
|
|
5
|
+
// value before it leaves the process. Change both together.
|
|
6
|
+
export const PARCEL_CAPACITY_MAX = 10_000;
|
|
7
|
+
export const PARCEL_WEIGHT_GRAMS_MAX = 70_000;
|
|
8
|
+
const RULE_FIELDS = /* GraphQL */ `
|
|
9
|
+
id
|
|
10
|
+
customFields {
|
|
11
|
+
defaultParcelCapacity
|
|
12
|
+
maxParcelWeightGrams
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
export const PARCEL_RULE_QUERY = /* GraphQL */ `
|
|
16
|
+
query ParcelRule {
|
|
17
|
+
activeChannel { ${RULE_FIELDS} }
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
export const SET_PARCEL_RULE_MUTATION = /* GraphQL */ `
|
|
21
|
+
mutation SetParcelRule($input: UpdateChannelInput!) {
|
|
22
|
+
updateChannel(input: $input) {
|
|
23
|
+
... on Channel { ${RULE_FIELDS} }
|
|
24
|
+
... on ErrorResult { errorCode message }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
function toRule(channel) {
|
|
29
|
+
return {
|
|
30
|
+
channelId: channel.id,
|
|
31
|
+
defaultParcelCapacity: channel.customFields?.defaultParcelCapacity ?? null,
|
|
32
|
+
maxParcelWeightGrams: channel.customFields?.maxParcelWeightGrams ?? null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function invalid(field, message) {
|
|
36
|
+
return new ValidationError({
|
|
37
|
+
code: 'PARCEL_RULE_INVALID',
|
|
38
|
+
message,
|
|
39
|
+
status: 400,
|
|
40
|
+
service: 'vendure',
|
|
41
|
+
details: [{ field, code: 'PARCEL_RULE_INVALID', message }],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function assertBound(field, value, max, unit) {
|
|
45
|
+
if (value === undefined || value === null)
|
|
46
|
+
return;
|
|
47
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= max)
|
|
48
|
+
return;
|
|
49
|
+
throw invalid(field, `${field} must be a whole number of ${unit} from 1 to ${max.toLocaleString('en')}, or null to clear it; got ${JSON.stringify(value)}.`);
|
|
50
|
+
}
|
|
51
|
+
function customFieldsOf(input) {
|
|
52
|
+
const fields = {};
|
|
53
|
+
if (input.defaultParcelCapacity !== undefined) {
|
|
54
|
+
fields.defaultParcelCapacity = input.defaultParcelCapacity;
|
|
55
|
+
}
|
|
56
|
+
if (input.maxParcelWeightGrams !== undefined) {
|
|
57
|
+
fields.maxParcelWeightGrams = input.maxParcelWeightGrams;
|
|
58
|
+
}
|
|
59
|
+
return fields;
|
|
60
|
+
}
|
|
61
|
+
export function createShippingModule(transport) {
|
|
62
|
+
const get = async (options) => {
|
|
63
|
+
const data = await transport.graphql({ api: 'admin', query: PARCEL_RULE_QUERY }, options);
|
|
64
|
+
return toRule(data.activeChannel);
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
parcelRule: {
|
|
68
|
+
get,
|
|
69
|
+
async set(input, options) {
|
|
70
|
+
assertBound('defaultParcelCapacity', input?.defaultParcelCapacity, PARCEL_CAPACITY_MAX, 'items');
|
|
71
|
+
assertBound('maxParcelWeightGrams', input?.maxParcelWeightGrams, PARCEL_WEIGHT_GRAMS_MAX, 'grams');
|
|
72
|
+
const customFields = customFieldsOf(input ?? {});
|
|
73
|
+
if (Object.keys(customFields).length === 0) {
|
|
74
|
+
throw invalid('input', 'Pass defaultParcelCapacity and/or maxParcelWeightGrams; an empty input changes nothing.');
|
|
75
|
+
}
|
|
76
|
+
// `updateChannel` needs the id, and the active channel is the one the
|
|
77
|
+
// client is scoped to — read it with the SAME call options, so a
|
|
78
|
+
// per-call tenant writes its own channel rather than the client's.
|
|
79
|
+
const { channelId } = await get(options);
|
|
80
|
+
const data = await transport.graphql({
|
|
81
|
+
api: 'admin',
|
|
82
|
+
query: SET_PARCEL_RULE_MUTATION,
|
|
83
|
+
variables: { input: { id: channelId, customFields } },
|
|
84
|
+
}, options);
|
|
85
|
+
if (isErrorResult(data.updateChannel))
|
|
86
|
+
throw errorFromErrorResult(data.updateChannel);
|
|
87
|
+
return toRule(data.updateChannel);
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|