@sixtyninecommerce/admin 0.10.0 → 0.12.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 +5772 -118
- package/dist/auth/index.d.ts +7 -1
- package/dist/auth/index.js +8 -1
- package/dist/catalog/assets.d.ts +120 -8
- package/dist/catalog/assets.js +98 -12
- package/dist/catalog/collections.d.ts +279 -14
- package/dist/catalog/collections.js +246 -15
- package/dist/catalog/documents.d.ts +142 -0
- package/dist/catalog/documents.js +114 -0
- package/dist/catalog/index.d.ts +54 -6
- package/dist/catalog/index.js +10 -1
- package/dist/catalog/metadata.d.ts +244 -0
- package/dist/catalog/metadata.js +220 -0
- package/dist/catalog/products.d.ts +159 -19
- package/dist/catalog/products.js +88 -13
- package/dist/catalog/properties.d.ts +140 -14
- package/dist/catalog/properties.js +65 -1
- package/dist/catalog/recommendations.d.ts +288 -0
- package/dist/catalog/recommendations.js +237 -0
- package/dist/catalog/reviews.d.ts +381 -0
- package/dist/catalog/reviews.js +325 -0
- package/dist/catalog/types.d.ts +17 -1
- package/dist/catalog/types.js +10 -0
- package/dist/catalog/variant-find.d.ts +136 -0
- package/dist/catalog/variant-find.js +239 -0
- package/dist/catalog/variants.d.ts +293 -26
- package/dist/catalog/variants.js +164 -7
- package/dist/catalog/visibility.d.ts +38 -7
- package/dist/catalog/visibility.js +19 -1
- package/dist/client.d.ts +121 -9
- package/dist/client.js +53 -14
- package/dist/content/assets.d.ts +285 -0
- package/dist/content/assets.js +181 -0
- package/dist/content/forms.d.ts +332 -1
- package/dist/content/forms.js +140 -4
- package/dist/content/items.d.ts +292 -0
- package/dist/content/items.js +170 -0
- package/dist/content/menus.d.ts +254 -0
- package/dist/content/menus.js +70 -0
- package/dist/content/strings.d.ts +137 -0
- package/dist/content/strings.js +82 -0
- package/dist/content/types.d.ts +282 -0
- package/dist/content/types.js +100 -0
- package/dist/customers/age-verification.d.ts +66 -0
- package/dist/customers/age-verification.js +79 -0
- package/dist/customers/credit.d.ts +263 -0
- package/dist/customers/credit.js +244 -0
- package/dist/customers/delivery-preferences.d.ts +120 -0
- package/dist/customers/delivery-preferences.js +97 -0
- package/dist/customers/delivery-terms.d.ts +44 -0
- package/dist/customers/delivery-terms.js +32 -0
- package/dist/customers/groups.d.ts +207 -0
- package/dist/customers/groups.js +438 -0
- package/dist/customers/index.d.ts +462 -37
- package/dist/customers/index.js +317 -40
- package/dist/customers/invitation-status.d.ts +57 -0
- package/dist/customers/invitation-status.js +21 -0
- package/dist/customers/notes.d.ts +67 -0
- package/dist/customers/notes.js +51 -0
- package/dist/customers/notification-recipients.d.ts +97 -0
- package/dist/customers/notification-recipients.js +84 -0
- package/dist/customers/saved-lists.d.ts +42 -0
- package/dist/customers/saved-lists.js +61 -0
- package/dist/customers/vat.d.ts +83 -0
- package/dist/customers/vat.js +46 -0
- package/dist/customers/wire.d.ts +20 -0
- package/dist/customers/wire.js +35 -0
- package/dist/documents/ar.d.ts +307 -0
- package/dist/documents/ar.js +254 -0
- package/dist/documents/emails.d.ts +150 -0
- package/dist/documents/emails.js +220 -0
- package/dist/documents/index.d.ts +744 -21
- package/dist/documents/index.js +438 -24
- package/dist/documents/pdf.d.ts +2 -0
- package/dist/documents/pdf.js +11 -0
- package/dist/fulfillments/create.d.ts +167 -0
- package/dist/fulfillments/create.js +216 -0
- package/dist/fulfillments/index.d.ts +163 -11
- package/dist/fulfillments/index.js +135 -6
- package/dist/fulfillments/payment-policy.d.ts +64 -0
- package/dist/fulfillments/payment-policy.js +140 -0
- package/dist/fulfillments/warehouse.d.ts +724 -0
- package/dist/fulfillments/warehouse.js +914 -0
- package/dist/geo/index.d.ts +119 -0
- package/dist/geo/index.js +107 -0
- package/dist/index.d.ts +147 -39
- package/dist/index.js +51 -12
- package/dist/integrations/afas-actions.d.ts +234 -0
- package/dist/integrations/afas-actions.js +310 -0
- package/dist/integrations/afas-types.d.ts +483 -0
- package/dist/integrations/afas-types.js +5 -0
- package/dist/integrations/afas.d.ts +234 -0
- package/dist/integrations/afas.js +290 -0
- package/dist/integrations/connections.d.ts +103 -0
- package/dist/integrations/connections.js +55 -0
- package/dist/integrations/esp.d.ts +313 -0
- package/dist/integrations/esp.js +169 -0
- package/dist/integrations/exact-online-types.d.ts +965 -0
- package/dist/integrations/exact-online-types.js +6 -0
- package/dist/integrations/exact-online.d.ts +784 -0
- package/dist/integrations/exact-online.js +1089 -0
- package/dist/integrations/index.d.ts +44 -3
- package/dist/integrations/index.js +23 -3
- package/dist/integrations/pipeline-discoveries.d.ts +65 -0
- package/dist/integrations/pipeline-discoveries.js +65 -0
- package/dist/integrations/pipeline-keys.d.ts +13 -0
- package/dist/integrations/pipeline-keys.js +22 -0
- package/dist/integrations/pipeline-runs.d.ts +119 -0
- package/dist/integrations/pipeline-runs.js +87 -0
- package/dist/integrations/pipelines.d.ts +87 -15
- package/dist/integrations/pipelines.js +41 -22
- package/dist/integrations/price-import.d.ts +223 -0
- package/dist/integrations/price-import.js +124 -0
- package/dist/integrations/types.d.ts +367 -3
- package/dist/integrations/types.js +4 -3
- package/dist/locations/index.d.ts +66 -0
- package/dist/locations/index.js +31 -0
- package/dist/meta/index.d.ts +172 -0
- package/dist/metafields/index.d.ts +8 -1
- package/dist/metafields/index.js +10 -0
- package/dist/orders/cancel-with-money.d.ts +237 -0
- package/dist/orders/cancel-with-money.js +155 -0
- package/dist/orders/commit-edit.d.ts +23 -1
- package/dist/orders/commit-edit.js +19 -2
- package/dist/orders/deposit-policy.d.ts +3 -1
- package/dist/orders/drafts.d.ts +800 -0
- package/dist/orders/drafts.js +771 -0
- package/dist/orders/fields.d.ts +245 -0
- package/dist/orders/fields.js +188 -0
- package/dist/orders/get.d.ts +29 -19
- package/dist/orders/get.js +17 -8
- package/dist/orders/index.d.ts +66 -15
- package/dist/orders/index.js +25 -2
- package/dist/orders/lifecycle.d.ts +128 -0
- package/dist/orders/lifecycle.js +158 -0
- package/dist/orders/line-price-preview.d.ts +7 -2
- package/dist/orders/list.d.ts +243 -0
- package/dist/orders/list.js +487 -0
- package/dist/orders/manual-discounts.d.ts +13 -5
- package/dist/orders/manual-pricing.d.ts +5 -0
- package/dist/orders/money-view.d.ts +149 -0
- package/dist/orders/money-view.js +99 -0
- package/dist/orders/order-entry.d.ts +20 -7
- package/dist/orders/plan-edit.d.ts +34 -2
- package/dist/orders/plan-edit.js +112 -11
- package/dist/orders/settings.d.ts +201 -0
- package/dist/orders/settings.js +325 -0
- package/dist/orders/status.d.ts +159 -13
- package/dist/orders/status.js +21 -0
- package/dist/orders/surcharges.d.ts +20 -1
- package/dist/orders/surcharges.js +37 -11
- package/dist/payments/account-credit.d.ts +46 -0
- package/dist/payments/account-credit.js +42 -0
- package/dist/payments/ar.d.ts +292 -0
- package/dist/payments/ar.js +223 -0
- package/dist/payments/connected-account.d.ts +47 -0
- package/dist/payments/connected-account.js +61 -0
- package/dist/payments/credit-noted-fields.d.ts +19 -0
- package/dist/payments/credit-noted-fields.js +69 -0
- package/dist/payments/customer-consent.d.ts +30 -0
- package/dist/payments/customer-consent.js +49 -0
- package/dist/payments/index.d.ts +340 -19
- package/dist/payments/index.js +151 -1
- package/dist/payments/methods.d.ts +303 -8
- package/dist/payments/methods.js +337 -7
- package/dist/payments/mollie-methods.d.ts +159 -0
- package/dist/payments/mollie-methods.js +141 -0
- package/dist/payments/payment-links.d.ts +180 -0
- package/dist/payments/payment-links.js +97 -0
- package/dist/payments/provider-keys.d.ts +150 -0
- package/dist/payments/provider-keys.js +118 -0
- package/dist/payments/providers.d.ts +86 -11
- package/dist/payments/providers.js +29 -0
- package/dist/payments/requests.d.ts +147 -0
- package/dist/payments/requests.js +93 -0
- package/dist/payments/store-credit-fields.d.ts +15 -0
- package/dist/payments/store-credit-fields.js +51 -0
- package/dist/pickups/index.d.ts +106 -0
- package/dist/pickups/index.js +108 -0
- package/dist/platform/channels.d.ts +132 -0
- package/dist/platform/channels.js +128 -0
- package/dist/platform/email-gateways.d.ts +165 -0
- package/dist/platform/email-gateways.js +78 -0
- package/dist/platform/geo.d.ts +123 -0
- package/dist/platform/geo.js +144 -0
- package/dist/platform/tax.d.ts +162 -0
- package/dist/platform/tax.js +169 -0
- package/dist/pos/anomalies.d.ts +158 -0
- package/dist/pos/anomalies.js +64 -0
- package/dist/pos/assisted-selling.d.ts +79 -0
- package/dist/pos/assisted-selling.js +38 -0
- package/dist/pos/exchanges.d.ts +108 -0
- package/dist/pos/exchanges.js +22 -0
- package/dist/pos/gift-cards.d.ts +135 -0
- package/dist/pos/gift-cards.js +47 -0
- package/dist/pos/index.d.ts +2370 -58
- package/dist/pos/index.js +854 -7
- package/dist/pos/no-receipt.d.ts +94 -0
- package/dist/pos/no-receipt.js +25 -0
- package/dist/pos/offline-sales.d.ts +228 -0
- package/dist/pos/offline-sales.js +170 -0
- package/dist/pos/pay-by-link.d.ts +143 -0
- package/dist/pos/pay-by-link.js +57 -0
- package/dist/pos/quick-keys.d.ts +103 -0
- package/dist/pos/quick-keys.js +45 -0
- package/dist/pos/reports.d.ts +360 -0
- package/dist/pos/reports.js +62 -0
- package/dist/pos/returns.d.ts +244 -0
- package/dist/pos/returns.js +83 -0
- package/dist/pricing/bulk-coupons.d.ts +139 -0
- package/dist/pricing/bulk-coupons.js +112 -0
- package/dist/pricing/index.d.ts +174 -4
- package/dist/pricing/index.js +174 -1
- package/dist/pricing/price-lists.d.ts +606 -0
- package/dist/pricing/price-lists.js +600 -0
- package/dist/purchasing/alerts.d.ts +104 -0
- package/dist/purchasing/alerts.js +88 -0
- package/dist/purchasing/backorders.d.ts +61 -0
- package/dist/purchasing/backorders.js +92 -0
- package/dist/purchasing/cost.d.ts +110 -0
- package/dist/purchasing/cost.js +86 -0
- package/dist/purchasing/index.d.ts +54 -0
- package/dist/purchasing/index.js +24 -0
- package/dist/purchasing/links.d.ts +263 -0
- package/dist/purchasing/links.js +192 -0
- package/dist/purchasing/purchase-orders.d.ts +551 -0
- package/dist/purchasing/purchase-orders.js +491 -0
- package/dist/purchasing/receipts.d.ts +396 -0
- package/dist/purchasing/receipts.js +287 -0
- package/dist/purchasing/replenishment.d.ts +281 -0
- package/dist/purchasing/replenishment.js +240 -0
- package/dist/purchasing/returns.d.ts +273 -0
- package/dist/purchasing/returns.js +229 -0
- package/dist/purchasing/settings.d.ts +152 -0
- package/dist/purchasing/settings.js +181 -0
- package/dist/purchasing/suppliers.d.ts +476 -0
- package/dist/purchasing/suppliers.js +438 -0
- package/dist/quotes/index.d.ts +518 -0
- package/dist/quotes/index.js +439 -0
- package/dist/reports/index.d.ts +71 -8
- package/dist/reports/index.js +74 -11
- package/dist/returns/index.d.ts +675 -21
- package/dist/returns/index.js +756 -11
- package/dist/search/index.d.ts +76 -1
- package/dist/search/index.js +63 -0
- package/dist/seo/config.d.ts +128 -0
- package/dist/seo/config.js +95 -0
- package/dist/seo/redirects.d.ts +125 -0
- package/dist/seo/redirects.js +60 -1
- package/dist/seo/settings.d.ts +147 -0
- package/dist/seo/settings.js +209 -0
- package/dist/shipping/definitions.d.ts +29 -0
- package/dist/shipping/definitions.js +47 -0
- package/dist/shipping/fulfillment-profiles.d.ts +174 -0
- package/dist/shipping/fulfillment-profiles.js +104 -0
- package/dist/shipping/hendriks.d.ts +97 -0
- package/dist/shipping/hendriks.js +50 -0
- package/dist/shipping/index.d.ts +54 -0
- package/dist/shipping/index.js +27 -0
- package/dist/shipping/methods.d.ts +205 -0
- package/dist/shipping/methods.js +240 -0
- package/dist/shipping/myparcel.d.ts +668 -0
- package/dist/shipping/myparcel.js +466 -0
- package/dist/shipping/parcel-count-suggestion.d.ts +2 -0
- package/dist/shipping/parcel-rule.d.ts +6 -5
- package/dist/shipping/parcel-rule.js +21 -23
- package/dist/shipping/rate-tables.d.ts +324 -0
- package/dist/shipping/rate-tables.js +390 -0
- package/dist/shipping/ship-to-countries.d.ts +75 -0
- package/dist/shipping/ship-to-countries.js +47 -0
- package/dist/stock/by-location.d.ts +86 -0
- package/dist/stock/by-location.js +94 -0
- package/dist/stock/index.d.ts +18 -4
- package/dist/stock/index.js +7 -0
- package/dist/stock/locations.d.ts +144 -0
- package/dist/stock/locations.js +168 -0
- package/dist/stock/settings.d.ts +106 -0
- package/dist/stock/settings.js +103 -0
- package/dist/stock/transfers.d.ts +386 -52
- package/dist/stock/transfers.js +222 -17
- package/dist/stored-value/index.d.ts +312 -11
- package/dist/stored-value/index.js +223 -4
- package/dist/subscriptions/index.d.ts +682 -0
- package/dist/subscriptions/index.js +815 -0
- package/dist/tax/display-policy.d.ts +105 -0
- package/dist/tax/display-policy.js +84 -0
- package/dist/tax/index.d.ts +79 -1
- package/dist/tax/index.js +52 -0
- package/dist/tenant/email-style.d.ts +102 -0
- package/dist/tenant/email-style.js +72 -0
- package/dist/tenant/email-templates.d.ts +287 -0
- package/dist/tenant/email-templates.js +271 -0
- package/dist/tenant/notifications.d.ts +23 -17
- package/dist/tenant/settings-sources.d.ts +1 -1
- package/dist/tenant/time-zone.d.ts +17 -0
- package/dist/tenant/time-zone.js +11 -0
- package/dist/types.d.ts +50 -3
- package/dist/users/index.d.ts +3 -0
- package/dist/users/index.js +2 -0
- package/dist/users/warehouses.d.ts +50 -0
- package/dist/users/warehouses.js +51 -0
- package/package.json +2 -2
package/dist/customers/index.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import { errorFromErrorResult, isErrorResult, pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
|
|
1
|
+
import { BusinessRuleError, errorFromErrorResult, isErrorResult, pageFromVendureList, readOffsetCursor, ValidationError, } from '@sixtyninecommerce/core';
|
|
2
2
|
import { CATALOGUE_OWNERSHIP_FIELDS } from '../catalog/types.js';
|
|
3
3
|
import { COMPLIANCE_REQUEST_FIELDS } from '../compliance/index.js';
|
|
4
4
|
import { redactionWatcher } from '../customer-data-redaction.js';
|
|
5
|
+
import { listCustomerOrders, listCustomerRecentOrders, } from '../orders/list.js';
|
|
6
|
+
import { createCustomerDateOfBirthModule, readCustomerAgeVerification, } from './age-verification.js';
|
|
7
|
+
import { createCustomerCreditModule } from './credit.js';
|
|
8
|
+
import { createCustomerDeliveryPreferencesModule, } from './delivery-preferences.js';
|
|
9
|
+
import { readCustomerDeliveryTerms } from './delivery-terms.js';
|
|
10
|
+
import { createCustomerB2BGroupsModule, createCustomerGroup, deleteCustomerGroup, deleteCustomerGroups, listCustomerGroups, readCustomerGroup, updateCustomerGroup, } from './groups.js';
|
|
11
|
+
import { readCustomerInvitationStatus, } from './invitation-status.js';
|
|
12
|
+
import { createCustomerNotesModule } from './notes.js';
|
|
13
|
+
import { createCustomerNotificationRecipientsModule, } from './notification-recipients.js';
|
|
14
|
+
import { readCustomerSavedLists } from './saved-lists.js';
|
|
15
|
+
import { createCustomerVatModule } from './vat.js';
|
|
16
|
+
import { companyOf } from './wire.js';
|
|
17
|
+
export { CUSTOMER_GROUP_FIELDS, CUSTOMER_GROUP_LIST_INCLUDES, CUSTOMER_GROUP_LIST_SORT_FIELDS, CUSTOMER_GROUP_QUERY, MAX_CUSTOMER_GROUP_LIST_TAKE, } from './groups.js';
|
|
5
18
|
/**
|
|
6
19
|
* Customers — the commerce master record (headless spec §6.5).
|
|
7
20
|
*
|
|
@@ -75,14 +88,6 @@ export const CUSTOMER_ADDRESS_FIELDS = /* GraphQL */ `
|
|
|
75
88
|
defaultShippingAddress
|
|
76
89
|
defaultBillingAddress
|
|
77
90
|
`;
|
|
78
|
-
export const LIST_QUERY = /* GraphQL */ `
|
|
79
|
-
query Customers($options: CustomerListOptions) {
|
|
80
|
-
customers(options: $options) {
|
|
81
|
-
items { ${CUSTOMER_FIELDS} }
|
|
82
|
-
totalItems
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
`;
|
|
86
91
|
export const GET_QUERY = /* GraphQL */ `
|
|
87
92
|
query CustomerById($id: ID!) {
|
|
88
93
|
customer(id: $id) { ${CUSTOMER_FIELDS} }
|
|
@@ -96,6 +101,94 @@ export const ADDRESSES_QUERY = /* GraphQL */ `
|
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
`;
|
|
104
|
+
/**
|
|
105
|
+
* A list row: the customer, plus what a list shows beside the name (the address book's company
|
|
106
|
+
* flags, for {@link CustomerListItem.company}). The order count is its own block: the engine
|
|
107
|
+
* counts each row's orders in a query of its own, so a list asks for it only when it shows it.
|
|
108
|
+
*/
|
|
109
|
+
export const CUSTOMER_LIST_FIELDS = /* GraphQL */ `
|
|
110
|
+
${CUSTOMER_FIELDS}
|
|
111
|
+
addresses {
|
|
112
|
+
company
|
|
113
|
+
defaultBillingAddress
|
|
114
|
+
defaultShippingAddress
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
117
|
+
/** The list document, with or without each row's order count. */
|
|
118
|
+
export function customerListQuery(include = []) {
|
|
119
|
+
// `take: 1`, never 0: the engine passes 0 on and TypeORM reads a falsy limit as NO limit, so
|
|
120
|
+
// `take: 0` would load every order of every row. `totalItems` is its own COUNT either way.
|
|
121
|
+
const orderCount = include.includes('orderCount')
|
|
122
|
+
? 'orders(options: { take: 1 }) { totalItems }'
|
|
123
|
+
: '';
|
|
124
|
+
return /* GraphQL */ `
|
|
125
|
+
query CustomersList($options: CustomerListOptions) {
|
|
126
|
+
customers(options: $options) {
|
|
127
|
+
items {
|
|
128
|
+
${CUSTOMER_LIST_FIELDS}
|
|
129
|
+
${orderCount}
|
|
130
|
+
}
|
|
131
|
+
totalItems
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
/** How many history entries `get` reads with `include: ['history']`, newest first. */
|
|
137
|
+
export const CUSTOMER_HISTORY_TAKE = 20;
|
|
138
|
+
/** The plugin custom fields `include: ['customFields']` reads (AFAS, VAT validation, age check). */
|
|
139
|
+
export const CUSTOMER_PLUGIN_FIELDS = /* GraphQL */ `
|
|
140
|
+
afasVrId
|
|
141
|
+
afasOrgId
|
|
142
|
+
afasContactId
|
|
143
|
+
afasPersId
|
|
144
|
+
vatNumber
|
|
145
|
+
vatValidationStatus
|
|
146
|
+
vatValidatedAt
|
|
147
|
+
vatCompanyName
|
|
148
|
+
vatCompanyAddress
|
|
149
|
+
dateOfBirth
|
|
150
|
+
`;
|
|
151
|
+
/**
|
|
152
|
+
* The document `get` sends for a set of blocks. It depends on the SET only, never on the order
|
|
153
|
+
* the caller listed them in. Without blocks it is {@link GET_QUERY}.
|
|
154
|
+
*/
|
|
155
|
+
export function customerQuery(include = []) {
|
|
156
|
+
const asked = new Set(include);
|
|
157
|
+
if (asked.size === 0)
|
|
158
|
+
return GET_QUERY;
|
|
159
|
+
const pluginFields = asked.has('customFields');
|
|
160
|
+
const blocks = [];
|
|
161
|
+
if (asked.has('addresses')) {
|
|
162
|
+
// The AFAS address id is a plugin field too, so it rides on `customFields`.
|
|
163
|
+
const addressPluginFields = pluginFields ? 'customFields { afasAddressId }' : '';
|
|
164
|
+
blocks.push(`addresses { ${CUSTOMER_ADDRESS_FIELDS} ${addressPluginFields} }`);
|
|
165
|
+
}
|
|
166
|
+
if (asked.has('user'))
|
|
167
|
+
blocks.push('user { id verified lastLogin }');
|
|
168
|
+
if (asked.has('history')) {
|
|
169
|
+
blocks.push(`history(options: { take: ${String(CUSTOMER_HISTORY_TAKE)}, sort: { createdAt: DESC } }) {
|
|
170
|
+
items {
|
|
171
|
+
id
|
|
172
|
+
type
|
|
173
|
+
isPublic
|
|
174
|
+
data
|
|
175
|
+
createdAt
|
|
176
|
+
administrator { id firstName lastName }
|
|
177
|
+
}
|
|
178
|
+
totalItems
|
|
179
|
+
}`);
|
|
180
|
+
}
|
|
181
|
+
if (pluginFields)
|
|
182
|
+
blocks.push(`customFields { ${CUSTOMER_PLUGIN_FIELDS} }`);
|
|
183
|
+
return /* GraphQL */ `
|
|
184
|
+
query CustomerById($id: ID!) {
|
|
185
|
+
customer(id: $id) {
|
|
186
|
+
${CUSTOMER_FIELDS}
|
|
187
|
+
${blocks.join('\n')}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
99
192
|
/**
|
|
100
193
|
* The CHANNEL-SCOPED creation mutation. See the module docblock — core
|
|
101
194
|
* `createCustomer` is not an option, and this SDK exposes no path to it.
|
|
@@ -285,20 +378,6 @@ export const GROUP_BY_EXTERNAL_KEY_QUERY = /* GraphQL */ `
|
|
|
285
378
|
customerGroupByExternalKey(key: $key) { ${EXTERNAL_KEYED_GROUP_FIELDS} }
|
|
286
379
|
}
|
|
287
380
|
`;
|
|
288
|
-
/** A customer group as {@link CustomerGroupsModule.get} reads it. */
|
|
289
|
-
export const CUSTOMER_GROUP_FIELDS = /* GraphQL */ `
|
|
290
|
-
id
|
|
291
|
-
createdAt
|
|
292
|
-
updatedAt
|
|
293
|
-
name
|
|
294
|
-
externalKey
|
|
295
|
-
${CATALOGUE_OWNERSHIP_FIELDS}
|
|
296
|
-
`;
|
|
297
|
-
export const CUSTOMER_GROUP_QUERY = /* GraphQL */ `
|
|
298
|
-
query CustomerGroup($id: ID!) {
|
|
299
|
-
customerGroup(id: $id) { ${CUSTOMER_GROUP_FIELDS} }
|
|
300
|
-
}
|
|
301
|
-
`;
|
|
302
381
|
export const GROUP_EXTERNAL_KEY_QUERY = /* GraphQL */ `
|
|
303
382
|
query CustomerGroupExternalKey($id: ID!) {
|
|
304
383
|
customerGroup(id: $id) {
|
|
@@ -357,32 +436,194 @@ export const DELETE_ADDRESS_MUTATION = /* GraphQL */ `
|
|
|
357
436
|
deleteCustomerAddress(id: $id) { success }
|
|
358
437
|
}
|
|
359
438
|
`;
|
|
439
|
+
export const CUSTOMER_LIST_SORT_FIELDS = [
|
|
440
|
+
'createdAt',
|
|
441
|
+
'updatedAt',
|
|
442
|
+
'firstName',
|
|
443
|
+
'lastName',
|
|
444
|
+
'emailAddress',
|
|
445
|
+
'phoneNumber',
|
|
446
|
+
];
|
|
447
|
+
/** The blocks a list row can add: `orderCount`, each row's order count. */
|
|
448
|
+
export const CUSTOMER_LIST_INCLUDES = ['orderCount'];
|
|
449
|
+
/** The engine's `adminListQueryLimit`: a larger page is refused. */
|
|
450
|
+
export const MAX_CUSTOMER_LIST_TAKE = 1000;
|
|
451
|
+
/**
|
|
452
|
+
* The blocks `get` can add to a customer, each asked for by name in `include`:
|
|
453
|
+
*
|
|
454
|
+
* - `addresses`: the address book.
|
|
455
|
+
* - `user`: the sign-in account (`null` for a guest customer).
|
|
456
|
+
* - `history`: the {@link CUSTOMER_HISTORY_TAKE} most recent history entries, newest first.
|
|
457
|
+
* - `customFields`: the fields the AFAS, VAT-validation and age-check plugins keep on the customer
|
|
458
|
+
* (and, with `addresses`, each address's AFAS id).
|
|
459
|
+
*/
|
|
460
|
+
export const CUSTOMER_INCLUDES = ['addresses', 'user', 'history', 'customFields'];
|
|
360
461
|
const DEFAULT_PAGE_SIZE = 25;
|
|
462
|
+
function invalidListOptions(message) {
|
|
463
|
+
return new ValidationError({
|
|
464
|
+
code: 'INVALID_CUSTOMER_LIST_OPTIONS',
|
|
465
|
+
message,
|
|
466
|
+
status: 400,
|
|
467
|
+
service: 'vendure',
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
function listText(value, path) {
|
|
471
|
+
if (typeof value !== 'string')
|
|
472
|
+
throw invalidListOptions(`${path} must be a string.`);
|
|
473
|
+
return value;
|
|
474
|
+
}
|
|
475
|
+
function escapeRegex(value) {
|
|
476
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
477
|
+
}
|
|
478
|
+
/** The Admin API's `CustomerFilterParameter` for a typed filter; unknown keys are refused. */
|
|
479
|
+
export function toCustomerFilterParameter(filter) {
|
|
480
|
+
if (filter === undefined)
|
|
481
|
+
return {};
|
|
482
|
+
if (typeof filter !== 'object' || filter === null || Array.isArray(filter)) {
|
|
483
|
+
throw invalidListOptions('filter must be an object.');
|
|
484
|
+
}
|
|
485
|
+
const out = {};
|
|
486
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
487
|
+
if (value === undefined)
|
|
488
|
+
continue;
|
|
489
|
+
switch (key) {
|
|
490
|
+
case 'ids':
|
|
491
|
+
if (!Array.isArray(value) || value.some((id) => typeof id !== 'string')) {
|
|
492
|
+
throw invalidListOptions('filter.ids must be a list of strings.');
|
|
493
|
+
}
|
|
494
|
+
out.id = { in: [...value] };
|
|
495
|
+
break;
|
|
496
|
+
case 'emailStartsWith':
|
|
497
|
+
// The engine matches `regex` case-insensitively (`~*`), and an anchored, escaped prefix
|
|
498
|
+
// is a starts-with: `contains` would match anywhere and read `%`/`_` as wildcards.
|
|
499
|
+
out.emailAddress = { regex: `^${escapeRegex(listText(value, 'filter.emailStartsWith'))}` };
|
|
500
|
+
break;
|
|
501
|
+
case 'lastName':
|
|
502
|
+
out.lastName = { contains: listText(value, 'filter.lastName') };
|
|
503
|
+
break;
|
|
504
|
+
default:
|
|
505
|
+
throw invalidListOptions(`filter.${key} is not a customer list filter.`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return out;
|
|
509
|
+
}
|
|
510
|
+
function customerListRequest(options) {
|
|
511
|
+
if (options?.after !== undefined && options.skip !== undefined) {
|
|
512
|
+
throw invalidListOptions('Page with after or with skip, not both.');
|
|
513
|
+
}
|
|
514
|
+
const take = options?.take ?? options?.limit ?? DEFAULT_PAGE_SIZE;
|
|
515
|
+
const skip = options?.skip ?? (options?.after === undefined ? 0 : readOffsetCursor(options.after));
|
|
516
|
+
if (!Number.isInteger(take) || take < 1 || take > MAX_CUSTOMER_LIST_TAKE) {
|
|
517
|
+
throw invalidListOptions(`take must be a whole number from 1 to ${String(MAX_CUSTOMER_LIST_TAKE)}.`);
|
|
518
|
+
}
|
|
519
|
+
if (!Number.isInteger(skip) || skip < 0) {
|
|
520
|
+
throw invalidListOptions('skip must be a whole number of 0 or more.');
|
|
521
|
+
}
|
|
522
|
+
const sort = options?.sort;
|
|
523
|
+
if (sort !== undefined &&
|
|
524
|
+
(typeof sort !== 'object' ||
|
|
525
|
+
sort === null ||
|
|
526
|
+
!CUSTOMER_LIST_SORT_FIELDS.includes(sort.field) ||
|
|
527
|
+
(sort.direction !== 'ASC' && sort.direction !== 'DESC'))) {
|
|
528
|
+
throw invalidListOptions(`sort must be { field: ${CUSTOMER_LIST_SORT_FIELDS.join(' | ')}, direction: ASC | DESC }.`);
|
|
529
|
+
}
|
|
530
|
+
const include = options?.include ?? [];
|
|
531
|
+
if (!Array.isArray(include) ||
|
|
532
|
+
!include.every((name) => CUSTOMER_LIST_INCLUDES.includes(name))) {
|
|
533
|
+
throw invalidListOptions(`include takes a list of ${CUSTOMER_LIST_INCLUDES.join(', ')}.`);
|
|
534
|
+
}
|
|
535
|
+
const filter = toCustomerFilterParameter(options?.filter);
|
|
536
|
+
return {
|
|
537
|
+
skip,
|
|
538
|
+
take,
|
|
539
|
+
request: {
|
|
540
|
+
query: customerListQuery(include),
|
|
541
|
+
...(Object.keys(filter).length > 0 ? { filter } : {}),
|
|
542
|
+
...(sort ? { sort: { [sort.field]: sort.direction } } : {}),
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function toCustomerListItem(row) {
|
|
547
|
+
const { orders, addresses, ...customer } = row;
|
|
548
|
+
const book = addresses ?? [];
|
|
549
|
+
return {
|
|
550
|
+
...customer,
|
|
551
|
+
addresses: book,
|
|
552
|
+
company: companyOf(book),
|
|
553
|
+
...(orders === undefined ? {} : { orderCount: orders.totalItems }),
|
|
554
|
+
};
|
|
555
|
+
}
|
|
361
556
|
export function createCustomersModule(transport) {
|
|
362
|
-
const listPage = async (skip, take, options) => {
|
|
557
|
+
const listPage = async (skip, take, request, options) => {
|
|
363
558
|
const redaction = redactionWatcher();
|
|
364
559
|
const data = await transport.graphql({
|
|
365
560
|
api: 'admin',
|
|
366
|
-
query:
|
|
367
|
-
variables: {
|
|
561
|
+
query: request.query,
|
|
562
|
+
variables: {
|
|
563
|
+
options: {
|
|
564
|
+
skip,
|
|
565
|
+
take,
|
|
566
|
+
...(request.filter ? { filter: request.filter, filterOperator: 'AND' } : {}),
|
|
567
|
+
...(request.sort ? { sort: request.sort } : {}),
|
|
568
|
+
},
|
|
569
|
+
},
|
|
368
570
|
onExtensions: redaction.onExtensions,
|
|
369
571
|
}, options);
|
|
370
|
-
data.customers.items.
|
|
371
|
-
|
|
572
|
+
const items = data.customers.items.map((row) => redaction.mark(toCustomerListItem(row)));
|
|
573
|
+
// Each following page re-sends the same filter, sort and per-call options.
|
|
574
|
+
return pageFromVendureList({ items, totalItems: data.customers.totalItems }, skip, take, (cursor) => listPage(readOffsetCursor(cursor), take, request, options));
|
|
372
575
|
};
|
|
576
|
+
async function get(id, options) {
|
|
577
|
+
// Typed callers cannot get here wrong. A JavaScript caller can, and an unknown name would
|
|
578
|
+
// otherwise be dropped in silence: the read would answer without the block it asked for.
|
|
579
|
+
const include = options?.include ?? [];
|
|
580
|
+
const known = (name) => CUSTOMER_INCLUDES.includes(name);
|
|
581
|
+
if (!Array.isArray(include) || !include.every(known)) {
|
|
582
|
+
const given = Array.isArray(include)
|
|
583
|
+
? include.filter((name) => !known(name)).map((name) => `"${String(name)}"`)
|
|
584
|
+
: [String(include)];
|
|
585
|
+
throw new ValidationError({
|
|
586
|
+
code: 'INVALID_CUSTOMER_INCLUDE',
|
|
587
|
+
message: `\`include\` takes a list of a customer's blocks, and ${given.join(', ')} is not one. The blocks are: ${CUSTOMER_INCLUDES.join(', ')}.`,
|
|
588
|
+
status: 400,
|
|
589
|
+
service: 'vendure',
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
const redaction = redactionWatcher();
|
|
593
|
+
const data = await transport.graphql({
|
|
594
|
+
api: 'admin',
|
|
595
|
+
query: customerQuery(include),
|
|
596
|
+
variables: { id },
|
|
597
|
+
onExtensions: redaction.onExtensions,
|
|
598
|
+
}, options);
|
|
599
|
+
const customer = redaction.mark(data.customer);
|
|
600
|
+
customer?.addresses?.forEach(redaction.mark);
|
|
601
|
+
return customer;
|
|
602
|
+
}
|
|
373
603
|
return {
|
|
374
|
-
list(options) {
|
|
604
|
+
async list(options) {
|
|
605
|
+
// A refused option rejects the promise, like an engine refusal, rather than throwing here.
|
|
375
606
|
// The Admin API is offset-paged; the offset hides inside the cursor so a
|
|
376
607
|
// consumer only ever sees `Page<T>` (spec §9.3).
|
|
377
|
-
const take = options
|
|
378
|
-
|
|
379
|
-
return listPage(skip, take, options);
|
|
608
|
+
const { skip, take, request } = customerListRequest(options);
|
|
609
|
+
return listPage(skip, take, request, options);
|
|
380
610
|
},
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const data = await transport.graphql({ api: 'admin', query: GET_QUERY, variables: { id }, onExtensions: redaction.onExtensions }, options);
|
|
384
|
-
return redaction.mark(data.customer);
|
|
611
|
+
orders(customerId, options) {
|
|
612
|
+
return listCustomerOrders(transport, customerId, options);
|
|
385
613
|
},
|
|
614
|
+
recentOrders(customerId, options) {
|
|
615
|
+
return listCustomerRecentOrders(transport, customerId, options);
|
|
616
|
+
},
|
|
617
|
+
savedLists(customerId, options) {
|
|
618
|
+
return readCustomerSavedLists(transport, customerId, options);
|
|
619
|
+
},
|
|
620
|
+
deliveryTerms(customerId, options) {
|
|
621
|
+
return readCustomerDeliveryTerms(transport, customerId, options);
|
|
622
|
+
},
|
|
623
|
+
// The answer holds exactly the blocks asked for; `CustomerFor` decides how many of them the
|
|
624
|
+
// caller's type of `include` may rely on. The overloads narrow this one implementation, which
|
|
625
|
+
// TypeScript cannot check for them.
|
|
626
|
+
get: get,
|
|
386
627
|
async create(input, options) {
|
|
387
628
|
const redaction = redactionWatcher();
|
|
388
629
|
const data = await transport.graphql({
|
|
@@ -421,6 +662,12 @@ export function createCustomersModule(transport) {
|
|
|
421
662
|
const data = await transport.graphql({ api: 'admin', query, variables: { customerId: id, mode: options?.mode ?? null } }, options);
|
|
422
663
|
return (data.inviteCustomer ?? data.resendCustomerInvitation);
|
|
423
664
|
},
|
|
665
|
+
invitationStatus(id, options) {
|
|
666
|
+
return readCustomerInvitationStatus(transport, id, options);
|
|
667
|
+
},
|
|
668
|
+
ageVerification(id, options) {
|
|
669
|
+
return readCustomerAgeVerification(transport, id, options);
|
|
670
|
+
},
|
|
424
671
|
async inviteB2B(options) {
|
|
425
672
|
const data = await transport.graphql({
|
|
426
673
|
api: 'admin',
|
|
@@ -498,10 +745,24 @@ export function createCustomersModule(transport) {
|
|
|
498
745
|
return result;
|
|
499
746
|
},
|
|
500
747
|
groups: {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
748
|
+
list(options) {
|
|
749
|
+
return listCustomerGroups(transport, options);
|
|
750
|
+
},
|
|
751
|
+
// The overloads narrow this one implementation: with `members` the answer carries them.
|
|
752
|
+
get: ((id, options) => readCustomerGroup(transport, id, options)),
|
|
753
|
+
create(input, options) {
|
|
754
|
+
return createCustomerGroup(transport, input, options);
|
|
755
|
+
},
|
|
756
|
+
update(id, input, options) {
|
|
757
|
+
return updateCustomerGroup(transport, id, input, options);
|
|
758
|
+
},
|
|
759
|
+
delete(id, options) {
|
|
760
|
+
return deleteCustomerGroup(transport, id, options);
|
|
761
|
+
},
|
|
762
|
+
deleteMany(ids, options) {
|
|
763
|
+
return deleteCustomerGroups(transport, ids, options);
|
|
504
764
|
},
|
|
765
|
+
b2b: createCustomerB2BGroupsModule(transport),
|
|
505
766
|
async byExternalKey(key, options) {
|
|
506
767
|
const data = await transport.graphql({ api: 'admin', query: GROUP_BY_EXTERNAL_KEY_QUERY, variables: { key } }, options);
|
|
507
768
|
return data.customerGroupByExternalKey;
|
|
@@ -547,6 +808,12 @@ export function createCustomersModule(transport) {
|
|
|
547
808
|
return data.removeCustomersFromGroup;
|
|
548
809
|
},
|
|
549
810
|
},
|
|
811
|
+
credit: createCustomerCreditModule(transport),
|
|
812
|
+
notes: createCustomerNotesModule(transport),
|
|
813
|
+
deliveryPreferences: createCustomerDeliveryPreferencesModule(transport),
|
|
814
|
+
notificationRecipients: createCustomerNotificationRecipientsModule(transport),
|
|
815
|
+
dateOfBirth: createCustomerDateOfBirthModule(transport),
|
|
816
|
+
vat: createCustomerVatModule(transport),
|
|
550
817
|
addresses: {
|
|
551
818
|
async list(customerId, options) {
|
|
552
819
|
const redaction = redactionWatcher();
|
|
@@ -579,7 +846,17 @@ export function createCustomersModule(transport) {
|
|
|
579
846
|
return redaction.mark(data.updateCustomerAddress);
|
|
580
847
|
},
|
|
581
848
|
async delete(id, options) {
|
|
582
|
-
await transport.graphql({ api: 'admin', query: DELETE_ADDRESS_MUTATION, variables: { id } }, options);
|
|
849
|
+
const data = await transport.graphql({ api: 'admin', query: DELETE_ADDRESS_MUTATION, variables: { id } }, options);
|
|
850
|
+
// Answered at HTTP 200, so the transport never throws for it: returned unchecked, an
|
|
851
|
+
// address the engine kept would read as deleted.
|
|
852
|
+
if (data.deleteCustomerAddress.success !== true) {
|
|
853
|
+
throw new BusinessRuleError({
|
|
854
|
+
code: 'ADDRESS_NOT_DELETED',
|
|
855
|
+
message: 'The address was not deleted. Reload the customer and try again.',
|
|
856
|
+
status: 422,
|
|
857
|
+
service: 'vendure',
|
|
858
|
+
});
|
|
859
|
+
}
|
|
583
860
|
},
|
|
584
861
|
},
|
|
585
862
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a customer can be sent an invitation, and what the last one was (headless spec §9.2
|
|
3
|
+
* `customers` "invitations"; W10-33). The engine decides: read it before offering
|
|
4
|
+
* `customers.invite`.
|
|
5
|
+
*/
|
|
6
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
7
|
+
import type { Call } from '../types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Why no invitation can be sent, the first that applies in this order:
|
|
10
|
+
*
|
|
11
|
+
* - `DEFAULT_CHANNEL`: the call names the platform root channel or an organization's workspace,
|
|
12
|
+
* not a store. Nothing of the customer is read there.
|
|
13
|
+
* - `ERASED`: the store erased the customer. Add a new customer to serve the person again.
|
|
14
|
+
* - `NO_EMAIL`: the customer has no email address.
|
|
15
|
+
* - `PLACEHOLDER_EMAIL`: the address is a stand-in under the reserved `.invalid` domain (an ERP
|
|
16
|
+
* sync writes one for an account it has no address for). Set the real address first.
|
|
17
|
+
* - `NO_LOGIN`: a guest checkout record with no sign-in account.
|
|
18
|
+
* - `NO_STOREFRONT_URL`: the store has no storefront URL, so the link would lead nowhere.
|
|
19
|
+
*/
|
|
20
|
+
export type CustomerInvitationBlockedReason = 'DEFAULT_CHANNEL' | 'ERASED' | 'NO_EMAIL' | 'PLACEHOLDER_EMAIL' | 'NO_LOGIN' | 'NO_STOREFRONT_URL';
|
|
21
|
+
/** What {@link CustomersModule.invitationStatus} answers. */
|
|
22
|
+
export interface CustomerInvitationStatus {
|
|
23
|
+
/**
|
|
24
|
+
* Null when nothing known here stops an invitation. `customers.invite` still judges the send
|
|
25
|
+
* itself and can refuse what this read cannot see: the store's invitation email switched off
|
|
26
|
+
* or without a recipient, or a sign-in the address shares with another store
|
|
27
|
+
* (`CUSTOMER_LOGIN_ON_OTHER_STORES`).
|
|
28
|
+
*/
|
|
29
|
+
blockedReason: CustomerInvitationBlockedReason | (string & {}) | null;
|
|
30
|
+
/**
|
|
31
|
+
* The newest invitation sent to the address the customer has NOW, or null. An invitation to
|
|
32
|
+
* an earlier address does not count: it never reached the current one.
|
|
33
|
+
*/
|
|
34
|
+
lastInvitation: {
|
|
35
|
+
customerId: string;
|
|
36
|
+
emailAddress: string;
|
|
37
|
+
mode: 'PASSWORD_SETUP' | 'MAGIC_LINK' | (string & {});
|
|
38
|
+
/** ISO-8601 UTC. */
|
|
39
|
+
sentAt: string;
|
|
40
|
+
} | null;
|
|
41
|
+
/**
|
|
42
|
+
* ISO-8601 UTC while the cooldown of the newest invitation (to any address) still runs:
|
|
43
|
+
* `customers.invite` refuses another one until then. Null when none is running.
|
|
44
|
+
*/
|
|
45
|
+
cooldownUntil: string | null;
|
|
46
|
+
/** How long a customer waits between two invitations, in seconds. */
|
|
47
|
+
cooldownSeconds: number;
|
|
48
|
+
/** `RESEND` once {@link lastInvitation} is set: pass `resend: true` to `customers.invite`. */
|
|
49
|
+
nextMode: 'SEND' | 'RESEND' | (string & {});
|
|
50
|
+
/**
|
|
51
|
+
* Set when the caller's customer-data level blanked the last invitation's address (an app
|
|
52
|
+
* installation below `identity`): "" then means "not granted".
|
|
53
|
+
*/
|
|
54
|
+
redacted?: true;
|
|
55
|
+
}
|
|
56
|
+
export declare const INVITATION_STATUS_QUERY = "\n query CustomerInvitationStatus($customerId: ID!) {\n customerInvitationStatus(customerId: $customerId) {\n blockedReason\n lastInvitation {\n customerId\n emailAddress\n mode\n sentAt\n }\n cooldownUntil\n cooldownSeconds\n nextMode\n }\n }\n";
|
|
57
|
+
export declare function readCustomerInvitationStatus(transport: Transport, customerId: string, options?: Call): Promise<CustomerInvitationStatus>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { customerDataField } from './wire.js';
|
|
2
|
+
export const INVITATION_STATUS_QUERY = /* GraphQL */ `
|
|
3
|
+
query CustomerInvitationStatus($customerId: ID!) {
|
|
4
|
+
customerInvitationStatus(customerId: $customerId) {
|
|
5
|
+
blockedReason
|
|
6
|
+
lastInvitation {
|
|
7
|
+
customerId
|
|
8
|
+
emailAddress
|
|
9
|
+
mode
|
|
10
|
+
sentAt
|
|
11
|
+
}
|
|
12
|
+
cooldownUntil
|
|
13
|
+
cooldownSeconds
|
|
14
|
+
nextMode
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
export async function readCustomerInvitationStatus(transport, customerId, options) {
|
|
19
|
+
const { value, mark } = await customerDataField(transport, INVITATION_STATUS_QUERY, 'customerInvitationStatus', { customerId }, options);
|
|
20
|
+
return mark(value);
|
|
21
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A customer's staff notes (headless spec §9.2 `customers`; W10-33). A note is an entry of the
|
|
3
|
+
* customer's history: `customers.get(id, { include: ['history'] })` lists it, type
|
|
4
|
+
* `CUSTOMER_NOTE`, with its id.
|
|
5
|
+
*/
|
|
6
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
7
|
+
import type { Call } from '../types.js';
|
|
8
|
+
/** A staff note on a customer. */
|
|
9
|
+
export interface CustomerNote {
|
|
10
|
+
id: string;
|
|
11
|
+
note: string;
|
|
12
|
+
/** Whether the customer sees it. */
|
|
13
|
+
isPublic: boolean;
|
|
14
|
+
/** ISO-8601 with offset. */
|
|
15
|
+
createdAt: string;
|
|
16
|
+
/** ISO-8601 with offset. */
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
/**
|
|
19
|
+
* Set when the caller's customer-data level withheld the text (an app installation below
|
|
20
|
+
* `full`): `note` is then "", meaning "not granted".
|
|
21
|
+
*/
|
|
22
|
+
redacted?: true;
|
|
23
|
+
}
|
|
24
|
+
export interface CustomerNoteInput {
|
|
25
|
+
note: string;
|
|
26
|
+
/** Whether the customer sees it. Default false: a note is for staff unless you say otherwise. */
|
|
27
|
+
isPublic?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface CustomerNotesModule {
|
|
30
|
+
/**
|
|
31
|
+
* Add a note to the customer's history. Needs `UpdateCustomer`.
|
|
32
|
+
*
|
|
33
|
+
* It resolves with nothing: the engine answers the customer, not the note. The note's id (for
|
|
34
|
+
* `update` and `delete`) is on `customers.get(id, { include: ['history'] })`, newest first. A
|
|
35
|
+
* customer this store does not hold is a `NotFoundError` (`ENTITY_NOT_FOUND`).
|
|
36
|
+
*
|
|
37
|
+
* @effect write
|
|
38
|
+
*/
|
|
39
|
+
add(customerId: string, input: CustomerNoteInput, options?: Call): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Change a note's text. Needs `UpdateCustomer`. An empty text leaves the note as it is. An id
|
|
42
|
+
* that is not a note of this store is a `NotFoundError` (`ENTITY_NOT_FOUND`), the same as a
|
|
43
|
+
* missing one.
|
|
44
|
+
*
|
|
45
|
+
* The engine records an invitation as a note reading `Invitation sent (<MODE>) to <address>`,
|
|
46
|
+
* and {@link CustomersModule.invitationStatus} reads the last invitation from it: rewording or
|
|
47
|
+
* deleting that note makes the customer read as never invited.
|
|
48
|
+
*
|
|
49
|
+
* @effect write
|
|
50
|
+
*/
|
|
51
|
+
update(noteId: string, input: {
|
|
52
|
+
note: string;
|
|
53
|
+
}, options?: Call): Promise<CustomerNote>;
|
|
54
|
+
/**
|
|
55
|
+
* Delete a note for good. Needs `UpdateCustomer`.
|
|
56
|
+
*
|
|
57
|
+
* Refused `CUSTOMER_NOTE_NOT_DELETED` (`ConflictError`, 409, as every `*_NOT_DELETED` of this
|
|
58
|
+
* SDK) with the engine's own reason when this store has no such note.
|
|
59
|
+
*
|
|
60
|
+
* @effect destructive
|
|
61
|
+
*/
|
|
62
|
+
delete(noteId: string, options?: Call): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
export declare const ADD_CUSTOMER_NOTE_MUTATION = "\n mutation AddNoteToCustomer($input: AddNoteToCustomerInput!) {\n addNoteToCustomer(input: $input) {\n id\n }\n }\n";
|
|
65
|
+
export declare const UPDATE_CUSTOMER_NOTE_MUTATION = "\n mutation UpdateCustomerNote($input: UpdateCustomerNoteInput!) {\n updateCustomerNote(input: $input) {\n id\n isPublic\n data\n createdAt\n updatedAt\n }\n }\n";
|
|
66
|
+
export declare const DELETE_CUSTOMER_NOTE_MUTATION = "\n mutation DeleteCustomerNote($id: ID!) {\n deleteCustomerNote(id: $id) {\n result\n message\n }\n }\n";
|
|
67
|
+
export declare function createCustomerNotesModule(transport: Transport): CustomerNotesModule;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { assertDeleted, fieldOf } from '../catalog/types.js';
|
|
2
|
+
import { customerDataField } from './wire.js';
|
|
3
|
+
export const ADD_CUSTOMER_NOTE_MUTATION = /* GraphQL */ `
|
|
4
|
+
mutation AddNoteToCustomer($input: AddNoteToCustomerInput!) {
|
|
5
|
+
addNoteToCustomer(input: $input) {
|
|
6
|
+
id
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
`;
|
|
10
|
+
export const UPDATE_CUSTOMER_NOTE_MUTATION = /* GraphQL */ `
|
|
11
|
+
mutation UpdateCustomerNote($input: UpdateCustomerNoteInput!) {
|
|
12
|
+
updateCustomerNote(input: $input) {
|
|
13
|
+
id
|
|
14
|
+
isPublic
|
|
15
|
+
data
|
|
16
|
+
createdAt
|
|
17
|
+
updatedAt
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
export const DELETE_CUSTOMER_NOTE_MUTATION = /* GraphQL */ `
|
|
22
|
+
mutation DeleteCustomerNote($id: ID!) {
|
|
23
|
+
deleteCustomerNote(id: $id) {
|
|
24
|
+
result
|
|
25
|
+
message
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
export function createCustomerNotesModule(transport) {
|
|
30
|
+
return {
|
|
31
|
+
async add(customerId, input, options) {
|
|
32
|
+
await fieldOf(transport, ADD_CUSTOMER_NOTE_MUTATION, 'addNoteToCustomer', { input: { id: customerId, note: input.note, isPublic: input.isPublic ?? false } }, options);
|
|
33
|
+
},
|
|
34
|
+
async update(noteId, input, options) {
|
|
35
|
+
const { value: entry, mark } = await customerDataField(transport, UPDATE_CUSTOMER_NOTE_MUTATION, 'updateCustomerNote', { input: { noteId, note: input.note } }, options);
|
|
36
|
+
const text = entry.data?.note;
|
|
37
|
+
return mark({
|
|
38
|
+
id: entry.id,
|
|
39
|
+
note: typeof text === 'string' ? text : '',
|
|
40
|
+
isPublic: entry.isPublic,
|
|
41
|
+
createdAt: entry.createdAt,
|
|
42
|
+
updatedAt: entry.updatedAt,
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
async delete(noteId, options) {
|
|
46
|
+
assertDeleted('CUSTOMER_NOTE_NOT_DELETED', [
|
|
47
|
+
await fieldOf(transport, DELETE_CUSTOMER_NOTE_MUTATION, 'deleteCustomerNote', { id: noteId }, options),
|
|
48
|
+
]);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|