@sixtyninecommerce/admin 0.11.0 → 0.13.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.
Files changed (153) hide show
  1. package/README.md +1509 -28
  2. package/dist/auth/index.d.ts +7 -1
  3. package/dist/auth/index.js +8 -1
  4. package/dist/catalog/documents.d.ts +142 -0
  5. package/dist/catalog/documents.js +114 -0
  6. package/dist/catalog/index.d.ts +48 -7
  7. package/dist/catalog/index.js +6 -1
  8. package/dist/catalog/metadata.d.ts +244 -0
  9. package/dist/catalog/metadata.js +220 -0
  10. package/dist/catalog/properties.d.ts +38 -0
  11. package/dist/catalog/properties.js +52 -1
  12. package/dist/catalog/recommendations.d.ts +33 -6
  13. package/dist/catalog/recommendations.js +94 -2
  14. package/dist/catalog/reviews.d.ts +17 -4
  15. package/dist/catalog/reviews.js +7 -0
  16. package/dist/catalog/variants.d.ts +60 -0
  17. package/dist/catalog/variants.js +39 -0
  18. package/dist/catalog/visibility.d.ts +18 -2
  19. package/dist/client.d.ts +29 -3
  20. package/dist/client.js +19 -8
  21. package/dist/content/assets.d.ts +3 -4
  22. package/dist/content/forms.d.ts +332 -1
  23. package/dist/content/forms.js +140 -4
  24. package/dist/content/items.d.ts +31 -8
  25. package/dist/content/items.js +1 -1
  26. package/dist/content/menus.d.ts +254 -0
  27. package/dist/content/menus.js +70 -0
  28. package/dist/content/strings.d.ts +137 -0
  29. package/dist/content/strings.js +82 -0
  30. package/dist/customers/delivery-terms.d.ts +44 -0
  31. package/dist/customers/delivery-terms.js +32 -0
  32. package/dist/customers/groups.d.ts +16 -5
  33. package/dist/customers/groups.js +6 -2
  34. package/dist/customers/index.d.ts +15 -0
  35. package/dist/customers/index.js +4 -0
  36. package/dist/documents/emails.d.ts +154 -0
  37. package/dist/documents/emails.js +220 -0
  38. package/dist/documents/index.d.ts +13 -0
  39. package/dist/documents/index.js +2 -0
  40. package/dist/fulfillments/create.d.ts +1 -1
  41. package/dist/fulfillments/create.js +5 -2
  42. package/dist/fulfillments/index.d.ts +119 -1
  43. package/dist/fulfillments/index.js +124 -4
  44. package/dist/fulfillments/warehouse.d.ts +29 -17
  45. package/dist/fulfillments/warehouse.js +32 -1
  46. package/dist/index.d.ts +43 -23
  47. package/dist/index.js +11 -5
  48. package/dist/integrations/exact-online-types.d.ts +542 -0
  49. package/dist/integrations/exact-online.d.ts +541 -5
  50. package/dist/integrations/exact-online.js +714 -1
  51. package/dist/integrations/index.d.ts +23 -8
  52. package/dist/integrations/index.js +9 -4
  53. package/dist/integrations/pipeline-discoveries.d.ts +65 -0
  54. package/dist/integrations/pipeline-discoveries.js +65 -0
  55. package/dist/integrations/pipeline-runs.d.ts +119 -0
  56. package/dist/integrations/pipeline-runs.js +87 -0
  57. package/dist/integrations/pipelines.d.ts +73 -2
  58. package/dist/integrations/pipelines.js +36 -1
  59. package/dist/integrations/price-import.d.ts +223 -0
  60. package/dist/integrations/price-import.js +124 -0
  61. package/dist/integrations/types.d.ts +221 -3
  62. package/dist/integrations/types.js +4 -3
  63. package/dist/meta/index.d.ts +10 -0
  64. package/dist/metafields/index.d.ts +8 -1
  65. package/dist/metafields/index.js +10 -0
  66. package/dist/orders/cancel-with-money.d.ts +10 -0
  67. package/dist/orders/cancel-with-money.js +2 -0
  68. package/dist/orders/commit-edit.d.ts +16 -0
  69. package/dist/orders/commit-edit.js +14 -1
  70. package/dist/orders/deposit-policy.d.ts +3 -1
  71. package/dist/orders/index.d.ts +22 -11
  72. package/dist/orders/index.js +5 -2
  73. package/dist/orders/list.d.ts +26 -5
  74. package/dist/orders/list.js +123 -13
  75. package/dist/orders/manual-discounts.d.ts +13 -5
  76. package/dist/orders/money-view.d.ts +149 -0
  77. package/dist/orders/money-view.js +99 -0
  78. package/dist/orders/plan-edit.d.ts +34 -2
  79. package/dist/orders/plan-edit.js +110 -12
  80. package/dist/orders/surcharges.d.ts +12 -0
  81. package/dist/orders/surcharges.js +34 -9
  82. package/dist/payments/account-credit.d.ts +17 -1
  83. package/dist/payments/account-credit.js +25 -2
  84. package/dist/payments/credit-noted-fields.d.ts +8 -1
  85. package/dist/payments/credit-noted-fields.js +34 -2
  86. package/dist/payments/customer-consent.d.ts +30 -0
  87. package/dist/payments/customer-consent.js +49 -0
  88. package/dist/payments/index.d.ts +89 -14
  89. package/dist/payments/index.js +22 -2
  90. package/dist/payments/requests.d.ts +6 -3
  91. package/dist/payments/requests.js +1 -0
  92. package/dist/pickups/index.d.ts +5 -3
  93. package/dist/pickups/index.js +5 -2
  94. package/dist/platform/email-gateways.d.ts +165 -0
  95. package/dist/platform/email-gateways.js +78 -0
  96. package/dist/platform/tax.d.ts +11 -3
  97. package/dist/platform/tax.js +30 -0
  98. package/dist/pos/exchanges.d.ts +6 -1
  99. package/dist/pos/index.d.ts +229 -10
  100. package/dist/pos/index.js +113 -4
  101. package/dist/pos/offline-sales.d.ts +44 -3
  102. package/dist/pos/offline-sales.js +57 -3
  103. package/dist/pos/returns.d.ts +28 -1
  104. package/dist/pos/returns.js +36 -6
  105. package/dist/pricing/price-lists.d.ts +3 -1
  106. package/dist/purchasing/alerts.d.ts +19 -4
  107. package/dist/purchasing/alerts.js +29 -3
  108. package/dist/purchasing/backorders.d.ts +61 -0
  109. package/dist/purchasing/backorders.js +92 -0
  110. package/dist/purchasing/index.d.ts +21 -3
  111. package/dist/purchasing/index.js +8 -0
  112. package/dist/purchasing/links.d.ts +263 -0
  113. package/dist/purchasing/links.js +192 -0
  114. package/dist/purchasing/purchase-orders.d.ts +4 -1
  115. package/dist/purchasing/receipts.d.ts +396 -0
  116. package/dist/purchasing/receipts.js +287 -0
  117. package/dist/purchasing/returns.d.ts +273 -0
  118. package/dist/purchasing/returns.js +229 -0
  119. package/dist/quotes/index.d.ts +20 -3
  120. package/dist/returns/index.d.ts +62 -1
  121. package/dist/returns/index.js +61 -0
  122. package/dist/seo/config.d.ts +128 -0
  123. package/dist/seo/config.js +95 -0
  124. package/dist/seo/redirects.d.ts +125 -0
  125. package/dist/seo/redirects.js +60 -1
  126. package/dist/seo/settings.d.ts +147 -0
  127. package/dist/seo/settings.js +209 -0
  128. package/dist/shipping/index.d.ts +6 -0
  129. package/dist/shipping/index.js +2 -0
  130. package/dist/shipping/methods.d.ts +8 -1
  131. package/dist/shipping/myparcel.d.ts +415 -1
  132. package/dist/shipping/myparcel.js +291 -3
  133. package/dist/shipping/rate-tables.d.ts +149 -4
  134. package/dist/shipping/rate-tables.js +229 -23
  135. package/dist/shipping/ship-to-countries.d.ts +75 -0
  136. package/dist/shipping/ship-to-countries.js +47 -0
  137. package/dist/stock/by-location.d.ts +86 -0
  138. package/dist/stock/by-location.js +94 -0
  139. package/dist/stock/index.d.ts +8 -1
  140. package/dist/stock/index.js +4 -0
  141. package/dist/stock/settings.d.ts +106 -0
  142. package/dist/stock/settings.js +103 -0
  143. package/dist/stock/transfers.d.ts +171 -10
  144. package/dist/stock/transfers.js +82 -4
  145. package/dist/stored-value/index.d.ts +98 -7
  146. package/dist/stored-value/index.js +71 -4
  147. package/dist/tax/display-policy.d.ts +105 -0
  148. package/dist/tax/display-policy.js +84 -0
  149. package/dist/tax/index.d.ts +3 -0
  150. package/dist/tax/index.js +2 -0
  151. package/dist/tenant/email-templates.d.ts +222 -1
  152. package/dist/tenant/email-templates.js +227 -10
  153. package/package.json +2 -2
