@sixtyninecommerce/admin 0.11.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.
Files changed (147) hide show
  1. package/README.md +1501 -23
  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/index.d.ts +15 -0
  33. package/dist/customers/index.js +4 -0
  34. package/dist/documents/emails.d.ts +150 -0
  35. package/dist/documents/emails.js +220 -0
  36. package/dist/documents/index.d.ts +13 -0
  37. package/dist/documents/index.js +2 -0
  38. package/dist/fulfillments/index.d.ts +119 -1
  39. package/dist/fulfillments/index.js +124 -4
  40. package/dist/index.d.ts +43 -23
  41. package/dist/index.js +11 -5
  42. package/dist/integrations/exact-online-types.d.ts +542 -0
  43. package/dist/integrations/exact-online.d.ts +541 -5
  44. package/dist/integrations/exact-online.js +714 -1
  45. package/dist/integrations/index.d.ts +23 -8
  46. package/dist/integrations/index.js +9 -4
  47. package/dist/integrations/pipeline-discoveries.d.ts +65 -0
  48. package/dist/integrations/pipeline-discoveries.js +65 -0
  49. package/dist/integrations/pipeline-runs.d.ts +119 -0
  50. package/dist/integrations/pipeline-runs.js +87 -0
  51. package/dist/integrations/pipelines.d.ts +73 -2
  52. package/dist/integrations/pipelines.js +36 -1
  53. package/dist/integrations/price-import.d.ts +223 -0
  54. package/dist/integrations/price-import.js +124 -0
  55. package/dist/integrations/types.d.ts +221 -3
  56. package/dist/integrations/types.js +4 -3
  57. package/dist/meta/index.d.ts +10 -0
  58. package/dist/metafields/index.d.ts +8 -1
  59. package/dist/metafields/index.js +10 -0
  60. package/dist/orders/cancel-with-money.d.ts +10 -0
  61. package/dist/orders/cancel-with-money.js +2 -0
  62. package/dist/orders/commit-edit.d.ts +16 -0
  63. package/dist/orders/commit-edit.js +14 -1
  64. package/dist/orders/deposit-policy.d.ts +3 -1
  65. package/dist/orders/index.d.ts +22 -11
  66. package/dist/orders/index.js +5 -2
  67. package/dist/orders/list.d.ts +26 -5
  68. package/dist/orders/list.js +123 -13
  69. package/dist/orders/manual-discounts.d.ts +13 -5
  70. package/dist/orders/money-view.d.ts +149 -0
  71. package/dist/orders/money-view.js +99 -0
  72. package/dist/orders/plan-edit.d.ts +34 -2
  73. package/dist/orders/plan-edit.js +110 -12
  74. package/dist/orders/surcharges.d.ts +12 -0
  75. package/dist/orders/surcharges.js +34 -9
  76. package/dist/payments/account-credit.d.ts +17 -1
  77. package/dist/payments/account-credit.js +25 -2
  78. package/dist/payments/credit-noted-fields.d.ts +8 -1
  79. package/dist/payments/credit-noted-fields.js +34 -2
  80. package/dist/payments/customer-consent.d.ts +30 -0
  81. package/dist/payments/customer-consent.js +49 -0
  82. package/dist/payments/index.d.ts +89 -14
  83. package/dist/payments/index.js +22 -2
  84. package/dist/payments/requests.d.ts +6 -3
  85. package/dist/payments/requests.js +1 -0
  86. package/dist/pickups/index.d.ts +5 -3
  87. package/dist/pickups/index.js +5 -2
  88. package/dist/platform/email-gateways.d.ts +165 -0
  89. package/dist/platform/email-gateways.js +78 -0
  90. package/dist/platform/tax.d.ts +11 -3
  91. package/dist/platform/tax.js +30 -0
  92. package/dist/pos/exchanges.d.ts +6 -1
  93. package/dist/pos/index.d.ts +229 -10
  94. package/dist/pos/index.js +113 -4
  95. package/dist/pos/offline-sales.d.ts +44 -3
  96. package/dist/pos/offline-sales.js +57 -3
  97. package/dist/pos/returns.d.ts +28 -1
  98. package/dist/pos/returns.js +36 -6
  99. package/dist/pricing/price-lists.d.ts +3 -1
  100. package/dist/purchasing/alerts.d.ts +19 -4
  101. package/dist/purchasing/alerts.js +29 -3
  102. package/dist/purchasing/backorders.d.ts +61 -0
  103. package/dist/purchasing/backorders.js +92 -0
  104. package/dist/purchasing/index.d.ts +21 -3
  105. package/dist/purchasing/index.js +8 -0
  106. package/dist/purchasing/links.d.ts +263 -0
  107. package/dist/purchasing/links.js +192 -0
  108. package/dist/purchasing/purchase-orders.d.ts +4 -1
  109. package/dist/purchasing/receipts.d.ts +396 -0
  110. package/dist/purchasing/receipts.js +287 -0
  111. package/dist/purchasing/returns.d.ts +273 -0
  112. package/dist/purchasing/returns.js +229 -0
  113. package/dist/quotes/index.d.ts +20 -3
  114. package/dist/returns/index.d.ts +62 -1
  115. package/dist/returns/index.js +61 -0
  116. package/dist/seo/config.d.ts +128 -0
  117. package/dist/seo/config.js +95 -0
  118. package/dist/seo/redirects.d.ts +125 -0
  119. package/dist/seo/redirects.js +60 -1
  120. package/dist/seo/settings.d.ts +147 -0
  121. package/dist/seo/settings.js +209 -0
  122. package/dist/shipping/index.d.ts +6 -0
  123. package/dist/shipping/index.js +2 -0
  124. package/dist/shipping/methods.d.ts +8 -1
  125. package/dist/shipping/myparcel.d.ts +415 -1
  126. package/dist/shipping/myparcel.js +291 -3
  127. package/dist/shipping/rate-tables.d.ts +149 -4
  128. package/dist/shipping/rate-tables.js +229 -23
  129. package/dist/shipping/ship-to-countries.d.ts +75 -0
  130. package/dist/shipping/ship-to-countries.js +47 -0
  131. package/dist/stock/by-location.d.ts +86 -0
  132. package/dist/stock/by-location.js +94 -0
  133. package/dist/stock/index.d.ts +8 -1
  134. package/dist/stock/index.js +4 -0
  135. package/dist/stock/settings.d.ts +106 -0
  136. package/dist/stock/settings.js +103 -0
  137. package/dist/stock/transfers.d.ts +171 -10
  138. package/dist/stock/transfers.js +82 -4
  139. package/dist/stored-value/index.d.ts +98 -7
  140. package/dist/stored-value/index.js +71 -4
  141. package/dist/tax/display-policy.d.ts +105 -0
  142. package/dist/tax/display-policy.js +84 -0
  143. package/dist/tax/index.d.ts +3 -0
  144. package/dist/tax/index.js +2 -0
  145. package/dist/tenant/email-templates.d.ts +222 -1
  146. package/dist/tenant/email-templates.js +227 -10
  147. package/package.json +2 -2
