@sixtyninecommerce/admin 0.9.0 → 0.11.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 +4598 -128
- 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/index.d.ts +9 -2
- package/dist/catalog/index.js +4 -0
- package/dist/catalog/products.d.ts +318 -17
- package/dist/catalog/products.js +182 -14
- package/dist/catalog/properties.d.ts +102 -14
- package/dist/catalog/properties.js +13 -0
- package/dist/catalog/recommendations.d.ts +261 -0
- package/dist/catalog/recommendations.js +145 -0
- package/dist/catalog/reviews.d.ts +368 -0
- package/dist/catalog/reviews.js +318 -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 +282 -25
- package/dist/catalog/variants.js +173 -25
- package/dist/catalog/visibility.d.ts +20 -5
- package/dist/catalog/visibility.js +19 -1
- package/dist/client.d.ts +116 -9
- package/dist/client.js +41 -7
- package/dist/content/assets.d.ts +286 -0
- package/dist/content/assets.js +181 -0
- package/dist/content/items.d.ts +269 -0
- package/dist/content/items.js +170 -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/groups.d.ts +207 -0
- package/dist/customers/groups.js +438 -0
- package/dist/customers/index.d.ts +447 -37
- package/dist/customers/index.js +313 -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/index.d.ts +731 -21
- package/dist/documents/index.js +436 -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 +121 -10
- package/dist/fulfillments/index.js +120 -2
- 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 +129 -30
- package/dist/index.js +47 -9
- 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 +423 -0
- package/dist/integrations/exact-online-types.js +6 -0
- package/dist/integrations/exact-online.d.ts +248 -0
- package/dist/integrations/exact-online.js +376 -0
- package/dist/integrations/index.d.ts +40 -0
- package/dist/integrations/index.js +24 -0
- package/dist/integrations/pipeline-keys.d.ts +13 -0
- package/dist/integrations/pipeline-keys.js +22 -0
- package/dist/integrations/pipelines.d.ts +114 -0
- package/dist/integrations/pipelines.js +46 -0
- package/dist/integrations/types.d.ts +322 -0
- package/dist/integrations/types.js +8 -0
- package/dist/jobs/bulk-action-run.d.ts +11 -2
- package/dist/jobs/bulk-action-run.js +4 -5
- package/dist/locations/index.d.ts +66 -0
- package/dist/locations/index.js +31 -0
- package/dist/meta/index.d.ts +162 -0
- package/dist/orders/bulk.d.ts +75 -0
- package/dist/orders/bulk.js +150 -0
- package/dist/orders/cancel-with-money.d.ts +227 -0
- package/dist/orders/cancel-with-money.js +153 -0
- package/dist/orders/commit-edit.d.ts +7 -1
- package/dist/orders/commit-edit.js +5 -1
- package/dist/orders/deposit-policy.d.ts +103 -0
- package/dist/orders/deposit-policy.js +202 -0
- 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 +125 -5
- package/dist/orders/index.js +35 -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 +222 -0
- package/dist/orders/list.js +377 -0
- package/dist/orders/manual-discounts.d.ts +11 -2
- package/dist/orders/manual-discounts.js +2 -0
- package/dist/orders/manual-pricing.d.ts +5 -0
- package/dist/orders/order-entry.d.ts +20 -7
- package/dist/orders/plan-edit.d.ts +1 -1
- package/dist/orders/plan-edit.js +4 -1
- 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 +8 -1
- package/dist/orders/surcharges.js +3 -2
- package/dist/payments/account-credit.d.ts +30 -0
- package/dist/payments/account-credit.js +19 -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 +12 -0
- package/dist/payments/credit-noted-fields.js +37 -0
- package/dist/payments/index.d.ts +263 -10
- package/dist/payments/index.js +133 -1
- package/dist/payments/methods.d.ts +332 -0
- package/dist/payments/methods.js +363 -0
- 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 +144 -0
- package/dist/payments/requests.js +92 -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 +104 -0
- package/dist/pickups/index.js +105 -0
- package/dist/platform/channels.d.ts +132 -0
- package/dist/platform/channels.js +128 -0
- package/dist/platform/geo.d.ts +123 -0
- package/dist/platform/geo.js +144 -0
- package/dist/platform/tax.d.ts +154 -0
- package/dist/platform/tax.js +139 -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 +103 -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 +2125 -32
- package/dist/pos/index.js +742 -4
- package/dist/pos/no-receipt.d.ts +94 -0
- package/dist/pos/no-receipt.js +25 -0
- package/dist/pos/offline-sales.d.ts +187 -0
- package/dist/pos/offline-sales.js +116 -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 +217 -0
- package/dist/pos/returns.js +53 -0
- package/dist/pricing/bulk-coupons.d.ts +139 -0
- package/dist/pricing/bulk-coupons.js +112 -0
- package/dist/pricing/index.d.ts +177 -4
- package/dist/pricing/index.js +176 -1
- package/dist/pricing/price-lists.d.ts +604 -0
- package/dist/pricing/price-lists.js +600 -0
- package/dist/pricing/vouchers.d.ts +103 -0
- package/dist/pricing/vouchers.js +69 -0
- package/dist/purchasing/alerts.d.ts +89 -0
- package/dist/purchasing/alerts.js +62 -0
- package/dist/purchasing/cost.d.ts +110 -0
- package/dist/purchasing/cost.js +86 -0
- package/dist/purchasing/index.d.ts +36 -0
- package/dist/purchasing/index.js +16 -0
- package/dist/purchasing/purchase-orders.d.ts +548 -0
- package/dist/purchasing/purchase-orders.js +491 -0
- package/dist/purchasing/replenishment.d.ts +281 -0
- package/dist/purchasing/replenishment.js +240 -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 +501 -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 +818 -0
- package/dist/returns/index.js +844 -0
- package/dist/search/index.d.ts +174 -0
- package/dist/search/index.js +125 -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 +48 -0
- package/dist/shipping/index.js +25 -0
- package/dist/shipping/methods.d.ts +198 -0
- package/dist/shipping/methods.js +240 -0
- package/dist/shipping/myparcel.d.ts +254 -0
- package/dist/shipping/myparcel.js +178 -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 +179 -0
- package/dist/shipping/rate-tables.js +184 -0
- package/dist/stock/index.d.ts +10 -3
- package/dist/stock/index.js +3 -0
- package/dist/stock/locations.d.ts +144 -0
- package/dist/stock/locations.js +168 -0
- package/dist/stock/transfers.d.ts +216 -43
- package/dist/stock/transfers.js +140 -13
- package/dist/stored-value/index.d.ts +214 -4
- package/dist/stored-value/index.js +152 -0
- package/dist/subscriptions/index.d.ts +682 -0
- package/dist/subscriptions/index.js +815 -0
- package/dist/tax/index.d.ts +76 -1
- package/dist/tax/index.js +50 -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 +66 -0
- package/dist/tenant/email-templates.js +54 -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 +51 -4
- 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,19 @@
|
|
|
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 { createCustomerB2BGroupsModule, createCustomerGroup, deleteCustomerGroup, deleteCustomerGroups, listCustomerGroups, readCustomerGroup, updateCustomerGroup, } from './groups.js';
|
|
10
|
+
import { readCustomerInvitationStatus, } from './invitation-status.js';
|
|
11
|
+
import { createCustomerNotesModule } from './notes.js';
|
|
12
|
+
import { createCustomerNotificationRecipientsModule, } from './notification-recipients.js';
|
|
13
|
+
import { readCustomerSavedLists } from './saved-lists.js';
|
|
14
|
+
import { createCustomerVatModule } from './vat.js';
|
|
15
|
+
import { companyOf } from './wire.js';
|
|
16
|
+
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
17
|
/**
|
|
6
18
|
* Customers — the commerce master record (headless spec §6.5).
|
|
7
19
|
*
|
|
@@ -75,14 +87,6 @@ export const CUSTOMER_ADDRESS_FIELDS = /* GraphQL */ `
|
|
|
75
87
|
defaultShippingAddress
|
|
76
88
|
defaultBillingAddress
|
|
77
89
|
`;
|
|
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
90
|
export const GET_QUERY = /* GraphQL */ `
|
|
87
91
|
query CustomerById($id: ID!) {
|
|
88
92
|
customer(id: $id) { ${CUSTOMER_FIELDS} }
|
|
@@ -96,6 +100,94 @@ export const ADDRESSES_QUERY = /* GraphQL */ `
|
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
`;
|
|
103
|
+
/**
|
|
104
|
+
* A list row: the customer, plus what a list shows beside the name (the address book's company
|
|
105
|
+
* flags, for {@link CustomerListItem.company}). The order count is its own block: the engine
|
|
106
|
+
* counts each row's orders in a query of its own, so a list asks for it only when it shows it.
|
|
107
|
+
*/
|
|
108
|
+
export const CUSTOMER_LIST_FIELDS = /* GraphQL */ `
|
|
109
|
+
${CUSTOMER_FIELDS}
|
|
110
|
+
addresses {
|
|
111
|
+
company
|
|
112
|
+
defaultBillingAddress
|
|
113
|
+
defaultShippingAddress
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
/** The list document, with or without each row's order count. */
|
|
117
|
+
export function customerListQuery(include = []) {
|
|
118
|
+
// `take: 1`, never 0: the engine passes 0 on and TypeORM reads a falsy limit as NO limit, so
|
|
119
|
+
// `take: 0` would load every order of every row. `totalItems` is its own COUNT either way.
|
|
120
|
+
const orderCount = include.includes('orderCount')
|
|
121
|
+
? 'orders(options: { take: 1 }) { totalItems }'
|
|
122
|
+
: '';
|
|
123
|
+
return /* GraphQL */ `
|
|
124
|
+
query CustomersList($options: CustomerListOptions) {
|
|
125
|
+
customers(options: $options) {
|
|
126
|
+
items {
|
|
127
|
+
${CUSTOMER_LIST_FIELDS}
|
|
128
|
+
${orderCount}
|
|
129
|
+
}
|
|
130
|
+
totalItems
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
/** How many history entries `get` reads with `include: ['history']`, newest first. */
|
|
136
|
+
export const CUSTOMER_HISTORY_TAKE = 20;
|
|
137
|
+
/** The plugin custom fields `include: ['customFields']` reads (AFAS, VAT validation, age check). */
|
|
138
|
+
export const CUSTOMER_PLUGIN_FIELDS = /* GraphQL */ `
|
|
139
|
+
afasVrId
|
|
140
|
+
afasOrgId
|
|
141
|
+
afasContactId
|
|
142
|
+
afasPersId
|
|
143
|
+
vatNumber
|
|
144
|
+
vatValidationStatus
|
|
145
|
+
vatValidatedAt
|
|
146
|
+
vatCompanyName
|
|
147
|
+
vatCompanyAddress
|
|
148
|
+
dateOfBirth
|
|
149
|
+
`;
|
|
150
|
+
/**
|
|
151
|
+
* The document `get` sends for a set of blocks. It depends on the SET only, never on the order
|
|
152
|
+
* the caller listed them in. Without blocks it is {@link GET_QUERY}.
|
|
153
|
+
*/
|
|
154
|
+
export function customerQuery(include = []) {
|
|
155
|
+
const asked = new Set(include);
|
|
156
|
+
if (asked.size === 0)
|
|
157
|
+
return GET_QUERY;
|
|
158
|
+
const pluginFields = asked.has('customFields');
|
|
159
|
+
const blocks = [];
|
|
160
|
+
if (asked.has('addresses')) {
|
|
161
|
+
// The AFAS address id is a plugin field too, so it rides on `customFields`.
|
|
162
|
+
const addressPluginFields = pluginFields ? 'customFields { afasAddressId }' : '';
|
|
163
|
+
blocks.push(`addresses { ${CUSTOMER_ADDRESS_FIELDS} ${addressPluginFields} }`);
|
|
164
|
+
}
|
|
165
|
+
if (asked.has('user'))
|
|
166
|
+
blocks.push('user { id verified lastLogin }');
|
|
167
|
+
if (asked.has('history')) {
|
|
168
|
+
blocks.push(`history(options: { take: ${String(CUSTOMER_HISTORY_TAKE)}, sort: { createdAt: DESC } }) {
|
|
169
|
+
items {
|
|
170
|
+
id
|
|
171
|
+
type
|
|
172
|
+
isPublic
|
|
173
|
+
data
|
|
174
|
+
createdAt
|
|
175
|
+
administrator { id firstName lastName }
|
|
176
|
+
}
|
|
177
|
+
totalItems
|
|
178
|
+
}`);
|
|
179
|
+
}
|
|
180
|
+
if (pluginFields)
|
|
181
|
+
blocks.push(`customFields { ${CUSTOMER_PLUGIN_FIELDS} }`);
|
|
182
|
+
return /* GraphQL */ `
|
|
183
|
+
query CustomerById($id: ID!) {
|
|
184
|
+
customer(id: $id) {
|
|
185
|
+
${CUSTOMER_FIELDS}
|
|
186
|
+
${blocks.join('\n')}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
99
191
|
/**
|
|
100
192
|
* The CHANNEL-SCOPED creation mutation. See the module docblock — core
|
|
101
193
|
* `createCustomer` is not an option, and this SDK exposes no path to it.
|
|
@@ -285,20 +377,6 @@ export const GROUP_BY_EXTERNAL_KEY_QUERY = /* GraphQL */ `
|
|
|
285
377
|
customerGroupByExternalKey(key: $key) { ${EXTERNAL_KEYED_GROUP_FIELDS} }
|
|
286
378
|
}
|
|
287
379
|
`;
|
|
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
380
|
export const GROUP_EXTERNAL_KEY_QUERY = /* GraphQL */ `
|
|
303
381
|
query CustomerGroupExternalKey($id: ID!) {
|
|
304
382
|
customerGroup(id: $id) {
|
|
@@ -357,32 +435,191 @@ export const DELETE_ADDRESS_MUTATION = /* GraphQL */ `
|
|
|
357
435
|
deleteCustomerAddress(id: $id) { success }
|
|
358
436
|
}
|
|
359
437
|
`;
|
|
438
|
+
export const CUSTOMER_LIST_SORT_FIELDS = [
|
|
439
|
+
'createdAt',
|
|
440
|
+
'updatedAt',
|
|
441
|
+
'firstName',
|
|
442
|
+
'lastName',
|
|
443
|
+
'emailAddress',
|
|
444
|
+
'phoneNumber',
|
|
445
|
+
];
|
|
446
|
+
/** The blocks a list row can add: `orderCount`, each row's order count. */
|
|
447
|
+
export const CUSTOMER_LIST_INCLUDES = ['orderCount'];
|
|
448
|
+
/** The engine's `adminListQueryLimit`: a larger page is refused. */
|
|
449
|
+
export const MAX_CUSTOMER_LIST_TAKE = 1000;
|
|
450
|
+
/**
|
|
451
|
+
* The blocks `get` can add to a customer, each asked for by name in `include`:
|
|
452
|
+
*
|
|
453
|
+
* - `addresses`: the address book.
|
|
454
|
+
* - `user`: the sign-in account (`null` for a guest customer).
|
|
455
|
+
* - `history`: the {@link CUSTOMER_HISTORY_TAKE} most recent history entries, newest first.
|
|
456
|
+
* - `customFields`: the fields the AFAS, VAT-validation and age-check plugins keep on the customer
|
|
457
|
+
* (and, with `addresses`, each address's AFAS id).
|
|
458
|
+
*/
|
|
459
|
+
export const CUSTOMER_INCLUDES = ['addresses', 'user', 'history', 'customFields'];
|
|
360
460
|
const DEFAULT_PAGE_SIZE = 25;
|
|
461
|
+
function invalidListOptions(message) {
|
|
462
|
+
return new ValidationError({
|
|
463
|
+
code: 'INVALID_CUSTOMER_LIST_OPTIONS',
|
|
464
|
+
message,
|
|
465
|
+
status: 400,
|
|
466
|
+
service: 'vendure',
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
function listText(value, path) {
|
|
470
|
+
if (typeof value !== 'string')
|
|
471
|
+
throw invalidListOptions(`${path} must be a string.`);
|
|
472
|
+
return value;
|
|
473
|
+
}
|
|
474
|
+
function escapeRegex(value) {
|
|
475
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
476
|
+
}
|
|
477
|
+
/** The Admin API's `CustomerFilterParameter` for a typed filter; unknown keys are refused. */
|
|
478
|
+
export function toCustomerFilterParameter(filter) {
|
|
479
|
+
if (filter === undefined)
|
|
480
|
+
return {};
|
|
481
|
+
if (typeof filter !== 'object' || filter === null || Array.isArray(filter)) {
|
|
482
|
+
throw invalidListOptions('filter must be an object.');
|
|
483
|
+
}
|
|
484
|
+
const out = {};
|
|
485
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
486
|
+
if (value === undefined)
|
|
487
|
+
continue;
|
|
488
|
+
switch (key) {
|
|
489
|
+
case 'ids':
|
|
490
|
+
if (!Array.isArray(value) || value.some((id) => typeof id !== 'string')) {
|
|
491
|
+
throw invalidListOptions('filter.ids must be a list of strings.');
|
|
492
|
+
}
|
|
493
|
+
out.id = { in: [...value] };
|
|
494
|
+
break;
|
|
495
|
+
case 'emailStartsWith':
|
|
496
|
+
// The engine matches `regex` case-insensitively (`~*`), and an anchored, escaped prefix
|
|
497
|
+
// is a starts-with: `contains` would match anywhere and read `%`/`_` as wildcards.
|
|
498
|
+
out.emailAddress = { regex: `^${escapeRegex(listText(value, 'filter.emailStartsWith'))}` };
|
|
499
|
+
break;
|
|
500
|
+
case 'lastName':
|
|
501
|
+
out.lastName = { contains: listText(value, 'filter.lastName') };
|
|
502
|
+
break;
|
|
503
|
+
default:
|
|
504
|
+
throw invalidListOptions(`filter.${key} is not a customer list filter.`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
function customerListRequest(options) {
|
|
510
|
+
if (options?.after !== undefined && options.skip !== undefined) {
|
|
511
|
+
throw invalidListOptions('Page with after or with skip, not both.');
|
|
512
|
+
}
|
|
513
|
+
const take = options?.take ?? options?.limit ?? DEFAULT_PAGE_SIZE;
|
|
514
|
+
const skip = options?.skip ?? (options?.after === undefined ? 0 : readOffsetCursor(options.after));
|
|
515
|
+
if (!Number.isInteger(take) || take < 1 || take > MAX_CUSTOMER_LIST_TAKE) {
|
|
516
|
+
throw invalidListOptions(`take must be a whole number from 1 to ${String(MAX_CUSTOMER_LIST_TAKE)}.`);
|
|
517
|
+
}
|
|
518
|
+
if (!Number.isInteger(skip) || skip < 0) {
|
|
519
|
+
throw invalidListOptions('skip must be a whole number of 0 or more.');
|
|
520
|
+
}
|
|
521
|
+
const sort = options?.sort;
|
|
522
|
+
if (sort !== undefined &&
|
|
523
|
+
(typeof sort !== 'object' ||
|
|
524
|
+
sort === null ||
|
|
525
|
+
!CUSTOMER_LIST_SORT_FIELDS.includes(sort.field) ||
|
|
526
|
+
(sort.direction !== 'ASC' && sort.direction !== 'DESC'))) {
|
|
527
|
+
throw invalidListOptions(`sort must be { field: ${CUSTOMER_LIST_SORT_FIELDS.join(' | ')}, direction: ASC | DESC }.`);
|
|
528
|
+
}
|
|
529
|
+
const include = options?.include ?? [];
|
|
530
|
+
if (!Array.isArray(include) ||
|
|
531
|
+
!include.every((name) => CUSTOMER_LIST_INCLUDES.includes(name))) {
|
|
532
|
+
throw invalidListOptions(`include takes a list of ${CUSTOMER_LIST_INCLUDES.join(', ')}.`);
|
|
533
|
+
}
|
|
534
|
+
const filter = toCustomerFilterParameter(options?.filter);
|
|
535
|
+
return {
|
|
536
|
+
skip,
|
|
537
|
+
take,
|
|
538
|
+
request: {
|
|
539
|
+
query: customerListQuery(include),
|
|
540
|
+
...(Object.keys(filter).length > 0 ? { filter } : {}),
|
|
541
|
+
...(sort ? { sort: { [sort.field]: sort.direction } } : {}),
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function toCustomerListItem(row) {
|
|
546
|
+
const { orders, addresses, ...customer } = row;
|
|
547
|
+
const book = addresses ?? [];
|
|
548
|
+
return {
|
|
549
|
+
...customer,
|
|
550
|
+
addresses: book,
|
|
551
|
+
company: companyOf(book),
|
|
552
|
+
...(orders === undefined ? {} : { orderCount: orders.totalItems }),
|
|
553
|
+
};
|
|
554
|
+
}
|
|
361
555
|
export function createCustomersModule(transport) {
|
|
362
|
-
const listPage = async (skip, take, options) => {
|
|
556
|
+
const listPage = async (skip, take, request, options) => {
|
|
363
557
|
const redaction = redactionWatcher();
|
|
364
558
|
const data = await transport.graphql({
|
|
365
559
|
api: 'admin',
|
|
366
|
-
query:
|
|
367
|
-
variables: {
|
|
560
|
+
query: request.query,
|
|
561
|
+
variables: {
|
|
562
|
+
options: {
|
|
563
|
+
skip,
|
|
564
|
+
take,
|
|
565
|
+
...(request.filter ? { filter: request.filter, filterOperator: 'AND' } : {}),
|
|
566
|
+
...(request.sort ? { sort: request.sort } : {}),
|
|
567
|
+
},
|
|
568
|
+
},
|
|
368
569
|
onExtensions: redaction.onExtensions,
|
|
369
570
|
}, options);
|
|
370
|
-
data.customers.items.
|
|
371
|
-
|
|
571
|
+
const items = data.customers.items.map((row) => redaction.mark(toCustomerListItem(row)));
|
|
572
|
+
// Each following page re-sends the same filter, sort and per-call options.
|
|
573
|
+
return pageFromVendureList({ items, totalItems: data.customers.totalItems }, skip, take, (cursor) => listPage(readOffsetCursor(cursor), take, request, options));
|
|
372
574
|
};
|
|
575
|
+
async function get(id, options) {
|
|
576
|
+
// Typed callers cannot get here wrong. A JavaScript caller can, and an unknown name would
|
|
577
|
+
// otherwise be dropped in silence: the read would answer without the block it asked for.
|
|
578
|
+
const include = options?.include ?? [];
|
|
579
|
+
const known = (name) => CUSTOMER_INCLUDES.includes(name);
|
|
580
|
+
if (!Array.isArray(include) || !include.every(known)) {
|
|
581
|
+
const given = Array.isArray(include)
|
|
582
|
+
? include.filter((name) => !known(name)).map((name) => `"${String(name)}"`)
|
|
583
|
+
: [String(include)];
|
|
584
|
+
throw new ValidationError({
|
|
585
|
+
code: 'INVALID_CUSTOMER_INCLUDE',
|
|
586
|
+
message: `\`include\` takes a list of a customer's blocks, and ${given.join(', ')} is not one. The blocks are: ${CUSTOMER_INCLUDES.join(', ')}.`,
|
|
587
|
+
status: 400,
|
|
588
|
+
service: 'vendure',
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
const redaction = redactionWatcher();
|
|
592
|
+
const data = await transport.graphql({
|
|
593
|
+
api: 'admin',
|
|
594
|
+
query: customerQuery(include),
|
|
595
|
+
variables: { id },
|
|
596
|
+
onExtensions: redaction.onExtensions,
|
|
597
|
+
}, options);
|
|
598
|
+
const customer = redaction.mark(data.customer);
|
|
599
|
+
customer?.addresses?.forEach(redaction.mark);
|
|
600
|
+
return customer;
|
|
601
|
+
}
|
|
373
602
|
return {
|
|
374
|
-
list(options) {
|
|
603
|
+
async list(options) {
|
|
604
|
+
// A refused option rejects the promise, like an engine refusal, rather than throwing here.
|
|
375
605
|
// The Admin API is offset-paged; the offset hides inside the cursor so a
|
|
376
606
|
// consumer only ever sees `Page<T>` (spec §9.3).
|
|
377
|
-
const take = options
|
|
378
|
-
|
|
379
|
-
return listPage(skip, take, options);
|
|
607
|
+
const { skip, take, request } = customerListRequest(options);
|
|
608
|
+
return listPage(skip, take, request, options);
|
|
380
609
|
},
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
610
|
+
orders(customerId, options) {
|
|
611
|
+
return listCustomerOrders(transport, customerId, options);
|
|
612
|
+
},
|
|
613
|
+
recentOrders(customerId, options) {
|
|
614
|
+
return listCustomerRecentOrders(transport, customerId, options);
|
|
615
|
+
},
|
|
616
|
+
savedLists(customerId, options) {
|
|
617
|
+
return readCustomerSavedLists(transport, customerId, options);
|
|
385
618
|
},
|
|
619
|
+
// The answer holds exactly the blocks asked for; `CustomerFor` decides how many of them the
|
|
620
|
+
// caller's type of `include` may rely on. The overloads narrow this one implementation, which
|
|
621
|
+
// TypeScript cannot check for them.
|
|
622
|
+
get: get,
|
|
386
623
|
async create(input, options) {
|
|
387
624
|
const redaction = redactionWatcher();
|
|
388
625
|
const data = await transport.graphql({
|
|
@@ -421,6 +658,12 @@ export function createCustomersModule(transport) {
|
|
|
421
658
|
const data = await transport.graphql({ api: 'admin', query, variables: { customerId: id, mode: options?.mode ?? null } }, options);
|
|
422
659
|
return (data.inviteCustomer ?? data.resendCustomerInvitation);
|
|
423
660
|
},
|
|
661
|
+
invitationStatus(id, options) {
|
|
662
|
+
return readCustomerInvitationStatus(transport, id, options);
|
|
663
|
+
},
|
|
664
|
+
ageVerification(id, options) {
|
|
665
|
+
return readCustomerAgeVerification(transport, id, options);
|
|
666
|
+
},
|
|
424
667
|
async inviteB2B(options) {
|
|
425
668
|
const data = await transport.graphql({
|
|
426
669
|
api: 'admin',
|
|
@@ -498,10 +741,24 @@ export function createCustomersModule(transport) {
|
|
|
498
741
|
return result;
|
|
499
742
|
},
|
|
500
743
|
groups: {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
744
|
+
list(options) {
|
|
745
|
+
return listCustomerGroups(transport, options);
|
|
746
|
+
},
|
|
747
|
+
// The overloads narrow this one implementation: with `members` the answer carries them.
|
|
748
|
+
get: ((id, options) => readCustomerGroup(transport, id, options)),
|
|
749
|
+
create(input, options) {
|
|
750
|
+
return createCustomerGroup(transport, input, options);
|
|
751
|
+
},
|
|
752
|
+
update(id, input, options) {
|
|
753
|
+
return updateCustomerGroup(transport, id, input, options);
|
|
754
|
+
},
|
|
755
|
+
delete(id, options) {
|
|
756
|
+
return deleteCustomerGroup(transport, id, options);
|
|
757
|
+
},
|
|
758
|
+
deleteMany(ids, options) {
|
|
759
|
+
return deleteCustomerGroups(transport, ids, options);
|
|
504
760
|
},
|
|
761
|
+
b2b: createCustomerB2BGroupsModule(transport),
|
|
505
762
|
async byExternalKey(key, options) {
|
|
506
763
|
const data = await transport.graphql({ api: 'admin', query: GROUP_BY_EXTERNAL_KEY_QUERY, variables: { key } }, options);
|
|
507
764
|
return data.customerGroupByExternalKey;
|
|
@@ -547,6 +804,12 @@ export function createCustomersModule(transport) {
|
|
|
547
804
|
return data.removeCustomersFromGroup;
|
|
548
805
|
},
|
|
549
806
|
},
|
|
807
|
+
credit: createCustomerCreditModule(transport),
|
|
808
|
+
notes: createCustomerNotesModule(transport),
|
|
809
|
+
deliveryPreferences: createCustomerDeliveryPreferencesModule(transport),
|
|
810
|
+
notificationRecipients: createCustomerNotificationRecipientsModule(transport),
|
|
811
|
+
dateOfBirth: createCustomerDateOfBirthModule(transport),
|
|
812
|
+
vat: createCustomerVatModule(transport),
|
|
550
813
|
addresses: {
|
|
551
814
|
async list(customerId, options) {
|
|
552
815
|
const redaction = redactionWatcher();
|
|
@@ -579,7 +842,17 @@ export function createCustomersModule(transport) {
|
|
|
579
842
|
return redaction.mark(data.updateCustomerAddress);
|
|
580
843
|
},
|
|
581
844
|
async delete(id, options) {
|
|
582
|
-
await transport.graphql({ api: 'admin', query: DELETE_ADDRESS_MUTATION, variables: { id } }, options);
|
|
845
|
+
const data = await transport.graphql({ api: 'admin', query: DELETE_ADDRESS_MUTATION, variables: { id } }, options);
|
|
846
|
+
// Answered at HTTP 200, so the transport never throws for it: returned unchecked, an
|
|
847
|
+
// address the engine kept would read as deleted.
|
|
848
|
+
if (data.deleteCustomerAddress.success !== true) {
|
|
849
|
+
throw new BusinessRuleError({
|
|
850
|
+
code: 'ADDRESS_NOT_DELETED',
|
|
851
|
+
message: 'The address was not deleted. Reload the customer and try again.',
|
|
852
|
+
status: 422,
|
|
853
|
+
service: 'vendure',
|
|
854
|
+
});
|
|
855
|
+
}
|
|
583
856
|
},
|
|
584
857
|
},
|
|
585
858
|
};
|
|
@@ -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
|
+
}
|