@@ -25,6 +25,13 @@ export interface CustomerGroup {
25
25
  canViewIncomingStock: boolean;
26
26
  /** Members may order more than is in stock, within the store's or the variant's backorder policy. */
27
27
  canBackorder: boolean;
28
+ /**
29
+ * "View only": signed-in members see the store's retail prices including VAT (the prices an
30
+ * anonymous shopper sees, never a price list's) and can fill a basket on the storefront, but
31
+ * checkout, payments and quote requests are refused (`VIEW_ONLY_ACCOUNT`). Staff can still
32
+ * place orders for them. The Exact customer import never links a customer to such a group.
33
+ */
34
+ viewOnly: boolean;
28
35
  /**
29
36
  * The store's own group, or its organization's (shared while the organization shares
30
37
  * `customer_groups`); see {@link CatalogueOwnership}. `editableHere` false: rename it, change
@@ -91,6 +98,7 @@ export interface CustomerGroupListItem {
91
98
  name: string;
92
99
  canViewIncomingStock: boolean;
93
100
  canBackorder: boolean;
101
+ viewOnly: boolean;
94
102
  /**
95
103
  * How many of the store's customers are in the group, present only when the call asked for
96
104
  * `include: ['memberCount']`.
@@ -134,12 +142,15 @@ export interface CreateCustomerGroupInput {
134
142
  canViewIncomingStock?: boolean;
135
143
  /** See {@link CustomerGroup.canBackorder}. Off when omitted. */
136
144
  canBackorder?: boolean;
145
+ /** See {@link CustomerGroup.viewOnly}. Off when omitted. */
146
+ viewOnly?: boolean;
137
147
  }
138
148
  /** A PATCH: an absent field keeps what the group has. */
139
149
  export interface UpdateCustomerGroupInput {
140
150
  name?: string;
141
151
  canViewIncomingStock?: boolean;
142
152
  canBackorder?: boolean;
153
+ viewOnly?: boolean;
143
154
  }
144
155
  /** A group by id and name. */