@@ -0,0 +1,229 @@
1
+ import { pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
2
+ export const SUPPLIER_RETURN_LINE_FIELDS = /* GraphQL */ `
3
+ id
4
+ supplierReturnId
5
+ position
6
+ goodsReceiptId
7
+ goodsReceiptCode
8
+ goodsReceiptLineId
9
+ purchaseOrderId
10
+ purchaseOrderCode
11
+ purchaseOrderLineId
12
+ productVariantId
13
+ sku
14
+ name
15
+ supplierSku
16
+ purchaseUnitLabel
17
+ purchaseUnitFactor
18
+ baseQuantity
19
+ reason
20
+ outcome
21
+ resolution
22
+ currencyCode
23
+ creditAmount
24
+ resolutionReference
25
+ unitCost
26
+ cancelledBaseQuantity
27
+ reopenedBaseQuantity
28
+ resolvedAt
29
+ resolvedByRef
30
+ stockMovementId
31
+ createdAt
32
+ `;
33
+ export const SUPPLIER_RETURN_FIELDS = /* GraphQL */ `
34
+ id
35
+ scopeId
36
+ legalEntityId
37
+ originChannelId
38
+ code
39
+ status
40
+ origin
41
+ supplierId
42
+ supplier { id code name status }
43
+ stockLocationId
44
+ stockLocation { id name }
45
+ supplierReference
46
+ notesToSupplier
47
+ carrier
48
+ trackingCode
49
+ shippedAt
50
+ resolvedAt
51
+ managedBy
52
+ externalId
53
+ createdByRef
54
+ documentIssuedAt
55
+ lines { ${SUPPLIER_RETURN_LINE_FIELDS} }
56
+ statusHistory { id fromStatus status actorRef reason metadata createdAt }
57
+ createdAt
58
+ updatedAt
59
+ `;
60
+ export const SUPPLIER_RETURNS_QUERY = /* GraphQL */ `
61
+ query SupplierReturns($options: SupplierReturnListOptions) {
62
+ supplierReturns(options: $options) {
63
+ items { ${SUPPLIER_RETURN_FIELDS} }
64
+ totalItems
65
+ }
66
+ }
67
+ `;
68
+ export const SUPPLIER_RETURN_QUERY = /* GraphQL */ `
69
+ query SupplierReturn($id: ID!) {
70
+ supplierReturn(id: $id) { ${SUPPLIER_RETURN_FIELDS} }
71
+ }
72
+ `;
73
+ export const CREATE_SUPPLIER_RETURN_MUTATION = /* GraphQL */ `
74
+ mutation CreateSupplierReturn($input: CreateSupplierReturnInput!, $idempotencyKey: String) {
75
+ createSupplierReturn(input: $input, idempotencyKey: $idempotencyKey) {
76
+ ${SUPPLIER_RETURN_FIELDS}
77
+ }
78
+ }
79
+ `;
80
+ export const UPDATE_SUPPLIER_RETURN_MUTATION = /* GraphQL */ `
81
+ mutation UpdateSupplierReturn($input: UpdateSupplierReturnInput!, $idempotencyKey: String) {
82
+ updateSupplierReturn(input: $input, idempotencyKey: $idempotencyKey) {
83
+ ${SUPPLIER_RETURN_FIELDS}
84
+ }
85
+ }
86
+ `;
87
+ export const SHIP_SUPPLIER_RETURN_MUTATION = /* GraphQL */ `
88
+ mutation ShipSupplierReturn($input: ShipSupplierReturnInput!, $idempotencyKey: String!) {
89
+ shipSupplierReturn(input: $input, idempotencyKey: $idempotencyKey) {
90
+ ${SUPPLIER_RETURN_FIELDS}
91
+ }
92
+ }
93
+ `;
94
+ export const RESOLVE_SUPPLIER_RETURN_LINE_MUTATION = /* GraphQL */ `
95
+ mutation ResolveSupplierReturnLine(
96
+ $input: ResolveSupplierReturnLineInput!
97
+ $idempotencyKey: String
98
+ ) {
99
+ resolveSupplierReturnLine(input: $input, idempotencyKey: $idempotencyKey) {
100
+ ${SUPPLIER_RETURN_FIELDS}
101
+ }
102
+ }
103
+ `;
104
+ export const CANCEL_SUPPLIER_RETURN_MUTATION = /* GraphQL */ `
105
+ mutation CancelSupplierReturn($id: ID!, $reason: String, $idempotencyKey: String) {
106
+ cancelSupplierReturn(id: $id, reason: $reason, idempotencyKey: $idempotencyKey) {
107
+ ${SUPPLIER_RETURN_FIELDS}
108
+ }
109
+ }
110
+ `;
111
+ export const CREATE_SUPPLIER_RETURN_DOCUMENT_URL_MUTATION = /* GraphQL */ `
112
+ mutation CreateSupplierReturnDocumentUrl($supplierReturnId: ID!) {
113
+ createSupplierReturnDocumentUrl(supplierReturnId: $supplierReturnId) {
114
+ url
115
+ expiresAt
116
+ filename
117
+ }
118
+ }
119
+ `;
120
+ const DEFAULT_PAGE_SIZE = 25;
121
+ const LIST_FILTERS = [
122
+ 'status',
123
+ 'origin',
124
+ 'supplierId',
125
+ 'stockLocationId',
126
+ 'purchaseOrderId',
127
+ 'goodsReceiptId',
128
+ 'term',
129
+ ];
130
+ const LINE_INPUT_FIELDS = [
131
+ 'goodsReceiptLineId',
132
+ 'baseQuantity',
133
+ 'reason',
134
+ 'outcome',
135
+ ];
136
+ const CREATE_INPUT_FIELDS = [
137
+ 'origin',
138
+ 'supplierId',
139
+ 'stockLocationId',
140
+ 'supplierReference',
141
+ 'notesToSupplier',
142
+ 'carrier',
143
+ 'trackingCode',
144
+ ];
145
+ const UPDATE_INPUT_FIELDS = [
146
+ 'stockLocationId',
147
+ 'supplierReference',
148
+ 'notesToSupplier',
149
+ 'carrier',
150
+ 'trackingCode',
151
+ ];
152
+ const SHIP_INPUT_FIELDS = [
153
+ 'carrier',
154
+ 'trackingCode',
155
+ 'notifySupplier',
156
+ ];
157
+ const RESOLVE_INPUT_FIELDS = [
158
+ 'supplierReturnLineId',
159
+ 'resolution',
160
+ 'creditAmount',
161
+ 'resolutionReference',
162
+ ];
163
+ function picked(input, fields) {
164
+ const out = {};
165
+ for (const field of fields) {
166
+ const value = input[field];
167
+ if (value !== undefined)
168
+ out[field] = value;
169
+ }
170
+ return out;
171
+ }
172
+ export function createPurchasingReturnsModule(transport) {
173
+ const listPage = async (skip, take, options) => {
174
+ const filters = options ? picked(options, LIST_FILTERS) : {};
175
+ const data = await transport.graphql({
176
+ api: 'admin',
177
+ query: SUPPLIER_RETURNS_QUERY,
178
+ variables: { options: { skip, take, ...filters } },
179
+ }, options);
180
+ return pageFromVendureList(data.supplierReturns, skip, take, (cursor) => listPage(readOffsetCursor(cursor), take, options));
181
+ };
182
+ const mutate = async (field, query, variables, call) => {
183
+ const data = await transport.graphql({ api: 'admin', query, variables: { ...variables, idempotencyKey: call?.idempotencyKey } }, call);
184
+ return data[field];
185
+ };
186
+ return {
187
+ async list(options) {
188
+ const take = options?.limit ?? DEFAULT_PAGE_SIZE;
189
+ const skip = options?.after === undefined ? 0 : readOffsetCursor(options.after);
190
+ return listPage(skip, take, options);
191
+ },
192
+ async get(id, call) {
193
+ const data = await transport.graphql({ api: 'admin', query: SUPPLIER_RETURN_QUERY, variables: { id } }, call);
194
+ return data.supplierReturn;
195
+ },
196
+ create(input, call) {
197
+ return mutate('createSupplierReturn', CREATE_SUPPLIER_RETURN_MUTATION, {
198
+ input: {
199
+ ...picked(input, CREATE_INPUT_FIELDS),
200
+ lines: input.lines.map((line) => picked(line, LINE_INPUT_FIELDS)),
201
+ },
202
+ }, call);
203
+ },
204
+ update(id, input, call) {
205
+ const fields = { ...picked(input, UPDATE_INPUT_FIELDS), id };
206
+ if (input.lines !== undefined) {
207
+ fields.lines = input.lines.map((line) => picked(line, LINE_INPUT_FIELDS));
208
+ }
209
+ return mutate('updateSupplierReturn', UPDATE_SUPPLIER_RETURN_MUTATION, { input: fields }, call);
210
+ },
211
+ ship(id, input, call) {
212
+ return mutate('shipSupplierReturn', SHIP_SUPPLIER_RETURN_MUTATION, { input: { ...picked(input, SHIP_INPUT_FIELDS), id } }, call);
213
+ },
214
+ resolveLine(input, call) {
215
+ return mutate('resolveSupplierReturnLine', RESOLVE_SUPPLIER_RETURN_LINE_MUTATION, { input: picked(input, RESOLVE_INPUT_FIELDS) }, call);
216
+ },
217
+ cancel(id, options) {
218
+ return mutate('cancelSupplierReturn', CANCEL_SUPPLIER_RETURN_MUTATION, { id, ...(options?.reason === undefined ? {} : { reason: options.reason }) }, options);
219
+ },
220
+ async document(id, call) {
221
+ const data = await transport.graphql({
222
+ api: 'admin',
223
+ query: CREATE_SUPPLIER_RETURN_DOCUMENT_URL_MUTATION,
224
+ variables: { supplierReturnId: id },
225
+ }, call);
226
+ return data.createSupplierReturnDocumentUrl;
227
+ },
228
+ };
229
+ }
@@ -65,16 +65,27 @@ export interface QuoteLine {
65
65
  discountValue: number;
66
66
  discountLabel: string | null;
67
67
  position: number;
68
- /** The whole-line discount, in the store's primary tax mode. Null while unpriced. */
68
+ /**
69
+ * What the converted order takes off the line, in the store's primary tax mode: (`unitPrice` −
70
+ * `effectiveUnitPrice`) × quantity. It can differ from the typed discount by the per-unit
71
+ * rounding (€1.00 off 3 × €10.00 takes €0.99). Null while unpriced.
72
+ */
69
73
  discountAmount: number | null;
70
74
  /**
71
75
  * The per-unit price the converted order line gets: the discounted line total ÷ quantity,
72
76
  * rounded. The figure to show as the line's unit price. Null while unpriced.
73
77
  */
74
78
  effectiveUnitPrice: number | null;
75
- /** Net line total after discount. Null while unpriced or when the variant is unavailable. */
79
+ /**
80
+ * Net total of the order line the quote converts into. Null while unpriced or when the variant
81
+ * is unavailable.
82
+ */
76
83
  lineTotal: number | null;
77
- /** Gross line total after discount. Null while unpriced or when the variant is unavailable. */
84
+ /**
85
+ * Gross total of the order line the quote converts into, at `taxRate`: the order charges
86
+ * exactly this unless its customer is taxed in another zone or reverse-charged (conversion
87
+ * then records a `priceNotice`). Null while unpriced or when the variant is unavailable.
88
+ */
78
89
  lineTotalWithTax: number | null;
79
90
  /** `lineTotalWithTax − lineTotal`. Null as those are. */
80
91
  taxAmount: number | null;
@@ -95,6 +106,12 @@ export interface QuoteStatusLogEntry {
95
106
  status: QuoteStatus;
96
107
  createdAt: string;
97
108
  actorUserId: string | null;
109
+ /**
110
+ * What the change recorded. An `approved` row carries the offer the customer was sent
111
+ * (`offeredSubTotal`, `offeredTotalWithTax`); a `converted_to_order` row the order
112
+ * (`orderId`, `orderCode`), both totals, and `priceNotice`, a plain-words sentence, when the
113
+ * order does not total what the quote offered (the order's amount applies).
114
+ */
98
115
  metadata: Record<string, unknown> | null;
99
116
  }
100
117
  export interface QuoteSalesRepresentative {
@@ -269,6 +269,26 @@ export interface SettleReturnInput {
269
269
  shippingRefundAmount?: number | null;
270
270
  /** Recorded on the refund and the return's history. */
271
271
  reason?: string | null;
272
+ /**
273
+ * `account` in a store whose book is store credit (`settlementOptions`' `account.book`
274
+ * `STORE_CREDIT`): whether the customer expressly agreed to store credit instead of a refund to
275
+ * their payment (EU consumer law, CRD art. 13(1)), as staff recorded it. The move records it on
276
+ * its ledger entries (`StoredValueEntry.customerAgreed`); never a condition, nothing is refused
277
+ * for it. Part of the request: a repeat under the same key sends the same answer. Sent only when
278
+ * given; an engine before the field (incentives sprint 8 PR 6a) refuses a call that gives it.
279
+ */
280
+ customerAgreed?: boolean | null;
281
+ /**
282
+ * The VAT rate, a percentage from 0 to 100, that the return's invoice credit note states: the
283
+ * note splits its lump sum at that rate (USER PD11). {@link ReturnSettlementOptions.creditNoteTaxRate}
284
+ * suggests one and lists the rates to pick. Used only where the settlement issues an invoice
285
+ * credit note (the methods `creditNoteTaxRate.methods` names). Left out, the note splits as the
286
+ * order allows. Outside 0 to 100:
287
+ * `RETURN_SETTLE_INPUT_INVALID` (`details[0].code` `NOT_A_RATE`) before anything moves. Part of
288
+ * the request: a repeat under the same key sends the same rate. Sent only when given; an engine
289
+ * before the field (whose plan has no `creditNoteTaxRate`) refuses a call that gives it.
290
+ */
291
+ taxRate?: number | null;
272
292
  }
273
293
  /** The store's RMA code format: `{prefix}{year}{number}{suffix}`, e.g. `RMA-000042`. */
274
294
  export interface ReturnNumberingConfig {
@@ -433,6 +453,34 @@ export interface ReturnSettlementAccountOption {
433
453
  */
434
454
  book?: 'AR' | 'STORE_CREDIT' | 'NONE' | (string & {}) | null;
435
455
  }
456
+ /** Why the plan suggests the credit note's VAT rate it does. */
457
+ export type ReturnCreditNoteTaxRateBasis = 'RETURNED_GOODS' | 'ORDER_MAIN_RATE';
458
+ /** The VAT rate a return's invoice credit note states, which staff pick (USER PD11). */
459
+ export interface ReturnCreditNoteTaxRateOption {
460
+ /** The rate to preselect, a percentage. */
461
+ suggested: number;
462
+ /**
463
+ * `RETURNED_GOODS`: every returned item carries this rate. `ORDER_MAIN_RATE`: the rate carrying
464
+ * most of the order's money, shipping included.
465
+ */
466
+ basis: ReturnCreditNoteTaxRateBasis | (string & {});
467
+ /** The rates to pick from, ascending, each once: the store's own, the order's and the suggestion. */
468
+ rates: number[];
469
+ /**
470
+ * The methods whose settlement issues the invoice credit note that takes the rate: ask for the
471
+ * rate on these only (a refund the accounts ledger documents, or an accounts store's own credit
472
+ * note, takes none).
473
+ */
474
+ methods: Array<ReturnSettlementMethod | (string & {})>;
475
+ /**
476
+ * The gross (minor units) of the invoice the note credits, when a settlement can still credit
477
+ * ALL of it; null once an earlier credit note took part of it, or when the engine does not
478
+ * know. A settlement reaching it credits the whole invoice: leave `taxRate` out and the note
479
+ * states the invoice's own VAT rows per rate, which no single rate can. Absent from an engine
480
+ * before it.
481
+ */
482
+ invoiceTotal?: number | null;
483
+ }
436
484
  export interface ReturnSettlementOptions {
437
485
  returnId: string;
438
486
  currencyCode: string;
@@ -495,6 +543,14 @@ export interface ReturnSettlementOptions {
495
543
  * 2026-10-10 (the method does not exist there).
496
544
  */
497
545
  account?: ReturnSettlementAccountOption;
546
+ /**
547
+ * The VAT rate for the invoice credit note a settlement issues: the suggestion, the rates to
548
+ * pick and the methods that issue the note (send the pick as {@link SettleReturnInput.taxRate}).
549
+ * Null when no settlement of it issues an invoice credit note (the order has no invoice) or
550
+ * nothing can be suggested. Absent from an engine before it: that engine refuses
551
+ * `taxRate`, so leave it out.
552
+ */
553
+ creditNoteTaxRate?: ReturnCreditNoteTaxRateOption | null;
498
554
  }
499
555
  export declare const RETURN_QUERY = "\n query SdkOrderReturn($id: ID!) {\n orderReturn(id: $id) {\n \n id\n code\n channelId\n orderId\n orderCode\n customerId\n currencyCode\n state\n initiator\n customerNote\n adminNote\n preferredSettlementMethod\n settlementMethod\n shippingRefundAmount\n settledAmount\n refundId\n creditNoteId\n creditNoteNumber\n requestedAt\n resolvedAt\n createdAt\n updatedAt\n lines {\n id\n returnId\n orderLineId\n productVariantId\n productVariantName\n productVariantSku\n requestedQty\n approvedQty\n receivedQty\n restockQty\n writeOffQty\n restockLocationId\n reasonCode\n reasonNote\n unitPriceWithTax\n unitPriceWithTaxUnrounded\n refundAmount\n createdAt\n updatedAt\n }\n\n alreadyCreditedBy {\n \n creditNoteId\n creditNoteNumber\n ledger\n\n }\n \n history {\n state\n actor\n actorUserId\n reason\n createdAt\n }\n\n \n refundEstimate\n customer {\n id\n firstName\n lastName\n emailAddress\n addresses {\n company\n defaultBillingAddress\n defaultShippingAddress\n }\n }\n lines {\n refundEstimate\n }\n\n allowedActions {\n action\n allowed\n reasonCode\n }\n }\n }\n";
500
556
  /**
@@ -530,7 +586,12 @@ export declare const RETURN_ELIGIBILITY_QUERY = "\n query SdkReturnEligibility(
530
586
  * `creditedQty`/`correctionCredits`: GraphQL refuses a whole document over one unknown field.
531
587
  */
532
588
  export declare const RETURN_ELIGIBILITY_QUERY_LEGACY = "\n query SdkReturnEligibilityLegacy($orderId: ID!) {\n returnEligibilityForOrder(orderId: $orderId) {\n orderId\n eligible\n reason\n returnWindowEndsAt\n lines {\n orderLineId\n productVariantId\n productVariantName\n productVariantSku\n fulfilledQty\n returnableQty\n unitPriceWithTax\n unitPriceWithTaxUnrounded\n }\n }\n }\n";
533
- export declare const RETURN_SETTLEMENT_OPTIONS_QUERY = "\n query SdkReturnSettlementOptions($returnId: ID!) {\n returnSettlementOptions(returnId: $returnId) {\n returnId\n currencyCode\n settleable\n blockedBy\n refund {\n available\n reasonCode\n payments {\n paymentId\n method\n refundable\n }\n }\n creditNote {\n available\n ledger\n reasonCode\n invoiceNumber\n }\n account {\n available\n reasonCode\n message\n held\n book\n }\n initialMethod\n defaultAmount\n amountCeiling\n correctionCredits {\n \n orderLineId\n creditNoteId\n creditNoteNumber\n ledger\n quantity\n amount\n\n }\n shippingCeiling\n alreadyCredited {\n available\n reasonCode\n creditNotes {\n \n creditNoteId\n creditNoteNumber\n ledger\n\n }\n }\n }\n }\n";
589
+ export declare const RETURN_SETTLEMENT_OPTIONS_QUERY = "\n query SdkReturnSettlementOptions($returnId: ID!) {\n returnSettlementOptions(returnId: $returnId) {\n returnId\n currencyCode\n settleable\n blockedBy\n refund {\n available\n reasonCode\n payments {\n paymentId\n method\n refundable\n }\n }\n creditNote {\n available\n ledger\n reasonCode\n invoiceNumber\n }\n account {\n available\n reasonCode\n message\n held\n book\n }\n initialMethod\n defaultAmount\n amountCeiling\n correctionCredits {\n \n orderLineId\n creditNoteId\n creditNoteNumber\n ledger\n quantity\n amount\n\n }\n shippingCeiling\n alreadyCredited {\n available\n reasonCode\n creditNotes {\n \n creditNoteId\n creditNoteNumber\n ledger\n\n }\n }\n creditNoteTaxRate {\n suggested\n basis\n rates\n methods\n invoiceTotal\n }\n }\n }\n";
590
+ /**
591
+ * {@link RETURN_SETTLEMENT_OPTIONS_QUERY} for an engine with the account's book but released before
592
+ * the credit note's VAT rate (USER PD11, B6b).
593
+ */
594
+ export declare const RETURN_SETTLEMENT_OPTIONS_QUERY_BEFORE_TAX_RATE = "\n query SdkReturnSettlementOptionsBeforeTaxRate($returnId: ID!) {\n returnSettlementOptions(returnId: $returnId) {\n returnId\n currencyCode\n settleable\n blockedBy\n refund {\n available\n reasonCode\n payments {\n paymentId\n method\n refundable\n }\n }\n creditNote {\n available\n ledger\n reasonCode\n invoiceNumber\n }\n account {\n available\n reasonCode\n message\n held\n book\n }\n initialMethod\n defaultAmount\n amountCeiling\n correctionCredits {\n \n orderLineId\n creditNoteId\n creditNoteNumber\n ledger\n quantity\n amount\n\n }\n shippingCeiling\n alreadyCredited {\n available\n reasonCode\n creditNotes {\n \n creditNoteId\n creditNoteNumber\n ledger\n\n }\n }\n }\n }\n";
534
595
  /**
535
596
  * {@link RETURN_SETTLEMENT_OPTIONS_QUERY} for an engine with `alreadyCredited` but released before
536
597
  * the account option named the store's book (incentives sprint 8 PR 4).
@@ -12,6 +12,7 @@
12
12
  * unknown value as "not available".
13
13
  */
14
14
  import { NotFoundError, ValidationError } from '@sixtyninecommerce/core';
15
+ import { statedCustomerAgreed } from '../payments/customer-consent.js';
15
16
  /** A return's state: the latest row of its status log. */
16
17
  export const RETURN_STATES = [
17
18
  'requested',
@@ -392,6 +393,63 @@ export const RETURN_ELIGIBILITY_QUERY_LEGACY = /* GraphQL */ `
392
393
  `;
393
394
  export const RETURN_SETTLEMENT_OPTIONS_QUERY = /* GraphQL */ `
394
395
  query SdkReturnSettlementOptions($returnId: ID!) {
396
+ returnSettlementOptions(returnId: $returnId) {
397
+ returnId
398
+ currencyCode
399
+ settleable
400
+ blockedBy
401
+ refund {
402
+ available
403
+ reasonCode
404
+ payments {
405
+ paymentId
406
+ method
407
+ refundable
408
+ }
409
+ }
410
+ creditNote {
411
+ available
412
+ ledger
413
+ reasonCode
414
+ invoiceNumber
415
+ }
416
+ account {
417
+ available
418
+ reasonCode
419
+ message
420
+ held
421
+ book
422
+ }
423
+ initialMethod
424
+ defaultAmount
425
+ amountCeiling
426
+ correctionCredits {
427
+ ${CORRECTION_CREDIT_FIELDS}
428
+ }
429
+ shippingCeiling
430
+ alreadyCredited {
431
+ available
432
+ reasonCode
433
+ creditNotes {
434
+ ${COVERING_CREDIT_NOTE_FIELDS}
435
+ }
436
+ }
437
+ creditNoteTaxRate {
438
+ suggested
439
+ basis
440
+ rates
441
+ methods
442
+ invoiceTotal
443
+ }
444
+ }
445
+ }
446
+ `;
447
+ /**
448
+ * {@link RETURN_SETTLEMENT_OPTIONS_QUERY} for an engine with the account's book but released before
449
+ * the credit note's VAT rate (USER PD11, B6b).
450
+ */
451
+ export const RETURN_SETTLEMENT_OPTIONS_QUERY_BEFORE_TAX_RATE = /* GraphQL */ `
452
+ query SdkReturnSettlementOptionsBeforeTaxRate($returnId: ID!) {
395
453
  returnSettlementOptions(returnId: $returnId) {
396
454
  returnId
397
455
  currencyCode
@@ -646,6 +704,7 @@ function isUnknownFieldRejection(error) {
646
704
  /** The plan's documents, newest first: each drops the newest field the one before it asks. */
647
705
  const SETTLEMENT_OPTIONS_DOCUMENTS = [
648
706
  RETURN_SETTLEMENT_OPTIONS_QUERY,
707
+ RETURN_SETTLEMENT_OPTIONS_QUERY_BEFORE_TAX_RATE,
649
708
  RETURN_SETTLEMENT_OPTIONS_QUERY_BEFORE_BOOK,
650
709
  RETURN_SETTLEMENT_OPTIONS_QUERY_BEFORE_CLOSE,
651
710
  RETURN_SETTLEMENT_OPTIONS_QUERY_BEFORE_ACCOUNT,
@@ -788,6 +847,8 @@ export function createReturnsModule(transport) {
788
847
  ? { shippingRefundAmount: input.shippingRefundAmount }
789
848
  : {}),
790
849
  ...(input.reason != null ? { reason: input.reason } : {}),
850
+ ...statedCustomerAgreed(input.customerAgreed),
851
+ ...(input.taxRate != null ? { taxRate: input.taxRate } : {}),
791
852
  },
792
853
  // A variable, not a header: the Admin API takes the key as an argument. `undefined`
793
854
  // when omitted, never null: `JSON.stringify` drops it, so no key stays off the wire.
@@ -0,0 +1,128 @@
1
+ import { type Transport } from '@sixtyninecommerce/core';
2
+ import type { SettingsSourceKind } from '../tenant/settings-sources.js';
3
+ import type { Call } from '../types.js';
4
+ /**
5
+ * How often the SEO audit (the storefront crawl included) runs. Mirrors `@IsIn` on the plugins
6
+ * service's `UpdateSeoConfigDto` and the meta catalog's `SEO_CRAWL_SCHEDULES`, pinned by
7
+ * `__tests__/seo-config.spec.ts`.
8
+ */
9
+ export declare const SEO_CRAWL_SCHEDULES: readonly ["off", "daily", "weekly"];
10
+ export type SeoCrawlSchedule = (typeof SEO_CRAWL_SCHEDULES)[number];
11
+ /**
12
+ * The ACTIVE store's SEO automation, as the plugins service applies it. The switches
13
+ * (`autoRedirectsEnabled`, `indexnowEnabled`, `cruxEnabled`, the crawl settings) are part of the
14
+ * `settings.seo` group: while the store takes the group from its organization they are the
15
+ * organization's, and `source` says so. The IndexNow key and the Search Console connection are
16
+ * always the store's own.
17
+ *
18
+ * The keys follow the service's wire names camelised (`indexnow_enabled` → `indexnowEnabled`), as
19
+ * the meta catalog's `seo` schema publishes them, so a head that renders that schema maps each
20
+ * property mechanically.
21
+ */
22
+ export interface SeoConfig {
23
+ /** Changed URLs are submitted to IndexNow (needs {@link SeoConfig.indexnowKey} to send any). */
24
+ indexnowEnabled: boolean;
25
+ /** The store's IndexNow key, served at `/<key>.txt`; null until one is generated. */
26
+ indexnowKey: string | null;
27
+ /** The Search Console property the store connected; null when none. */
28
+ gscPropertyUrl: string | null;
29
+ /** The store holds a Search Console connection. The credential itself is never answered. */
30
+ gscConnected: boolean;
31
+ /** When the connection was made (ISO 8601, UTC); null when there is none. */
32
+ gscConnectedAt: string | null;
33
+ /** Core Web Vitals (CrUX). Stored and shown; nothing on the platform reads it yet. */
34
+ cruxEnabled: boolean;
35
+ /** A string on the wire: a newer service may answer a schedule this SDK does not name yet. */
36
+ crawlSchedule: SeoCrawlSchedule | (string & {});
37
+ /** The most storefront pages one audit crawls (10 to 5,000). */
38
+ crawlMaxPages: number;
39
+ /** How fast the audit crawls, in pages per second (1 to 10). */
40
+ crawlRatePerSec: number;
41
+ /** A renamed product, collection or page keeps its old URL alive with a redirect. */
42
+ autoRedirectsEnabled: boolean;
43
+ /** `organization` while the switches above are the organization's, else `channel`. */
44
+ source: SettingsSourceKind | (string & {});
45
+ /** The organization locks `settings.seo`: {@link SeoConfigModule.set} refuses any change. */
46
+ locked: boolean;
47
+ /** The store keeps its own switches although the organization shares the group. */
48
+ overridden: boolean;
49
+ /** The organization shares the group without locking it, so the store may override it. */
50
+ inheritable: boolean;
51
+ /** The store's organization; null for a store outside any organization. */
52
+ organizationName: string | null;
53
+ }
54
+ /** The switches {@link SeoConfigModule.set} changes; a key left out keeps its value. */
55
+ export interface SeoConfigPatch {
56
+ indexnowEnabled?: boolean;
57
+ cruxEnabled?: boolean;
58
+ crawlSchedule?: SeoCrawlSchedule;
59
+ /** A whole number from 10 to 5,000. */
60
+ crawlMaxPages?: number;
61
+ /** A whole number from 1 to 10. */
62
+ crawlRatePerSec?: number;
63
+ autoRedirectsEnabled?: boolean;
64
+ }
65
+ /** What {@link SeoConfigModule.rotateIndexnowKey} answers. */
66
+ export interface IndexnowKeyRotation {
67
+ /** The new key. The store's `/<key>.txt` serves it from now on; the old key is void. */
68
+ key: string;
69
+ }
70
+ /**
71
+ * `admin.seo.config` — the ACTIVE store's SEO automation on the plugins service (Settings → SEO):
72
+ * automatic redirects, IndexNow, Core Web Vitals and the audit schedule. Needs the store's `seo`
73
+ * plugin: without it every method is `PermissionDeniedError` (403) naming the plugin.
74
+ *
75
+ * The other half of the store's SEO settings, the channel defaults (storefront URL, share and AI
76
+ * crawler defaults), is `admin.seo.settings`.
77
+ */
78
+ export interface SeoConfigModule {
79
+ /**
80
+ * The automation as the service applies it, and where its switches come from. Needs
81
+ * `ReadSettings`.
82
+ *
83
+ * @effect read
84
+ * @permission ReadSettings
85
+ */
86
+ get(options?: Call): Promise<SeoConfig>;
87
+ /**
88
+ * Change the switches `patch` names, and answer the whole configuration as it now applies.
89
+ * The next audit, URL change or IndexNow submission follows it.
90
+ *
91
+ * **Coded refusals:**
92
+ * - `SEO_CONFIG_INVALID` → `ValidationError` (400), before any request: an empty patch, a
93
+ * key that is not a switch here (the IndexNow key and Search Console have doors of their
94
+ * own), or a null (no switch can be cleared). One `details[]` entry per key, `field` naming
95
+ * it.
96
+ * - `VALIDATION_FAILED` → `ValidationError` (400), from the service: a value of the wrong type,
97
+ * a schedule outside {@link SEO_CRAWL_SCHEDULES}, or a number outside its range.
98
+ * - `MANAGED_BY_ORGANIZATION` → `BusinessRuleError` (422): a changed switch while the
99
+ * organization locks `settings.seo`.
100
+ * - `ORGANIZATION_SETTINGS_INHERITED` → `BusinessRuleError` (422): a changed switch while the
101
+ * store inherits the group; `tenant.settingsSources.override('settings.seo')` first. Sending
102
+ * back the values `get` answered is not a change and passes.
103
+ * - `ORGANIZATION_SETTINGS_SWITCH_OFF_CONFLICT` → `ConflictError` (409): in an organization's
104
+ * workspace, while the organization is switching its SEO defaults off and copying them into
105
+ * its stores. Nothing changed; try again once that finishes.
106
+ * - `PermissionDeniedError` (403): the credential lacks `UpdateSettings`.
107
+ *
108
+ * @effect write
109
+ * @permission UpdateSettings
110
+ */
111
+ set(patch: SeoConfigPatch, options?: Call): Promise<SeoConfig>;
112
+ /**
113
+ * Generate a new IndexNow key for the store and answer it. The old key is void at once: the
114
+ * store's `/<key>.txt` serves only the new one, and submissions use it from now on. The key is
115
+ * the store's own, so it is never refused for a store taking `settings.seo` from its
116
+ * organization.
117
+ *
118
+ * **Coded refusals:**
119
+ * - `ORGANIZATION_WORKSPACE_NOT_A_STORE` → `BusinessRuleError` (422): the active store is an
120
+ * organization's workspace, which has no storefront to prove.
121
+ * - `PermissionDeniedError` (403): the credential lacks `UpdateSettings`.
122
+ *
123
+ * @effect destructive
124
+ * @permission UpdateSettings
125
+ */
126
+ rotateIndexnowKey(options?: Call): Promise<IndexnowKeyRotation>;
127
+ }
128
+ export declare function createSeoConfigModule(transport: Transport): SeoConfigModule;
@@ -0,0 +1,95 @@
1
+ import { ValidationError } from '@sixtyninecommerce/core';
2
+ /**
3
+ * How often the SEO audit (the storefront crawl included) runs. Mirrors `@IsIn` on the plugins
4
+ * service's `UpdateSeoConfigDto` and the meta catalog's `SEO_CRAWL_SCHEDULES`, pinned by
5
+ * `__tests__/seo-config.spec.ts`.
6
+ */
7
+ export const SEO_CRAWL_SCHEDULES = ['off', 'daily', 'weekly'];
8
+ const INVALID = 'SEO_CONFIG_INVALID';
9
+ const PATCH_KEYS = [
10
+ 'indexnowEnabled',
11
+ 'cruxEnabled',
12
+ 'crawlSchedule',
13
+ 'crawlMaxPages',
14
+ 'crawlRatePerSec',
15
+ 'autoRedirectsEnabled',
16
+ ];
17
+ const KNOWN_KEYS = new Set(PATCH_KEYS);
18
+ /** Not a switch: answered by `get`, written through a door of its own. */
19
+ const OWN_DOORS = {
20
+ indexnowKey: 'indexnowKey is generated by rotateIndexnowKey(), never set.',
21
+ gscPropertyUrl: 'Search Console is connected through its OAuth flow, never set here.',
22
+ gscConnected: 'Search Console is connected through its OAuth flow, never set here.',
23
+ gscConnectedAt: 'Search Console is connected through its OAuth flow, never set here.',
24
+ };
25
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ function problemsOf(patch) {
27
+ const problems = [];
28
+ const add = (field, message) => problems.push({ field, code: INVALID, message });
29
+ if (!isRecord(patch)) {
30
+ add('patch', 'Pass the switches to change as an object, e.g. { autoRedirectsEnabled: false }.');
31
+ return problems;
32
+ }
33
+ const named = Object.keys(patch).filter((key) => patch[key] !== undefined);
34
+ if (named.length === 0) {
35
+ add('patch', `Name at least one switch to change: ${PATCH_KEYS.join(', ')}.`);
36
+ }
37
+ for (const key of named) {
38
+ if (!KNOWN_KEYS.has(key)) {
39
+ add(key, OWN_DOORS[key] ??
40
+ `${key} is not an SEO automation switch; they are ${PATCH_KEYS.join(', ')}.`);
41
+ }
42
+ else if (patch[key] === null) {
43
+ // The service's validation lets a null through to a NOT NULL column, whose refusal names
44
+ // no field (the meta catalog's `SEO_NULL`); nothing here has a "cleared" state.
45
+ add(key, `${key} cannot be cleared; send a value, or leave it out to keep it.`);
46
+ }
47
+ }
48
+ return problems;
49
+ }
50
+ /**
51
+ * The camelised wire body, its source fields filled in: they came with #882, and a service from
52
+ * before it leaves them out, which is the store's own settings. Every other field is the
53
+ * service's, as answered.
54
+ */
55
+ function configOf(body) {
56
+ return {
57
+ ...body,
58
+ source: body.source ?? 'channel',
59
+ locked: body.locked === true,
60
+ overridden: body.overridden === true,
61
+ inheritable: body.inheritable === true,
62
+ organizationName: body.organizationName ?? null,
63
+ };
64
+ }
65
+ export function createSeoConfigModule(transport) {
66
+ return {
67
+ async get(options) {
68
+ return configOf(await transport.rest('plugins', { method: 'GET', path: '/seo/config' }, options));
69
+ },
70
+ async set(patch, options) {
71
+ const problems = problemsOf(patch);
72
+ if (problems.length > 0) {
73
+ throw new ValidationError({
74
+ code: INVALID,
75
+ message: problems.map((problem) => problem.message).join(' '),
76
+ status: 400,
77
+ service: 'plugins',
78
+ details: problems,
79
+ });
80
+ }
81
+ // Only the named switches go out. The transport snake-cases them, and every key here
82
+ // camelises from a wire name, so the round trip is exact (`indexnowEnabled` →
83
+ // `indexnow_enabled`); a key spelt any other way would be refused whole by the service.
84
+ const body = {};
85
+ for (const key of PATCH_KEYS) {
86
+ if (patch[key] !== undefined)
87
+ body[key] = patch[key];
88
+ }
89
+ return configOf(await transport.rest('plugins', { method: 'PUT', path: '/seo/config', body }, options));
90
+ },
91
+ rotateIndexnowKey(options) {
92
+ return transport.rest('plugins', { method: 'POST', path: '/seo/config/indexnow-key/rotate' }, options);
93
+ },
94
+ };
95
+ }