145
156
  export interface CustomerGroupRef {
@@ -168,9 +179,9 @@ export interface CustomerB2BGroupsModule {
168
179
  set(groupIds: readonly string[], options?: Call): Promise<CustomerGroupRef[]>;
169
180
  }
170
181
  /** A customer group as `get`, `create` and `update` read it. */
171
- export declare const CUSTOMER_GROUP_FIELDS = "\n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n";
172
- export declare const CUSTOMER_GROUP_QUERY = "\n query CustomerGroup($id: ID!) {\n customerGroup(id: $id) { \n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n }\n";
173
- export declare const CUSTOMER_GROUP_MEMBERS_QUERY = "\n query CustomerGroupWithMembers($id: ID!, $members: CustomerListOptions) {\n customerGroup(id: $id) {\n \n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n\n customers(options: $members) {\n items {\n id\n createdAt\n firstName\n lastName\n emailAddress\n addresses {\n company\n defaultBillingAddress\n defaultShippingAddress\n }\n }\n totalItems\n }\n }\n }\n";
182
+ export declare const CUSTOMER_GROUP_FIELDS = "\n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n viewOnly\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n";
183
+ export declare const CUSTOMER_GROUP_QUERY = "\n query CustomerGroup($id: ID!) {\n customerGroup(id: $id) { \n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n viewOnly\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n }\n";
184
+ export declare const CUSTOMER_GROUP_MEMBERS_QUERY = "\n query CustomerGroupWithMembers($id: ID!, $members: CustomerListOptions) {\n customerGroup(id: $id) {\n \n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n viewOnly\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n\n customers(options: $members) {\n items {\n id\n createdAt\n firstName\n lastName\n emailAddress\n addresses {\n company\n defaultBillingAddress\n defaultShippingAddress\n }\n }\n totalItems\n }\n }\n }\n";
174
185
  /**
175
186
  * The list document for a set of blocks. The member count is the `totalItems` of a one-row page
176
187
  * of the group's customers: a `take` of 0 would make that page every member.
@@ -184,8 +195,8 @@ export declare const CUSTOMER_GROUP_MEMBERS_QUERY = "\n query CustomerGroupWith
184
195
  * fix is `omit: ['customers']` on those two resolvers, which this package cannot make.
185
196
  */
186
197
  export declare function customerGroupListQuery(include: readonly CustomerGroupListInclude[]): string;
187
- export declare const CREATE_CUSTOMER_GROUP_MUTATION = "\n mutation CreateCustomerGroup($input: CreateCustomerGroupInput!) {\n createCustomerGroup(input: $input) { \n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n }\n";
188
- export declare const UPDATE_CUSTOMER_GROUP_MUTATION = "\n mutation UpdateCustomerGroup($input: UpdateCustomerGroupInput!) {\n updateCustomerGroup(input: $input) { \n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n }\n";
198
+ export declare const CREATE_CUSTOMER_GROUP_MUTATION = "\n mutation CreateCustomerGroup($input: CreateCustomerGroupInput!) {\n createCustomerGroup(input: $input) { \n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n viewOnly\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n }\n";
199
+ export declare const UPDATE_CUSTOMER_GROUP_MUTATION = "\n mutation UpdateCustomerGroup($input: UpdateCustomerGroupInput!) {\n updateCustomerGroup(input: $input) { \n id\n createdAt\n updatedAt\n name\n externalKey\n \n customFields {\n canViewIncomingStock\n canBackorder\n viewOnly\n }\n\n \n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n\n }\n }\n";
189
200
  export declare const DELETE_CUSTOMER_GROUP_MUTATION = "\n mutation DeleteCustomerGroup($id: ID!) {\n deleteCustomerGroup(id: $id) { \n result\n message\n }\n }\n";
190
201
  export declare const DELETE_CUSTOMER_GROUPS_MUTATION = "\n mutation DeleteCustomerGroups($ids: [ID!]!) {\n deleteCustomerGroups(ids: $ids) { \n result\n message\n }\n }\n";
191
202
  export declare const B2B_CUSTOMER_GROUPS_QUERY = "\n query CustomerGroupRefs {\n b2bCustomerGroups {\n id\n name\n }\n }\n";
@@ -16,10 +16,13 @@ export const MAX_CUSTOMER_GROUP_LIST_TAKE = 1000;
16
16
  export const CUSTOMER_GROUP_LIST_SORT_FIELDS = ['name', 'createdAt', 'updatedAt'];
17
17
  /** The blocks a list row can add: `memberCount`, how many of the store's customers are in it. */
18
18
  export const CUSTOMER_GROUP_LIST_INCLUDES = ['memberCount'];
19
+ /** The group's boolean flags, each a `CustomerGroup.customFields` field of the same name. */
20
+ const GROUP_FLAGS = ['canViewIncomingStock', 'canBackorder', 'viewOnly'];
19
21
  const GROUP_FLAG_FIELDS = /* GraphQL */ `
20
22
  customFields {
21
23
  canViewIncomingStock
22
24
  canBackorder
25
+ viewOnly
23
26
  }
24
27
  `;
25
28
  /** A customer group as `get`, `create` and `update` read it. */
@@ -144,11 +147,12 @@ const isPageSize = (take) => typeof take === 'number' &&
144
147
  take >= 1 &&
145
148
  take <= MAX_CUSTOMER_GROUP_LIST_TAKE;
146
149
  const isOffset = (skip) => typeof skip === 'number' && Number.isInteger(skip) && skip >= 0;
147
- /** Both flags are `false` unless the engine answers `true`: a group has no third state. */
150
+ /** Every flag is `false` unless the engine answers `true`: a group has no third state. */
148
151
  function flagsOf(wire) {
149
152
  return {
150
153
  canViewIncomingStock: wire.customFields?.canViewIncomingStock === true,
151
154
  canBackorder: wire.customFields?.canBackorder === true,
155
+ viewOnly: wire.customFields?.viewOnly === true,
152
156
  };
153
157
  }
154
158
  function toCustomerGroup(wire) {
@@ -341,7 +345,7 @@ export async function listCustomerGroups(transport, options) {
341
345
  /** Only the flags the caller set travel: an omitted flag is never sent as `null`. */
342
346
  function flagFields(input) {
343
347
  const customFields = {};
344
- for (const flag of ['canViewIncomingStock', 'canBackorder']) {
348
+ for (const flag of GROUP_FLAGS) {
345
349
  const value = input[flag];
346
350
  if (value === undefined)
347
351
  continue;
@@ -6,6 +6,7 @@ import type { Call, ListOptions } from '../types.js';
6
6
  import { type CustomerAgeVerification, type CustomerDateOfBirthModule } from './age-verification.js';
7
7
  import { type CustomerCreditModule } from './credit.js';
8
8
  import { type CustomerDeliveryPreferencesModule } from './delivery-preferences.js';
9
+ import { type CustomerDeliveryTerms } from './delivery-terms.js';
9
10
  import { type CreateCustomerGroupInput, type CustomerB2BGroupsModule, type CustomerGroup, type CustomerGroupListItem, type CustomerGroupListOptions, type CustomerGroupMembersOptions, type CustomerGroupRef, type CustomerGroupWithMembers, type UpdateCustomerGroupInput } from './groups.js';
10
11
  import { type CustomerInvitationStatus } from './invitation-status.js';
11
12
  import { type CustomerNotesModule } from './notes.js';
@@ -1026,6 +1027,20 @@ export interface CustomersModule {
1026
1027
  * @effect read
1027
1028
  */
1028
1029
  savedLists(customerId: string, options?: Call): Promise<CustomerSavedList[] | null>;
1030
+ /**
1031
+ * The customer's delivery terms as an order placed now would take them: the preferred and
1032
+ * allowed shipping methods, the Incoterm, the dispatch lead time and whether a partial delivery
1033
+ * is accepted, from whichever source governs them (an ERP such as Exact Online). `null` when
1034
+ * nothing governs the customer's delivery. A local read of what the source last synchronized;
1035
+ * it calls no ERP.
1036
+ *
1037
+ * Refused `USER_INPUT_ERROR` (`ValidationError`, 400) for a customer this store does not hold
1038
+ * (another store's included). Needs `ReadCustomer`.
1039
+ *
1040
+ * @effect read
1041
+ * @permission ReadCustomer
1042
+ */
1043
+ deliveryTerms(customerId: string, options?: Call): Promise<CustomerDeliveryTerms | null>;
1029
1044
  groups: CustomerGroupsModule;
1030
1045
  addresses: CustomerAddressesModule;
1031
1046
  /** A customer's B2B credit: account, limit, terms, status and what may be invoiced. */
@@ -6,6 +6,7 @@ import { listCustomerOrders, listCustomerRecentOrders, } from '../orders/list.js
6
6
  import { createCustomerDateOfBirthModule, readCustomerAgeVerification, } from './age-verification.js';
7
7
  import { createCustomerCreditModule } from './credit.js';
8
8
  import { createCustomerDeliveryPreferencesModule, } from './delivery-preferences.js';
9
+ import { readCustomerDeliveryTerms } from './delivery-terms.js';
9
10
  import { createCustomerB2BGroupsModule, createCustomerGroup, deleteCustomerGroup, deleteCustomerGroups, listCustomerGroups, readCustomerGroup, updateCustomerGroup, } from './groups.js';
10
11
  import { readCustomerInvitationStatus, } from './invitation-status.js';
11
12
  import { createCustomerNotesModule } from './notes.js';
@@ -616,6 +617,9 @@ export function createCustomersModule(transport) {
616
617
  savedLists(customerId, options) {
617
618
  return readCustomerSavedLists(transport, customerId, options);
618
619
  },
620
+ deliveryTerms(customerId, options) {
621
+ return readCustomerDeliveryTerms(transport, customerId, options);
622
+ },
619
623
  // The answer holds exactly the blocks asked for; `CustomerFor` decides how many of them the
620
624
  // caller's type of `include` may rely on. The overloads narrow this one implementation, which
621
625
  // TypeScript cannot check for them.
@@ -0,0 +1,154 @@
1
+ /**
2
+ * `admin.documents.emails` — the store's email delivery log, its counts, a retry of one delivery,
3
+ * and a test send of a template (headless spec §9.2 `documents`, "email sends"; W10-103).
4
+ *
5
+ * Every call needs `UpdateSettings` on the active store. A delivery's recipients, subject, error
6
+ * and metadata are customer data: below `customerData: full` (an app installation) they read
7
+ * `''`/`null` and the row carries `redacted: true`.
8
+ */
9
+ import { type Page, type Transport } from '@sixtyninecommerce/core';
10
+ import type { Call, ListOptions } from '../types.js';
11
+ /**
12
+ * Where a delivery stands. `skipped`: the store's `sender: none` for the event (the platform sent
13
+ * nothing, on purpose). `suppressed_sandbox`: a sandbox store withheld the customer's copy. The
14
+ * engine may add others; a reader keeps a default branch.
15
+ */
16
+ export type EmailDeliveryStatus = 'sent' | 'failed' | 'blocked' | 'pending' | 'skipped' | 'suppressed_sandbox' | (string & {});
17
+ /** One email the store sent, or tried to. */
18
+ export interface EmailDelivery {
19
+ id: string;
20
+ /** The template it was rendered from (`order_confirmation`, …). */
21
+ templateType: string;
22
+ /** The notification event that sent it (`order_placed`, …). */
23
+ notificationEvent: string;
24
+ /** Comma-separated addresses. */
25
+ recipientTo: string;
26
+ recipientCc: string | null;
27
+ recipientBcc: string | null;
28
+ subject: string;
29
+ gatewayId: string | null;
30
+ status: EmailDeliveryStatus;
31
+ /** 1 for the first send; each retry adds one. */
32
+ attempt: number;
33
+ /** The mail provider's message id, when it took the email. */
34
+ messageId: string | null;
35
+ /**
36
+ * Why the last attempt failed, as a coded sentence: `CODE: not sent (facts). Plain sentence.`
37
+ * (`SMTP_SEND_FAILED: not sent (code=EAUTH step=AUTH PLAIN reply=535). …`). Never the
38
+ * provider's or the mail server's own words, which can quote the gateway's user name or hosts.
39
+ */
40
+ error: string | null;
41
+ durationMs: number | null;
42
+ /** The ids the email was rendered from (`orderId`, `invoiceId`, …) and other send facts. */
43
+ metadata: Record<string, unknown> | null;
44
+ /** ISO-8601 UTC. */
45
+ createdAt: string;
46
+ /**
47
+ * Set when the caller's customer-data level withheld part of the row (an app installation
48
+ * below `customerData: full`): the recipients, subject, error and metadata then read `''` or
49
+ * `null`, meaning "not granted", never "none on record".
50
+ */
51
+ redacted?: true;
52
+ }
53
+ /**
54
+ * Which deliveries a list or a count covers. The two instants need an explicit offset (`Z` or
55
+ * `±HH:mm`) and are refused before the request leaves without one (`INVALID_DATETIME`): a store's
56
+ * day is not the caller's. To cover the store's days, send the first instant of the first day as
57
+ * `createdFrom` and the first instant of the day AFTER the last one as `createdBefore`
58
+ * ({@link EmailDeliveriesModule.list}).
59
+ */
60
+ export interface EmailDeliveryFilter {
61
+ status?: EmailDeliveryStatus;
62
+ templateType?: string;
63
+ /** Created at or after this instant. */
64
+ createdFrom?: string;
65
+ /** Created strictly before this instant. */
66
+ createdBefore?: string;
67
+ }
68
+ /** How many of the store's deliveries are in each state, over a filter. */
69
+ export interface EmailDeliveryStats {
70
+ sent: number;
71
+ failed: number;
72
+ /** Never sent: every recipient is on the mail provider's suppression list. */
73
+ blocked: number;
74
+ pending: number;
75
+ }
76
+ export interface SendTestEmailInput {
77
+ /** A template type the platform renders (`admin.tenant.emailTemplates.catalog()`). */
78
+ templateType: string;
79
+ /** One email address. */
80
+ recipientEmail: string;
81
+ }
82
+ export interface EmailDeliveriesModule {
83
+ /**
84
+ * The store's deliveries, newest first, one page at a time (`limit` 1–100, 50 when omitted).
85
+ *
86
+ * **Store days.** The page that shows "from 24 to 25 October" in Europe/Amsterdam sends
87
+ * `createdFrom: '2026-10-24T00:00:00+02:00'` and `createdBefore: '2026-10-26T00:00:00+01:00'`:
88
+ * 25 October 2026 is 25 hours long there (the clocks go back), and the end is exclusive, so
89
+ * the whole last day is in and nothing of the next.
90
+ *
91
+ * **Coded refusals:** `INVALID_DATETIME` → `ValidationError` (400), an instant without an
92
+ * offset, before the request leaves; `EMAIL_DELIVERY_FILTER_INVALID` → `ValidationError`
93
+ * (400), a `limit` outside 1–100 or a range that ends before it starts.
94
+ *
95
+ * Rows carry `redacted: true` below `customerData: full` (see {@link EmailDelivery}).
96
+ *
97
+ * @effect read
98
+ * @permission UpdateSettings
99
+ */
100
+ list(filter?: EmailDeliveryFilter, options?: ListOptions): Promise<Page<EmailDelivery>>;
101
+ /**
102
+ * How many deliveries are `sent`, `failed`, `blocked` and `pending`, over the same filter as
103
+ * {@link list} without its `status` (a count by status). Without a filter: the store's whole
104
+ * log. On an engine from before this release (no filter argument) a filtered count is made
105
+ * from the list's totals, one read per status.
106
+ *
107
+ * @effect read
108
+ * @permission UpdateSettings
109
+ */
110
+ stats(filter?: Omit<EmailDeliveryFilter, 'status'>, options?: Call): Promise<EmailDeliveryStats>;
111
+ /**
112
+ * Send a `failed` or `blocked` delivery again, now, and answer the row with the new outcome:
113
+ * `status` is `sent`, `failed` again (with its `error`), `blocked` (the address is still on the
114
+ * provider's suppression list) or `suppressed_sandbox`. It resolves either way; read `status`.
115
+ * Synchronous: the email has been handed to the provider (or not) when it resolves.
116
+ *
117
+ * This sends the email to the CUSTOMER (or whoever it was addressed to) once more. Never call
118
+ * it in a loop or on a timer without a person deciding to.
119
+ *
120
+ * **Coded refusals** (nothing is sent): `EMAIL_DELIVERY_NOT_FOUND` → `NotFoundError` (404), no
121
+ * such delivery in this store; `EMAIL_DELIVERY_NOT_RETRYABLE` → `BusinessRuleError` (422), the
122
+ * delivery is not `failed` or `blocked`, it is a gift card's email (its code is not kept), or
123
+ * another retry of it started first.
124
+ *
125
+ * @effect irreversible
126
+ * @permission UpdateSettings
127
+ */
128
+ retry(id: string, options?: Call): Promise<EmailDelivery>;
129
+ }
130
+ export interface DocumentEmailsModule {
131
+ deliveries: EmailDeliveriesModule;
132
+ /**
133
+ * Render `templateType` with sample data and send it, now, through the store's own email
134
+ * gateway, to `recipientEmail`. It is real mail. The send writes no delivery row.
135
+ *
136
+ * **Coded refusals:** `INVALID_TEST_EMAIL_RECIPIENT` and `INVALID_TEST_EMAIL_TEMPLATE` →
137
+ * `ValidationError` (400), before anything is sent; `TEST_EMAIL_SEND_FAILED` →
138
+ * `BusinessRuleError` (422), the gateway did not take it (the message names the SMTP step or
139
+ * the provider's code, never the server's own words).
140
+ *
141
+ * @effect irreversible
142
+ * @permission UpdateSettings
143
+ */
144
+ sendTest(input: SendTestEmailInput, options?: Call): Promise<void>;
145
+ }
146
+ export declare const EMAIL_DELIVERIES_QUERY = "\n query AdminEmailDeliveries($options: EmailDeliveryLogListOptions) {\n emailDeliveryLogs(options: $options) {\n items { \n id\n templateType\n notificationEvent\n recipientTo\n recipientCc\n recipientBcc\n subject\n gatewayId\n status\n attempt\n messageId\n error\n durationMs\n metadata\n createdAt\n }\n totalItems\n }\n }\n";
147
+ export declare const EMAIL_DELIVERY_STATS_QUERY = "\n query AdminEmailDeliveryStats($options: EmailDeliveryStatsOptions) {\n emailDeliveryStats(options: $options) {\n sent\n failed\n blocked\n pending\n }\n }\n";
148
+ /** The store's whole log, without the filter argument an older engine does not know. */
149
+ export declare const EMAIL_DELIVERY_STATS_ALL_QUERY = "\n query AdminEmailDeliveryStatsAll {\n emailDeliveryStats {\n sent\n failed\n blocked\n pending\n }\n }\n";
150
+ /** How many deliveries match: the older engine's way to count over a filter. */
151
+ export declare const EMAIL_DELIVERY_COUNT_QUERY = "\n query AdminEmailDeliveryCount($options: EmailDeliveryLogListOptions) {\n emailDeliveryLogs(options: $options) {\n totalItems\n }\n }\n";
152
+ export declare const RETRY_EMAIL_DELIVERY_MUTATION = "\n mutation AdminRetryEmailDelivery($id: ID!) {\n retryEmailDelivery(id: $id) { \n id\n templateType\n notificationEvent\n recipientTo\n recipientCc\n recipientBcc\n subject\n gatewayId\n status\n attempt\n messageId\n error\n durationMs\n metadata\n createdAt\n }\n }\n";
153
+ export declare const SEND_TEST_EMAIL_MUTATION = "\n mutation AdminSendTestEmail($templateType: String!, $recipientEmail: String!) {\n sendTestEmail(templateType: $templateType, recipientEmail: $recipientEmail)\n }\n";
154
+ export declare function createDocumentEmailsModule(transport: Transport): DocumentEmailsModule;
@@ -0,0 +1,220 @@
1
+ /**
2
+ * `admin.documents.emails` — the store's email delivery log, its counts, a retry of one delivery,
3
+ * and a test send of a template (headless spec §9.2 `documents`, "email sends"; W10-103).
4
+ *
5
+ * Every call needs `UpdateSettings` on the active store. A delivery's recipients, subject, error
6
+ * and metadata are customer data: below `customerData: full` (an app installation) they read
7
+ * `''`/`null` and the row carries `redacted: true`.
8
+ */
9
+ import { pageFromVendureList, readOffsetCursor, ValidationError, } from '@sixtyninecommerce/core';
10
+ import { assertInstant } from '../catalog/types.js';
11
+ import { customerDataField } from '../customers/wire.js';
12
+ const DELIVERY_FIELDS = /* GraphQL */ `
13
+ id
14
+ templateType
15
+ notificationEvent
16
+ recipientTo
17
+ recipientCc
18
+ recipientBcc
19
+ subject
20
+ gatewayId
21
+ status
22
+ attempt
23
+ messageId
24
+ error
25
+ durationMs
26
+ metadata
27
+ createdAt
28
+ `;
29
+ export const EMAIL_DELIVERIES_QUERY = /* GraphQL */ `
30
+ query AdminEmailDeliveries($options: EmailDeliveryLogListOptions) {
31
+ emailDeliveryLogs(options: $options) {
32
+ items { ${DELIVERY_FIELDS} }
33
+ totalItems
34
+ }
35
+ }
36
+ `;
37
+ export const EMAIL_DELIVERY_STATS_QUERY = /* GraphQL */ `
38
+ query AdminEmailDeliveryStats($options: EmailDeliveryStatsOptions) {
39
+ emailDeliveryStats(options: $options) {
40
+ sent
41
+ failed
42
+ blocked
43
+ pending
44
+ }
45
+ }
46
+ `;
47
+ /** The store's whole log, without the filter argument an older engine does not know. */
48
+ export const EMAIL_DELIVERY_STATS_ALL_QUERY = /* GraphQL */ `
49
+ query AdminEmailDeliveryStatsAll {
50
+ emailDeliveryStats {
51
+ sent
52
+ failed
53
+ blocked
54
+ pending
55
+ }
56
+ }
57
+ `;
58
+ /** How many deliveries match: the older engine's way to count over a filter. */
59
+ export const EMAIL_DELIVERY_COUNT_QUERY = /* GraphQL */ `
60
+ query AdminEmailDeliveryCount($options: EmailDeliveryLogListOptions) {
61
+ emailDeliveryLogs(options: $options) {
62
+ totalItems
63
+ }
64
+ }
65
+ `;
66
+ export const RETRY_EMAIL_DELIVERY_MUTATION = /* GraphQL */ `
67
+ mutation AdminRetryEmailDelivery($id: ID!) {
68
+ retryEmailDelivery(id: $id) { ${DELIVERY_FIELDS} }
69
+ }
70
+ `;
71
+ export const SEND_TEST_EMAIL_MUTATION = /* GraphQL */ `
72
+ mutation AdminSendTestEmail($templateType: String!, $recipientEmail: String!) {
73
+ sendTestEmail(templateType: $templateType, recipientEmail: $recipientEmail)
74
+ }
75
+ `;
76
+ /** The engine's default page; the SDK states it so the cursor arithmetic knows the size. */
77
+ const DELIVERIES_PAGE_SIZE = 50;
78
+ function toDelivery(row) {
79
+ return {
80
+ id: String(row.id),
81
+ templateType: row.templateType,
82
+ notificationEvent: row.notificationEvent,
83
+ recipientTo: row.recipientTo,
84
+ recipientCc: row.recipientCc ?? null,
85
+ recipientBcc: row.recipientBcc ?? null,
86
+ subject: row.subject,
87
+ gatewayId: row.gatewayId ?? null,
88
+ status: row.status,
89
+ attempt: row.attempt,
90
+ messageId: row.messageId ?? null,
91
+ error: row.error ?? null,
92
+ durationMs: row.durationMs ?? null,
93
+ metadata: row.metadata ?? null,
94
+ createdAt: row.createdAt,
95
+ };
96
+ }
97
+ /** The engine's filter input: only the keys the caller set, so an older engine still answers. */
98
+ function wireFilter(filter) {
99
+ if (filter === undefined)
100
+ return {};
101
+ if (filter === null || typeof filter !== 'object' || Array.isArray(filter)) {
102
+ throw new ValidationError({
103
+ code: 'EMAIL_DELIVERY_FILTER_INVALID',
104
+ message: 'The delivery filter must be an object.',
105
+ status: 400,
106
+ service: 'vendure',
107
+ });
108
+ }
109
+ assertInstant('createdFrom', filter.createdFrom);
110
+ assertInstant('createdBefore', filter.createdBefore);
111
+ return {
112
+ ...(filter.status ? { status: filter.status } : {}),
113
+ ...(filter.templateType ? { templateType: filter.templateType } : {}),
114
+ ...(filter.createdFrom ? { dateFrom: filter.createdFrom } : {}),
115
+ ...(filter.createdBefore ? { dateBefore: filter.createdBefore } : {}),
116
+ };
117
+ }
118
+ /**
119
+ * The engine refused the document over a field or argument it does not know: an engine from
120
+ * before this release (no `dateBefore`, no `emailDeliveryStats(options:)`). The dashboard can be
121
+ * live before vendure-server (`.claude/rules/backend.md`, additive rule), so the module falls
122
+ * back to the older spelling instead of failing. Matched on the names this module added, so a
123
+ * real refusal is never retried.
124
+ */
125
+ function isOlderEngineRejection(error) {
126
+ if (!(error instanceof ValidationError) || error.service !== 'vendure')
127
+ return false;
128
+ // Apollo answers a document that fails validation (an unknown type or argument: the stats
129
+ // filter) with HTTP 400, which the transport reports as BAD_REQUEST without the engine's
130
+ // words; an unknown input field (dateBefore) is a variable coercion error at HTTP 200 and
131
+ // keeps them. Only ever asked about the documents this module added the names to.
132
+ return (error.code === 'BAD_REQUEST' ||
133
+ error.code === 'GRAPHQL_VALIDATION_FAILED' ||
134
+ /dateBefore|EmailDeliveryStatsOptions|Unknown argument "options"/.test(error.message));
135
+ }
136
+ /** The old inclusive spelling of an exclusive end: one millisecond before it. */
137
+ function legacyFilter(filter) {
138
+ const { dateBefore, ...rest } = filter;
139
+ if (typeof dateBefore !== 'string')
140
+ return rest;
141
+ return { ...rest, dateTo: new Date(Date.parse(dateBefore) - 1).toISOString() };
142
+ }
143
+ const COUNTED_STATUSES = ['sent', 'failed', 'blocked', 'pending'];
144
+ export function createDocumentEmailsModule(transport) {
145
+ /** Latched once the engine proved it predates `dateBefore` (one refused request per client). */
146
+ let olderEngine = false;
147
+ const listPage = async (skip, take, filter, options) => {
148
+ const read = (wire) => customerDataField(transport, EMAIL_DELIVERIES_QUERY, 'emailDeliveryLogs', { options: { ...wire, take, skip } }, options);
149
+ let answer;
150
+ if (olderEngine || !('dateBefore' in filter)) {
151
+ answer = await read(olderEngine ? legacyFilter(filter) : filter);
152
+ }
153
+ else {
154
+ try {
155
+ answer = await read(filter);
156
+ }
157
+ catch (error) {
158
+ if (!isOlderEngineRejection(error))
159
+ throw error;
160
+ olderEngine = true;
161
+ answer = await read(legacyFilter(filter));
162
+ }
163
+ }
164
+ const { value, mark } = answer;
165
+ const items = value.items.map((row) => mark(toDelivery(row)));
166
+ // Each following page re-sends the same filter and per-call options.
167
+ return pageFromVendureList({ items, totalItems: value.totalItems }, skip, take, (cursor) => listPage(readOffsetCursor(cursor), take, filter, options));
168
+ };
169
+ return {
170
+ deliveries: {
171
+ async list(filter, options) {
172
+ const wire = wireFilter(filter);
173
+ const take = options?.limit ?? DELIVERIES_PAGE_SIZE;
174
+ const skip = options?.after === undefined ? 0 : readOffsetCursor(options.after);
175
+ return listPage(skip, take, wire, options);
176
+ },
177
+ async stats(filter, options) {
178
+ const { status: _status, ...counted } = wireFilter(filter);
179
+ if (Object.keys(counted).length === 0) {
180
+ const data = await transport.graphql({ api: 'admin', query: EMAIL_DELIVERY_STATS_ALL_QUERY }, options);
181
+ return data.emailDeliveryStats;
182
+ }
183
+ if (!olderEngine) {
184
+ try {
185
+ const data = await transport.graphql({ api: 'admin', query: EMAIL_DELIVERY_STATS_QUERY, variables: { options: counted } }, options);
186
+ return data.emailDeliveryStats;
187
+ }
188
+ catch (error) {
189
+ if (!isOlderEngineRejection(error))
190
+ throw error;
191
+ olderEngine = true;
192
+ }
193
+ }
194
+ // An older engine counts only the whole log: count each status over the filter through
195
+ // the list's totals instead (one row read per status).
196
+ const totals = await Promise.all(COUNTED_STATUSES.map(async (status) => {
197
+ const data = await transport.graphql({
198
+ api: 'admin',
199
+ query: EMAIL_DELIVERY_COUNT_QUERY,
200
+ variables: { options: { ...legacyFilter(counted), status, take: 1, skip: 0 } },
201
+ }, options);
202
+ return data.emailDeliveryLogs.totalItems;
203
+ }));
204
+ const [sent = 0, failed = 0, blocked = 0, pending = 0] = totals;
205
+ return { sent, failed, blocked, pending };
206
+ },
207
+ async retry(id, options) {
208
+ const { value, mark } = await customerDataField(transport, RETRY_EMAIL_DELIVERY_MUTATION, 'retryEmailDelivery', { id }, options);
209
+ return mark(toDelivery(value));
210
+ },
211
+ },
212
+ async sendTest(input, options) {
213
+ await transport.graphql({
214
+ api: 'admin',
215
+ query: SEND_TEST_EMAIL_MUTATION,
216
+ variables: { templateType: input.templateType, recipientEmail: input.recipientEmail },
217
+ }, options);
218
+ },
219
+ };
220
+ }
@@ -1,6 +1,7 @@
1
1
  import { type Page, type Transport } from '@sixtyninecommerce/core';
2
2
  import type { Call, ListOptions } from '../types.js';
3
3
  import { type ArDocumentsModule } from './ar.js';
4
+ import { type DocumentEmailsModule } from './emails.js';
4
5
  import { decodePdf } from './pdf.js';
5
6
  export { decodePdf };
6
7
  /**
@@ -402,6 +403,16 @@ export interface InvoiceCorrectionInput {
402
403
  refundPaymentId?: string;
403
404
  /** RETAIL only: move the credited amount to the customer's account instead (`accountCredit`). */
404
405
  creditToAccount?: boolean;
406
+ /**
407
+ * `commitCorrection` with `creditToAccount` in a store whose book is store credit (the plan's
408
+ * `accountCredit.book` `STORE_CREDIT`): whether the customer expressly agreed to store credit
409
+ * instead of a refund to their payment (EU consumer law, CRD art. 13(1)), as staff recorded it.
410
+ * The move records it on its ledger entries (`StoredValueEntry.customerAgreed`); never a
411
+ * condition, nothing is refused for it. The plan ignores it and its `planHash` never sees it, so
412
+ * leave it out of `planCorrection` (an engine before the field, incentives sprint 8 PR 6a,
413
+ * refuses any call that gives it) and add it to the commit's input.
414
+ */
415
+ customerAgreed?: boolean;
405
416
  /**
406
417
  * Details wrong only: the order's payment term in days. The corrected invoice is reissued
407
418
  * under it and the order keeps it (its `paymentTermsDays`; its payment condition is cleared).
@@ -1591,6 +1602,8 @@ export interface DocumentsModule {
1591
1602
  extensions: DocumentExtensionsModule;
1592
1603
  /** The accounts-receivable ledger's documents: AR invoices, statements and credit notes. */
1593
1604
  ar: ArDocumentsModule;
1605
+ /** The store's email delivery log (counts, a retry) and a template's test send. */
1606
+ emails: DocumentEmailsModule;
1594
1607
  }
1595
1608
  export declare const GENERATE_INVOICE_MUTATION = "\n mutation GenerateInvoice(\n $orderId: ID!\n $invoicedOn: String!\n $languageCode: String\n $note: String\n $extensions: [DocumentExtensionInput!]\n ) {\n generateInvoice(\n orderId: $orderId\n invoicedOn: $invoicedOn\n languageCode: $languageCode\n note: $note\n extensions: $extensions\n ) { \n id\n invoiceNumber\n invoicedOn\n orderId\n languageCode\n storageKey\n totalWithTax\n currencyCode\n note\n voidedOn\n reversedByCreditNoteId\n supersededByInvoiceId\n reissueOfInvoiceId\n kind\n supplementsInvoiceId\n lines { \n description\n sku\n orderLineId\n quantity\n unitPriceNet\n unitPriceWithTax\n taxRate\n lineTotalNet\n lineTotalWithTax\n unitPriceBasis\n }\n numberingProvider\n amountPaid\n amountDue\n createdAt\n }\n }\n";
1596
1609
  export declare const GENERATE_PACKING_SLIP_MUTATION = "\n mutation GeneratePackingSlip(\n $fulfillmentId: ID!\n $languageCode: String\n $extensions: [DocumentExtensionInput!]\n ) {\n generatePackingSlip(\n fulfillmentId: $fulfillmentId\n languageCode: $languageCode\n extensions: $extensions\n ) { \n id\n packingSlipNumber\n fulfillmentId\n languageCode\n storageKey\n createdAt\n }\n }\n";
@@ -1,6 +1,7 @@
1
1
  import { createPage, idempotencyKey as mintIdempotencyKey, pageFromVendureList, readOffsetCursor, ValidationError, } from '@sixtyninecommerce/core';
2
2
  import { storeCreditAware } from '../payments/store-credit-fields.js';
3
3
  import { createArDocumentsModule } from './ar.js';
4
+ import { createDocumentEmailsModule } from './emails.js';
4
5
  import { decodePdf } from './pdf.js';
5
6
  // Also what `documents.ar.pdf` decodes with (headless W10-30).
6
7
  export { decodePdf };
@@ -903,6 +904,7 @@ export function createDocumentsModule(transport) {
903
904
  },
904
905
  },
905
906
  ar: createArDocumentsModule(transport),
907
+ emails: createDocumentEmailsModule(transport),
906
908
  extensions: {
907
909
  async list(options) {
908
910
  const data = await transport.graphql({ api: 'admin', query: DOCUMENT_EXTENSIONS_QUERY }, options);
@@ -162,6 +162,6 @@ export interface FulfillmentCreateMethods {
162
162
  */
163
163
  profileChargePreview(input: FulfillmentProfileChargePreviewInput, options?: Call): Promise<FulfillmentProfileChargePreview>;
164
164
  }
165
- export declare const CREATE_WAREHOUSE_FULFILLMENTS_MUTATION = "\n mutation CreateWarehouseFulfillments(\n $input: CreateWarehouseFulfillmentsInput!\n $idempotencyKey: String\n ) {\n createWarehouseFulfillments(input: $input, idempotencyKey: $idempotencyKey) {\n __typename\n ... on WarehouseCreateResult {\n created\n failed\n results {\n index\n fulfillment {\n \n id\n state\n handlerCode\n method\n mode\n trackingCode\n stockLocationId\n stockLocationName\n createdAt\n stateChangedAt\n overdue\n claimedBy {\n id\n name\n }\n claimedAt\n packedAt\n bookingStatus\n bookingError\n parcels {\n weightGrams\n lengthMm\n widthMm\n heightMm\n }\n orders {\n id\n code\n poNumber\n orderNotes\n requestedDeliveryDate\n deliveryDue\n paymentBadge\n hold {\n reason\n note\n }\n }\n customer {\n id\n name\n phone\n }\n shippingAddress {\n \n fullName\n company\n streetLine1\n streetLine2\n city\n province\n postalCode\n countryCode\n\n }\n lineCount\n unitCount\n\n }\n error {\n code\n message\n field\n }\n claimError {\n code\n message\n field\n }\n }\n }\n ... on WarehouseError {\n errorCode\n code\n message\n field\n }\n }\n }\n";
165
+ export declare const CREATE_WAREHOUSE_FULFILLMENTS_MUTATION = "\n mutation CreateWarehouseFulfillments(\n $input: CreateWarehouseFulfillmentsInput!\n $idempotencyKey: String\n ) {\n createWarehouseFulfillments(input: $input, idempotencyKey: $idempotencyKey) {\n __typename\n ... on WarehouseCreateResult {\n created\n failed\n results {\n index\n fulfillment {\n \n id\n state\n handlerCode\n method\n mode\n trackingCode\n stockLocationId\n stockLocationName\n dropShipSupplierName\n createdAt\n stateChangedAt\n overdue\n claimedBy {\n id\n name\n }\n claimedAt\n packedAt\n bookingStatus\n bookingError\n parcels {\n weightGrams\n lengthMm\n widthMm\n heightMm\n }\n orders {\n id\n code\n poNumber\n orderNotes\n requestedDeliveryDate\n deliveryDue\n paymentBadge\n hold {\n reason\n note\n }\n }\n customer {\n id\n name\n phone\n }\n shippingAddress {\n \n fullName\n company\n streetLine1\n streetLine2\n city\n province\n postalCode\n countryCode\n\n }\n lineCount\n unitCount\n\n }\n error {\n code\n message\n field\n }\n claimError {\n code\n message\n field\n }\n }\n }\n ... on WarehouseError {\n errorCode\n code\n message\n field\n }\n }\n }\n";
166
166
  export declare const FULFILLMENT_PROFILE_CHARGE_PREVIEW_QUERY = "\n query FulfillmentProfileChargePreview($input: FulfillmentProfileChargePreviewInput!) {\n fulfillmentProfileChargePreview(input: $input) {\n profileName\n currencyCode\n minOrder\n selectedNet\n shortfall\n deliveryCharge\n deliveryChargeIncludesTax\n settled\n handedOver\n }\n }\n";
167
167
  export declare function createFulfillmentCreateMethods(transport: Transport): FulfillmentCreateMethods;
@@ -9,7 +9,7 @@
9
9
  * 400, never a 422). Each group's own refusal is data in `results[].error`.
10
10
  */
11
11
  import { idempotencyKey as mintIdempotencyKey, ValidationError, } from '@sixtyninecommerce/core';
12
- import { assertId, FULFILLMENT_FIELDS, invalid, isWarehouseErrorMember, toFulfillment, toRefusal, warehouseRefusalError, withAttemptKey, } from './warehouse.js';
12
+ import { assertId, dropShipSupplierAware, FULFILLMENT_FIELDS, invalid, isWarehouseErrorMember, toFulfillment, toRefusal, warehouseRefusalError, withAttemptKey, } from './warehouse.js';
13
13
  export const CREATE_WAREHOUSE_FULFILLMENTS_MUTATION = /* GraphQL */ `
14
14
  mutation CreateWarehouseFulfillments(
15
15
  $input: CreateWarehouseFulfillmentsInput!
@@ -134,12 +134,15 @@ function assertPreviewInput(input) {
134
134
  }
135
135
  }
136
136
  export function createFulfillmentCreateMethods(transport) {
137
+ // The create answers the warehouse's fulfilment fields, `dropShipSupplierName` among them: an
138
+ // engine before purchase-orders step 5.5 refuses that field before the mutation runs.
139
+ const engine = dropShipSupplierAware(transport);
137
140
  return {
138
141
  async create(input, options) {
139
142
  assertCreateInput(input);
140
143
  const key = options?.idempotencyKey ?? mintIdempotencyKey();
141
144
  try {
142
- const data = await transport.graphql({
145
+ const data = await engine.graphql({
143
146
  api: 'admin',
144
147
  query: CREATE_WAREHOUSE_FULFILLMENTS_MUTATION,
145
148
  variables: {