@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
@@ -47,11 +47,17 @@ export declare const POS_OFFLINE_NOT_READY_REASONS: readonly ["prices_exclude_ta
47
47
  export type PosOfflineNotReadyReason = (typeof POS_OFFLINE_NOT_READY_REASONS)[number];
48
48
  /**
49
49
  * Why the engine refused or flagged an offline sale, in the order an answer lists them: the
50
- * refusals first (`item_gone` to `rounding_mismatch`: nothing was recorded but the row), then the
50
+ * refusals first (`item_gone` to `price_over_limit`: nothing was recorded but the row), then the
51
51
  * reviews (`price_changed` to `sold_at_suspect`: the sale is recorded). More may come.
52
52
  */
53
- export declare const POS_OFFLINE_SALE_REASONS: readonly ["item_gone", "drawer_closed", "currency_changed", "prices_exclude_tax", "out_of_stock", "gift_card", "total_mismatch", "rounding_mismatch", "price_changed", "not_fulfilled", "cashier_changed", "policy_off", "over_cap", "sold_at_suspect"];
53
+ export declare const POS_OFFLINE_SALE_REASONS: readonly ["item_gone", "drawer_closed", "currency_changed", "prices_exclude_tax", "out_of_stock", "gift_card", "total_mismatch", "rounding_mismatch", "price_over_limit", "price_changed", "not_fulfilled", "cashier_changed", "policy_off", "over_cap", "sold_at_suspect", "cashier_unverified", "item_gone_stand_in", "stock_short_stand_in", "total_corrected", "rounding_as_rung"];
54
54
  export type PosOfflineSaleReason = (typeof POS_OFFLINE_SALE_REASONS)[number];
55
+ /**
56
+ * The reasons the engine REFUSES a sale for (nothing recorded but its row): `item_gone` to
57
+ * `price_over_limit`. Every other reason is a review of a recorded sale. A reason this release does
58
+ * not know is neither: show it as it is.
59
+ */
60
+ export declare const POS_OFFLINE_SALE_REFUSAL_REASONS: readonly PosOfflineSaleReason[];
55
61
  /**
56
62
  * `recorded` (the order, the cash and the receipt number exist, as the till rang it),
57
63
  * `needs_review` (recorded, with a reason to look at) or `refused` (nothing but this row). More
@@ -132,6 +138,19 @@ export interface PosOfflineSale {
132
138
  resolvedByUserName: string | null;
133
139
  /** What the manager wrote they did about it. Staff text. */
134
140
  resolutionNote: string | null;
141
+ /**
142
+ * When a manager recorded it again (engine with POS polish PR 6), and their name now. Answered
143
+ * by `recordAgain` and `list` (an engine before PR 6 answers its list without them: absent);
144
+ * absent from `resolve`.
145
+ */
146
+ recordedAgainAt?: string | null;
147
+ recordedAgainByUserName?: string | null;
148
+ /**
149
+ * The drawer the sale's cash went into once recorded (its order's): the one it was rung in, or
150
+ * the one a manager recorded it again into (engine with POS polish PR 6); null while nothing is
151
+ * recorded, absent from an older engine (link `registerSessionId` then).
152
+ */
153
+ cashRegisterSessionId?: string | null;
135
154
  }
136
155
  /** `open` (the default): the sales a manager must still handle; `all`: every offline sale. */
137
156
  export type PosOfflineSaleFilter = 'open' | 'all';
@@ -170,11 +189,33 @@ export interface PosOfflineSalesModule {
170
189
  id: string;
171
190
  note: string;
172
191
  }, options?: Call): Promise<PosOfflineSale>;
192
+ /**
193
+ * Record a REFUSED offline sale again (engine with POS polish PR 6): the engine builds it the
194
+ * way the till's replay does, into the sale's own drawer while that is open, else the open drawer
195
+ * `registerSessionId` names (ignored while the own one is open); it is attributed to the caller
196
+ * (`recordedAgainAt`, `recordedAgainByUserName`). A sale refused again stays `refused` with its new
197
+ * reasons; one recorded again already answers as it is. Refused `POS_OFFLINE_SALE_NOT_FOUND`
198
+ * (`NotFoundError`: another store's, a till the caller may not see, an unknown id, or a sale that
199
+ * is not refused) and `POS_OFFLINE_SALE_DRAWER_INVALID` (`ValidationError`,
200
+ * `extensions.registerSessionId`: the own drawer is closed and none, or no open drawer of the
201
+ * store, was named) and `LOCATION_NOT_ALLOWED` (`PermissionDeniedError`: the drawer, the sale's
202
+ * own or the one named, stands at a location a location-restricted caller may not act at).
203
+ * Needs `UpdateSettings` or `ManagePosDevices`, and `CreateOrder` (it records an order and its
204
+ * cash). An engine before PR 6 refuses the document.
205
+ */
206
+ recordAgain(input: {
207
+ id: string;
208
+ registerSessionId?: string;
209
+ }, options?: Call): Promise<PosOfflineSale>;
173
210
  }
174
211
  export declare const POS_OFFLINE_CASH_SALES_QUERY = "query PosOfflineCashSales { getPosConfig { offlineCashSales { enabled maxSaleAmount maxSalesPerOutage maxOfflineMinutes ready notReadyReasons } } }";
175
212
  export declare const SET_POS_OFFLINE_CASH_SALES_MUTATION = "mutation SetPosOfflineCashSales($input: PosOfflineCashSalesInput!) { setPosOfflineCashSales(input: $input) { enabled maxSaleAmount maxSalesPerOutage maxOfflineMinutes ready notReadyReasons } }";
176
- export declare const POS_OFFLINE_SALES_QUERY = "query PosOfflineSales($input: PosOfflineSaleListInput) { posOfflineSales(input: $input) { items { id deviceId deviceLabel registerSessionId offlineSaleId provisionalReceiptNumber soldAt recordedAt status reasons orderId orderCode receiptNumber currencyCode totalWithTax cashTendered changeGiven cashRounding lines { variantId quantity unitPriceWithTax serverUnitPriceWithTax } staffName claimedStaffName resolvedAt resolvedByUserName resolutionNote } totalItems } }";
213
+ /** The list, naming who recorded a sale again (engine POS polish PR 6). */
214
+ export declare const POS_OFFLINE_SALES_QUERY = "query PosOfflineSales($input: PosOfflineSaleListInput) { posOfflineSales(input: $input) { items { id deviceId deviceLabel registerSessionId offlineSaleId provisionalReceiptNumber soldAt recordedAt status reasons orderId orderCode receiptNumber currencyCode totalWithTax cashTendered changeGiven cashRounding lines { variantId quantity unitPriceWithTax serverUnitPriceWithTax } staffName claimedStaffName resolvedAt resolvedByUserName resolutionNote recordedAgainAt recordedAgainByUserName cashRegisterSessionId } totalItems } }";
215
+ /** The list as an engine before PR 6 reads it: sent once that engine refused the fields. */
216
+ export declare const POS_OFFLINE_SALES_QUERY_BEFORE_RECORD_AGAIN = "query PosOfflineSales($input: PosOfflineSaleListInput) { posOfflineSales(input: $input) { items { id deviceId deviceLabel registerSessionId offlineSaleId provisionalReceiptNumber soldAt recordedAt status reasons orderId orderCode receiptNumber currencyCode totalWithTax cashTendered changeGiven cashRounding lines { variantId quantity unitPriceWithTax serverUnitPriceWithTax } staffName claimedStaffName resolvedAt resolvedByUserName resolutionNote } totalItems } }";
177
217
  export declare const RESOLVE_POS_OFFLINE_SALE_MUTATION = "mutation ResolvePosOfflineSale($input: ResolvePosOfflineSaleInput!) { resolvePosOfflineSale(input: $input) { id deviceId deviceLabel registerSessionId offlineSaleId provisionalReceiptNumber soldAt recordedAt status reasons orderId orderCode receiptNumber currencyCode totalWithTax cashTendered changeGiven cashRounding lines { variantId quantity unitPriceWithTax serverUnitPriceWithTax } staffName claimedStaffName resolvedAt resolvedByUserName resolutionNote } }";
218
+ export declare const RECORD_POS_OFFLINE_SALE_AGAIN_MUTATION = "mutation RecordPosOfflineSaleAgain($input: RecordPosOfflineSaleAgainInput!) { recordPosOfflineSaleAgain(input: $input) { id deviceId deviceLabel registerSessionId offlineSaleId provisionalReceiptNumber soldAt recordedAt status reasons orderId orderCode receiptNumber currencyCode totalWithTax cashTendered changeGiven cashRounding lines { variantId quantity unitPriceWithTax serverUnitPriceWithTax } staffName claimedStaffName resolvedAt resolvedByUserName resolutionNote recordedAgainAt recordedAgainByUserName cashRegisterSessionId } }";
178
219
  export declare const POS_DEVICE_OFFLINE_SALES_TO_REVIEW_QUERY = "query PosDeviceOfflineSalesToReview($stockLocationId: ID) { posDeviceFleet(stockLocationId: $stockLocationId) { device { id } offlineSalesToReview } }";
179
220
  /** `admin.pos.settings.offlineCashSales()`: null when POS was never set up. */
180
221
  export declare function readOfflineCashSales(transport: Transport, options?: Call): Promise<PosOfflineCashSales | null>;
@@ -1,4 +1,5 @@
1
1
  import { pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
2
+ import { isUnknownFieldRejection } from '../payments/store-credit-fields.js';
2
3
  /**
3
4
  * Offline cash sales (engine sprint 45, shipped dark): a till that loses its connection may keep
4
5
  * selling for cash, within the store's limits, and sends each sale when it is back. The engine
@@ -38,7 +39,7 @@ export const POS_OFFLINE_NOT_READY_REASONS = [
38
39
  ];
39
40
  /**
40
41
  * Why the engine refused or flagged an offline sale, in the order an answer lists them: the
41
- * refusals first (`item_gone` to `rounding_mismatch`: nothing was recorded but the row), then the
42
+ * refusals first (`item_gone` to `price_over_limit`: nothing was recorded but the row), then the
42
43
  * reviews (`price_changed` to `sold_at_suspect`: the sale is recorded). More may come.
43
44
  */
44
45
  export const POS_OFFLINE_SALE_REASONS = [
@@ -50,12 +51,34 @@ export const POS_OFFLINE_SALE_REASONS = [
50
51
  'gift_card',
51
52
  'total_mismatch',
52
53
  'rounding_mismatch',
54
+ 'price_over_limit',
53
55
  'price_changed',
54
56
  'not_fulfilled',
55
57
  'cashier_changed',
56
58
  'policy_off',
57
59
  'over_cap',
58
60
  'sold_at_suspect',
61
+ 'cashier_unverified',
62
+ 'item_gone_stand_in',
63
+ 'stock_short_stand_in',
64
+ 'total_corrected',
65
+ 'rounding_as_rung',
66
+ ];
67
+ /**
68
+ * The reasons the engine REFUSES a sale for (nothing recorded but its row): `item_gone` to
69
+ * `price_over_limit`. Every other reason is a review of a recorded sale. A reason this release does
70
+ * not know is neither: show it as it is.
71
+ */
72
+ export const POS_OFFLINE_SALE_REFUSAL_REASONS = [
73
+ 'item_gone',
74
+ 'drawer_closed',
75
+ 'currency_changed',
76
+ 'prices_exclude_tax',
77
+ 'out_of_stock',
78
+ 'gift_card',
79
+ 'total_mismatch',
80
+ 'rounding_mismatch',
81
+ 'price_over_limit',
59
82
  ];
60
83
  const DEFAULT_PAGE = 50;
61
84
  const MAX_PAGE = 100;
@@ -63,8 +86,13 @@ const POLICY_FIELDS = 'enabled maxSaleAmount maxSalesPerOutage maxOfflineMinutes
63
86
  const SALE_FIELDS = 'id deviceId deviceLabel registerSessionId offlineSaleId provisionalReceiptNumber soldAt recordedAt status reasons orderId orderCode receiptNumber currencyCode totalWithTax cashTendered changeGiven cashRounding lines { variantId quantity unitPriceWithTax serverUnitPriceWithTax } staffName claimedStaffName resolvedAt resolvedByUserName resolutionNote';
64
87
  export const POS_OFFLINE_CASH_SALES_QUERY = `query PosOfflineCashSales { getPosConfig { offlineCashSales { ${POLICY_FIELDS} } } }`;
65
88
  export const SET_POS_OFFLINE_CASH_SALES_MUTATION = `mutation SetPosOfflineCashSales($input: PosOfflineCashSalesInput!) { setPosOfflineCashSales(input: $input) { ${POLICY_FIELDS} } }`;
66
- export const POS_OFFLINE_SALES_QUERY = `query PosOfflineSales($input: PosOfflineSaleListInput) { posOfflineSales(input: $input) { items { ${SALE_FIELDS} } totalItems } }`;
89
+ /** The list, naming who recorded a sale again (engine POS polish PR 6). */
90
+ export const POS_OFFLINE_SALES_QUERY = `query PosOfflineSales($input: PosOfflineSaleListInput) { posOfflineSales(input: $input) { items { ${SALE_FIELDS} recordedAgainAt recordedAgainByUserName cashRegisterSessionId } totalItems } }`;
91
+ /** The list as an engine before PR 6 reads it: sent once that engine refused the fields. */
92
+ export const POS_OFFLINE_SALES_QUERY_BEFORE_RECORD_AGAIN = `query PosOfflineSales($input: PosOfflineSaleListInput) { posOfflineSales(input: $input) { items { ${SALE_FIELDS} } totalItems } }`;
67
93
  export const RESOLVE_POS_OFFLINE_SALE_MUTATION = `mutation ResolvePosOfflineSale($input: ResolvePosOfflineSaleInput!) { resolvePosOfflineSale(input: $input) { ${SALE_FIELDS} } }`;
94
+ // Engine POS polish PR 6: only this document selects who recorded it again.
95
+ export const RECORD_POS_OFFLINE_SALE_AGAIN_MUTATION = `mutation RecordPosOfflineSaleAgain($input: RecordPosOfflineSaleAgainInput!) { recordPosOfflineSaleAgain(input: $input) { ${SALE_FIELDS} recordedAgainAt recordedAgainByUserName cashRegisterSessionId } }`;
68
96
  // The fleet's count beside the device's id alone, its own document: the fleet read
69
97
  // (`POS_DEVICE_FLEET_QUERY`) never names it, so an engine before 45 keeps answering the fleet.
70
98
  export const POS_DEVICE_OFFLINE_SALES_TO_REVIEW_QUERY = 'query PosDeviceOfflineSalesToReview($stockLocationId: ID) { posDeviceFleet(stockLocationId: $stockLocationId) { device { id } offlineSalesToReview } }';
@@ -92,13 +120,30 @@ export async function readOfflineSalesToReview(transport, options) {
92
120
  }
93
121
  export function createPosOfflineSalesModule(transport) {
94
122
  const gql = gqlOf(transport);
123
+ // An engine before PR 6 refused the list's record-again fields once: every later page asks
124
+ // without them (one refused request per module, not per page).
125
+ let listBeforeRecordAgain = false;
126
+ async function listDocument(variables, options) {
127
+ if (!listBeforeRecordAgain) {
128
+ try {
129
+ return await gql(POS_OFFLINE_SALES_QUERY, variables, options);
130
+ }
131
+ catch (error) {
132
+ // Refused before it ran: the same read without the fields. Any other failure is the read's.
133
+ if (!isUnknownFieldRejection(error))
134
+ throw error;
135
+ listBeforeRecordAgain = true;
136
+ }
137
+ }
138
+ return gql(POS_OFFLINE_SALES_QUERY_BEFORE_RECORD_AGAIN, variables, options);
139
+ }
95
140
  async function salePage(skip, take, options) {
96
141
  const input = { skip, take };
97
142
  if (options?.filter !== undefined)
98
143
  input.filter = options.filter;
99
144
  if (options?.deviceId !== undefined)
100
145
  input.deviceId = options.deviceId;
101
- const data = await gql(POS_OFFLINE_SALES_QUERY, { input }, options);
146
+ const data = await listDocument({ input }, options);
102
147
  return pageFromVendureList(data.posOfflineSales, skip, take, (cursor) => salePage(readOffsetCursor(cursor), take, options));
103
148
  }
104
149
  return {
@@ -112,5 +157,14 @@ export function createPosOfflineSalesModule(transport) {
112
157
  const data = await gql(RESOLVE_POS_OFFLINE_SALE_MUTATION, { input: { id: input.id, note: input.note } }, options);
113
158
  return data.resolvePosOfflineSale;
114
159
  },
160
+ async recordAgain(input, options) {
161
+ const data = await gql(RECORD_POS_OFFLINE_SALE_AGAIN_MUTATION, {
162
+ input: {
163
+ id: input.id,
164
+ ...(input.registerSessionId ? { registerSessionId: input.registerSessionId } : {}),
165
+ },
166
+ }, options);
167
+ return data.recordPosOfflineSaleAgain;
168
+ },
115
169
  };
116
170
  }
@@ -26,6 +26,13 @@ import type { Call } from '../types.js';
26
26
  * refuses `POS_RETURN_STORE_CREDIT_UNAVAILABLE` (`extensions.reason`) when it cannot, nothing moved.
27
27
  * `quote` and `create` need an engine with C6b (an older one refuses their documents); `void`
28
28
  * keeps its sprint-20 selection and reads on any engine.
29
+ *
30
+ * The customer's consent to store credit (owner ruling Q4, EU consumer law CRD art. 13(1)): a
31
+ * store-credit payout asks whether the customer expressly agreed to store credit instead of a
32
+ * refund to their payment when `quote(...).storedValuePayout.asksConsent` says so, and `create`
33
+ * sends the answer as `customerAgreed`. The engine records it on the store credit; never a
34
+ * condition. `quote` reads `asksConsent` on an engine before it too (the document without it,
35
+ * `false`: such an engine takes no answer).
29
36
  */
30
37
  export type PosReturnKind = 'RETURN' | 'VOID';
31
38
  /** Where a return's value goes (C6b): REFUND (the default) or the customer's store credit. */
@@ -47,6 +54,13 @@ export interface PosReturnStoredValuePayout {
47
54
  available: boolean;
48
55
  /** no_customer, no_method or not_entitled when it cannot. More may come. */
49
56
  reason: string | null;
57
+ /**
58
+ * Ask the customer whether they expressly agreed to store credit before this payout, and send
59
+ * the answer as `PosReturnCreateInput.customerAgreed` (owner ruling Q4): true exactly when the
60
+ * payout is available. `false` from an engine before the field, which takes no answer, and on a
61
+ * webshop order's quote, which never pays out to store credit.
62
+ */
63
+ asksConsent: boolean;
50
64
  }
51
65
  /** The store credit a return was paid out to (C6b). */
52
66
  export interface PosReturnStoredValueCredit {
@@ -108,6 +122,17 @@ export interface PosReturnCreateInput {
108
122
  cashFirst?: boolean;
109
123
  /** REFUND when omitted (C6b). STORED_VALUE: the whole return to the sale's customer's store credit. */
110
124
  payout?: PosReturnPayout;
125
+ /**
126
+ * A STORED_VALUE payout whose quote said `asksConsent`: whether the customer expressly agreed to
127
+ * store credit instead of a refund to their payment (EU consumer law, CRD art. 13(1)), as staff
128
+ * asked them. The engine records it on the store credit with who stated it
129
+ * (`StoredValueEntry.customerAgreed`); never a condition, nothing is refused for it. Part of the
130
+ * request: a repeat under the same key sends the same answer, another answer is
131
+ * `IDEMPOTENCY_CONFLICT`. Sent only when given (true or false): left out or null, the request is
132
+ * what it always was. An engine before the field refuses a call that gives it, and that refusal
133
+ * is surfaced, never retried without the answer.
134
+ */
135
+ customerAgreed?: boolean | null;
111
136
  }
112
137
  export interface PosVoidInput {
113
138
  orderId: string;
@@ -208,7 +233,9 @@ export interface PosReturnsModule {
208
233
  idempotencyKey: string;
209
234
  }): Promise<PosReturnResult>;
210
235
  }
211
- export declare const POS_RETURN_QUOTE_QUERY = "query PosReturnQuote($input: PosReturnQuoteInput!) { posReturnQuote(input: $input) { orderId kind currencyCode receiptNumber lines { orderLineId name sku customItem restockable soldQuantity returnedQuantity returnableQuantity quantity refundAmount } total legs { paymentId kind method amount } cashPaidOut returnWindowEndsAt outsideWindow refundedOutsideReturns restockLocationName blockedBy blockedReason storedValuePayout { available reason } } }";
236
+ export declare const POS_RETURN_QUOTE_QUERY = "query PosReturnQuote($input: PosReturnQuoteInput!) { posReturnQuote(input: $input) { orderId kind currencyCode receiptNumber lines { orderLineId name sku customItem restockable soldQuantity returnedQuantity returnableQuantity quantity refundAmount } total legs { paymentId kind method amount } cashPaidOut returnWindowEndsAt outsideWindow refundedOutsideReturns restockLocationName blockedBy blockedReason storedValuePayout { available reason asksConsent } } }";
237
+ /** The quote for an engine before the till's consent (owner ruling Q4): no `asksConsent`. */
238
+ export declare const POS_RETURN_QUOTE_QUERY_BEFORE_CONSENT = "query PosReturnQuote($input: PosReturnQuoteInput!) { posReturnQuote(input: $input) { orderId kind currencyCode receiptNumber lines { orderLineId name sku customItem restockable soldQuantity returnedQuantity returnableQuantity quantity refundAmount } total legs { paymentId kind method amount } cashPaidOut returnWindowEndsAt outsideWindow refundedOutsideReturns restockLocationName blockedBy blockedReason storedValuePayout { available reason } } }";
212
239
  export declare const POS_RETURN_MUTATION = "mutation PosReturn($input: PosReturnInput!) { posReturn(input: $input) { orderId returnId returnCode kind total refunds { paymentId kind method amount refundId outcome } cashPaidOut complete storedValueCredit { accountId entryId amount } } }";
213
240
  export declare const POS_VOID_SALE_MUTATION = "mutation PosVoidSale($input: PosVoidSaleInput!) { posVoidSale(input: $input) { orderId returnId returnCode kind total refunds { paymentId kind method amount refundId outcome } cashPaidOut complete } }";
214
241
  export declare const POS_ONLINE_ORDER_QUERY = "query PosOnlineOrder($input: PosOnlineOrderRefInput!) { posOnlineOrder(input: $input) { orderId orderCode placedAt state currencyCode totalWithTax customerName refundMethodName returnableUnits blockedBy blockedReason } }";
@@ -1,17 +1,35 @@
1
- const QUOTE_FIELDS = 'orderId kind currencyCode receiptNumber lines { orderLineId name sku customItem restockable soldQuantity returnedQuantity returnableQuantity quantity refundAmount } total legs { paymentId kind method amount } cashPaidOut returnWindowEndsAt outsideWindow refundedOutsideReturns restockLocationName blockedBy blockedReason storedValuePayout { available reason }';
1
+ import { statedCustomerAgreed } from '../payments/customer-consent.js';
2
+ import { isUnknownFieldRejection } from '../payments/store-credit-fields.js';
3
+ /** A quote's fields up to its store-credit payout, which every engine with C6b reads. */
4
+ const QUOTE_BASE_FIELDS = 'orderId kind currencyCode receiptNumber lines { orderLineId name sku customItem restockable soldQuantity returnedQuantity returnableQuantity quantity refundAmount } total legs { paymentId kind method amount } cashPaidOut returnWindowEndsAt outsideWindow refundedOutsideReturns restockLocationName blockedBy blockedReason';
5
+ // Spliced as template constants, never built by a call: the repo's GraphQL cost scan
6
+ // (`graphql-cost-fits-repo.spec.ts`) resolves `${CONSTANT}` statically and nothing computed.
7
+ /** A quote's fields before the till's consent (owner ruling Q4): what every engine with C6b reads. */
8
+ const QUOTE_FIELDS_BEFORE_CONSENT = `${QUOTE_BASE_FIELDS} storedValuePayout { available reason }`;
9
+ const QUOTE_FIELDS = `${QUOTE_BASE_FIELDS} storedValuePayout { available reason asksConsent }`;
2
10
  /** The sprint-20 result: what a void answers (it never pays out to store credit). */
3
11
  const VOID_RESULT_FIELDS = 'orderId returnId returnCode kind total refunds { paymentId kind method amount refundId outcome } cashPaidOut complete';
4
12
  /** A return's result: the void's, and the store credit a STORED_VALUE payout made (C6b). */
5
13
  const RESULT_FIELDS = `${VOID_RESULT_FIELDS} storedValueCredit { accountId entryId amount }`;
6
14
  export const POS_RETURN_QUOTE_QUERY = `query PosReturnQuote($input: PosReturnQuoteInput!) { posReturnQuote(input: $input) { ${QUOTE_FIELDS} } }`;
15
+ /** The quote for an engine before the till's consent (owner ruling Q4): no `asksConsent`. */
16
+ export const POS_RETURN_QUOTE_QUERY_BEFORE_CONSENT = `query PosReturnQuote($input: PosReturnQuoteInput!) { posReturnQuote(input: $input) { ${QUOTE_FIELDS_BEFORE_CONSENT} } }`;
7
17
  export const POS_RETURN_MUTATION = `mutation PosReturn($input: PosReturnInput!) { posReturn(input: $input) { ${RESULT_FIELDS} } }`;
8
18
  export const POS_VOID_SALE_MUTATION = `mutation PosVoidSale($input: PosVoidSaleInput!) { posVoidSale(input: $input) { ${VOID_RESULT_FIELDS} } }`;
9
19
  // Engine C3/34: their own documents, so a call to an older engine fails alone.
10
20
  const ONLINE_ORDER_FIELDS = 'orderId orderCode placedAt state currencyCode totalWithTax customerName refundMethodName returnableUnits blockedBy blockedReason';
11
21
  export const POS_ONLINE_ORDER_QUERY = `query PosOnlineOrder($input: PosOnlineOrderRefInput!) { posOnlineOrder(input: $input) { ${ONLINE_ORDER_FIELDS} } }`;
12
- export const POS_ONLINE_RETURN_QUOTE_QUERY = `query PosOnlineReturnQuote($input: PosOnlineReturnQuoteInput!) { posOnlineReturnQuote(input: $input) { ${QUOTE_FIELDS} shippingRefund source } }`;
22
+ // Never store credit (D22), so it never asks consent: the selection before Q4, `asksConsent` false.
23
+ export const POS_ONLINE_RETURN_QUOTE_QUERY = `query PosOnlineReturnQuote($input: PosOnlineReturnQuoteInput!) { posOnlineReturnQuote(input: $input) { ${QUOTE_FIELDS_BEFORE_CONSENT} shippingRefund source } }`;
13
24
  // A webshop order's return never pays out to store credit (C3 D22): the void's result fields.
14
25
  export const POS_ONLINE_RETURN_MUTATION = `mutation PosOnlineReturn($input: PosOnlineReturnInput!) { posOnlineReturn(input: $input) { ${VOID_RESULT_FIELDS} } }`;
26
+ /** Such a quote as this SDK answers it: `asksConsent` false (that engine takes no answer). */
27
+ function withoutConsent(quote) {
28
+ return {
29
+ ...quote,
30
+ storedValuePayout: { ...quote.storedValuePayout, asksConsent: false },
31
+ };
32
+ }
15
33
  function keyOf(options, call) {
16
34
  const key = options?.idempotencyKey;
17
35
  if (typeof key !== 'string' || key.trim() === '') {
@@ -23,12 +41,24 @@ export function createPosReturnsModule(transport) {
23
41
  const gql = async (query, variables, options) => transport.graphql({ api: 'admin', query, variables }, options);
24
42
  return {
25
43
  async quote(input, options) {
26
- const data = await gql(POS_RETURN_QUOTE_QUERY, { input }, options);
27
- return data.posReturnQuote;
44
+ try {
45
+ const data = await gql(POS_RETURN_QUOTE_QUERY, { input }, options);
46
+ return data.posReturnQuote;
47
+ }
48
+ catch (error) {
49
+ // An engine before the till's consent (Q4) refuses the whole document; the quote is still
50
+ // readable without it, and that engine takes no answer. Any other failure is the read's.
51
+ if (!isUnknownFieldRejection(error))
52
+ throw error;
53
+ const data = await gql(POS_RETURN_QUOTE_QUERY_BEFORE_CONSENT, { input }, options);
54
+ return withoutConsent(data.posReturnQuote);
55
+ }
28
56
  },
29
57
  async create(input, options) {
30
58
  const idempotencyKey = keyOf(options, 'create');
31
- const data = await gql(POS_RETURN_MUTATION, { input: { ...input, idempotencyKey } }, options);
59
+ // The answer only when stated: left out or null, the request is what it always was.
60
+ const { customerAgreed, ...rest } = input;
61
+ const data = await gql(POS_RETURN_MUTATION, { input: { ...rest, ...statedCustomerAgreed(customerAgreed), idempotencyKey } }, options);
32
62
  return data.posReturn;
33
63
  },
34
64
  async void(input, options) {
@@ -42,7 +72,7 @@ export function createPosReturnsModule(transport) {
42
72
  },
43
73
  async onlineQuote(input, options) {
44
74
  const data = await gql(POS_ONLINE_RETURN_QUOTE_QUERY, { input }, options);
45
- return data.posOnlineReturnQuote;
75
+ return withoutConsent(data.posOnlineReturnQuote);
46
76
  },
47
77
  async onlineReturn(input, options) {
48
78
  const idempotencyKey = keyOf(options, 'onlineReturn');
@@ -500,7 +500,9 @@ export interface PricingPriceListsModule {
500
500
  /**
501
501
  * The lists that apply to a customer of the store NOW, IN THE ORDER the price-list pricing
502
502
  * tries them for each line: contracts first, then assigned to the customer, through one of
503
- * their groups, to every customer; then higher priority first. Only enabled lists inside their
503
+ * their groups, to every customer; then higher priority first. Lists of one standing (same
504
+ * contract flag, assignment and priority) are tried together and the lower price wins per line,
505
+ * an equal price going to the lower list id; they are listed by id. Only enabled lists inside their
504
506
  * date window, every list of the store considered; never an order's one-time discount (read
505
507
  * that as `Order.priceList`). Each comes with its first entries and its order rules. The
506
508
  * engine ranks them with the function its pricing uses, so a client never re-derives it.
@@ -30,13 +30,22 @@ export interface PurchasingAlertSummary {
30
30
  reorderSuggestions: number;
31
31
  /** Of those, the ones the last compute found new (not suggested before, or grown). */
32
32
  newReorderSuggestions: number;
33
+ /**
34
+ * The ACTIVE store's backordered order lines that stock on hand can ship now (its own orders,
35
+ * never another store's of the legal entity): `backorders.ready()`'s total. Purchase-orders
36
+ * step 3.6; `null` from an engine before it (the SDK asks again without the two counts).
37
+ */
38
+ backordersReadyLines: number | null;
39
+ /** The orders holding those lines, each counted once; `null` as `backordersReadyLines`. */
40
+ backordersReadyOrders: number | null;
33
41
  }
34
42
  /** Who one purchasing alert email goes to. */
35
43
  export interface PurchasingAlertRecipients {
36
44
  alert: PurchasingAlert;
37
45
  /**
38
46
  * The permission whose holders get the alert when no addresses are set:
39
- * `ApprovePurchaseOrder`, `UpdatePurchaseOrder` or `CreatePurchaseOrder`.
47
+ * `ApprovePurchaseOrder`, `ReceivePurchaseOrder` (`UpdatePurchaseOrder` on an engine before
48
+ * purchase-orders step 3.6) or `CreatePurchaseOrder`.
40
49
  */
41
50
  permission: string;
42
51
  /** The addresses set for this alert; empty when it goes to the permission's holders. */
@@ -52,9 +61,14 @@ export interface PurchasingAlertRecipientsInput {
52
61
  alert: PurchasingAlert;
53
62
  emails: string[];
54
63
  }
55
- export declare const PURCHASING_ALERT_SUMMARY_FIELDS = "\n localDay\n awaitingApproval\n overdueLines\n overdueOrders\n attentionOrders\n reorderSuggestions\n newReorderSuggestions\n";
64
+ export declare const PURCHASING_ALERT_SUMMARY_FIELDS = "\n \n localDay\n awaitingApproval\n overdueLines\n overdueOrders\n attentionOrders\n reorderSuggestions\n newReorderSuggestions\n\n backordersReadyLines\n backordersReadyOrders\n";
56
65
  export declare const PURCHASING_ALERT_RECIPIENTS_FIELDS = "\n alert\n permission\n emails\n recipients\n";
57
- export declare const PURCHASING_ALERTS_QUERY = "\n query PurchasingAlerts {\n purchasingAlerts { \n localDay\n awaitingApproval\n overdueLines\n overdueOrders\n attentionOrders\n reorderSuggestions\n newReorderSuggestions\n }\n }\n";
66
+ export declare const PURCHASING_ALERTS_QUERY = "\n query PurchasingAlerts {\n purchasingAlerts { \n \n localDay\n awaitingApproval\n overdueLines\n overdueOrders\n attentionOrders\n reorderSuggestions\n newReorderSuggestions\n\n backordersReadyLines\n backordersReadyOrders\n }\n }\n";
67
+ /**
68
+ * The summary without the backorder counts, for an engine before purchase-orders step 3.6: the SDK
69
+ * retries once with it when the engine refuses the fields, and answers the two counts null.
70
+ */
71
+ export declare const LEGACY_PURCHASING_ALERTS_QUERY = "\n query PurchasingAlertsLegacy {\n purchasingAlerts { \n localDay\n awaitingApproval\n overdueLines\n overdueOrders\n attentionOrders\n reorderSuggestions\n newReorderSuggestions\n }\n }\n";
58
72
  export declare const PURCHASING_ALERT_RECIPIENTS_QUERY = "\n query PurchasingAlertRecipients {\n purchasingAlertRecipients { \n alert\n permission\n emails\n recipients\n }\n }\n";
59
73
  export declare const UPDATE_PURCHASING_ALERT_RECIPIENTS_MUTATION = "\n mutation UpdatePurchasingAlertRecipients(\n $input: [PurchasingAlertRecipientsInput!]!\n $idempotencyKey: String\n ) {\n updatePurchasingAlertRecipients(input: $input, idempotencyKey: $idempotencyKey) {\n \n alert\n permission\n emails\n recipients\n\n }\n }\n";
60
74
  export interface PurchasingAlertRecipientsModule {
@@ -81,7 +95,8 @@ export interface PurchasingAlertRecipientsModule {
81
95
  export interface PurchasingAlertsModule {
82
96
  /**
83
97
  * The counts behind the nav badges and the Overview cards. Needs `ReadPurchaseOrder`. `null`
84
- * while purchasing is not switched on for the store's legal entity.
98
+ * while purchasing is not switched on for the store's legal entity. Against an engine before
99
+ * purchase-orders step 3.6 the backorder counts answer `null` and the rest as ever.
85
100
  */
86
101
  summary(call?: Call): Promise<PurchasingAlertSummary | null>;
87
102
  recipients: PurchasingAlertRecipientsModule;
@@ -1,4 +1,6 @@
1
- export const PURCHASING_ALERT_SUMMARY_FIELDS = /* GraphQL */ `
1
+ import { isUnknownFieldRejection } from '../payments/store-credit-fields.js';
2
+ /** The summary's counts an engine before purchase-orders step 3.6 answers. */
3
+ const STEP_4_3_SUMMARY_FIELDS = /* GraphQL */ `
2
4
  localDay
3
5
  awaitingApproval
4
6
  overdueLines
@@ -7,6 +9,11 @@ export const PURCHASING_ALERT_SUMMARY_FIELDS = /* GraphQL */ `
7
9
  reorderSuggestions
8
10
  newReorderSuggestions
9
11
  `;
12
+ export const PURCHASING_ALERT_SUMMARY_FIELDS = /* GraphQL */ `
13
+ ${STEP_4_3_SUMMARY_FIELDS}
14
+ backordersReadyLines
15
+ backordersReadyOrders
16
+ `;
10
17
  export const PURCHASING_ALERT_RECIPIENTS_FIELDS = /* GraphQL */ `
11
18
  alert
12
19
  permission
@@ -18,6 +25,15 @@ export const PURCHASING_ALERTS_QUERY = /* GraphQL */ `
18
25
  purchasingAlerts { ${PURCHASING_ALERT_SUMMARY_FIELDS} }
19
26
  }
20
27
  `;
28
+ /**
29
+ * The summary without the backorder counts, for an engine before purchase-orders step 3.6: the SDK
30
+ * retries once with it when the engine refuses the fields, and answers the two counts null.
31
+ */
32
+ export const LEGACY_PURCHASING_ALERTS_QUERY = /* GraphQL */ `
33
+ query PurchasingAlertsLegacy {
34
+ purchasingAlerts { ${STEP_4_3_SUMMARY_FIELDS} }
35
+ }
36
+ `;
21
37
  export const PURCHASING_ALERT_RECIPIENTS_QUERY = /* GraphQL */ `
22
38
  query PurchasingAlertRecipients {
23
39
  purchasingAlertRecipients { ${PURCHASING_ALERT_RECIPIENTS_FIELDS} }
@@ -36,8 +52,18 @@ export const UPDATE_PURCHASING_ALERT_RECIPIENTS_MUTATION = /* GraphQL */ `
36
52
  export function createPurchasingAlertsModule(transport) {
37
53
  return {
38
54
  async summary(call) {
39
- const data = await transport.graphql({ api: 'admin', query: PURCHASING_ALERTS_QUERY }, call);
40
- return data.purchasingAlerts ?? null;
55
+ try {
56
+ const data = await transport.graphql({ api: 'admin', query: PURCHASING_ALERTS_QUERY }, call);
57
+ return data.purchasingAlerts ?? null;
58
+ }
59
+ catch (error) {
60
+ if (!isUnknownFieldRejection(error))
61
+ throw error;
62
+ const data = await transport.graphql({ api: 'admin', query: LEGACY_PURCHASING_ALERTS_QUERY }, call);
63
+ return data.purchasingAlerts === null
64
+ ? null
65
+ : { ...data.purchasingAlerts, backordersReadyLines: null, backordersReadyOrders: null };
66
+ }
41
67
  },
42
68
  recipients: {
43
69
  async list(call) {
@@ -0,0 +1,61 @@
1
+ import { type Page, type Transport } from '@sixtyninecommerce/core';
2
+ import type { ListOptions } from '../types.js';
3
+ /**
4
+ * An open backordered order line of the active store that stock on hand can ship now
5
+ * (purchase-orders spec §8.11): the store's free on-hand of the variant shared out over its open
6
+ * backorders, oldest order first.
7
+ */
8
+ export interface BackorderReadyLine {
9
+ orderId: string;
10
+ orderCode: string;
11
+ /** The order's state; a parked or approval-waiting order keeps its turn too. */
12
+ orderState: string;
13
+ /** ISO-8601 UTC; null for an order never placed. */
14
+ orderPlacedAt: string | null;
15
+ orderLineId: string;
16
+ productVariantId: string;
17
+ sku: string;
18
+ /** The variant's name in the request's language. */
19
+ name: string;
20
+ /** The line's quantity. */
21
+ quantity: number;
22
+ /** Its backordered units not yet in a fulfilment. */
23
+ backorderedQuantity: number;
24
+ /** Of those, what stock on hand covers now (at least 1). */
25
+ readyQuantity: number;
26
+ /** The variant's on-hand, summed over the store's stock locations. */
27
+ stockOnHand: number;
28
+ /**
29
+ * True when the line was ordered from a supplier for this customer (a back-to-back link) and
30
+ * the supplier side cancelled it: it is an ordinary backorder again ("Supplier cancelled").
31
+ */
32
+ supplierCancelled: boolean;
33
+ }
34
+ export interface BackordersReadyOptions extends ListOptions {
35
+ /** Only the variants this goods receipt (of the store's legal entity) brought in. */
36
+ goodsReceiptId?: string;
37
+ }
38
+ export declare const BACKORDER_READY_LINE_FIELDS = "\n orderId\n orderCode\n orderState\n orderPlacedAt\n orderLineId\n productVariantId\n sku\n name\n quantity\n backorderedQuantity\n readyQuantity\n stockOnHand\n supplierCancelled\n";
39
+ export declare const BACKORDERS_READY_QUERY = "\n query BackordersReadyToShip($options: BackordersReadyOptions) {\n backordersReadyToShip(options: $options) {\n items { \n orderId\n orderCode\n orderState\n orderPlacedAt\n orderLineId\n productVariantId\n sku\n name\n quantity\n backorderedQuantity\n readyQuantity\n stockOnHand\n supplierCancelled\n }\n totalItems\n }\n }\n";
40
+ /**
41
+ * The list without `supplierCancelled`, for an engine before purchase-orders step 5.1: the SDK
42
+ * retries once with it when the engine refuses the field, and answers it `false` (that engine has
43
+ * no order links, so no supplier cancelled one).
44
+ */
45
+ export declare const LEGACY_BACKORDERS_READY_QUERY = "\n query BackordersReadyToShipLegacy($options: BackordersReadyOptions) {\n backordersReadyToShip(options: $options) {\n items { \n orderId\n orderCode\n orderState\n orderPlacedAt\n orderLineId\n productVariantId\n sku\n name\n quantity\n backorderedQuantity\n readyQuantity\n stockOnHand\n }\n totalItems\n }\n }\n";
46
+ /**
47
+ * `admin.purchasing.backorders` (purchase-orders spec §8.11, §8.14): what a goods receipt let the
48
+ * store ship. A read only: nothing is allocated. Needs the Purchasing plugin on the store and
49
+ * purchasing switched on for its legal entity (`PURCHASING_NOT_ENABLED`, 422), a store that
50
+ * purchases (`HUB_HAS_NO_PURCHASING`, `LEGAL_ENTITY_REQUIRED`), and `ReadOrder`.
51
+ */
52
+ export interface PurchasingBackordersModule {
53
+ /**
54
+ * The active store's open backordered order lines that stock on hand can ship now, oldest order
55
+ * first (pages of at most 100). `goodsReceiptId` narrows them to the variants that receipt
56
+ * brought in. Refusals: `GOODS_RECEIPT_NOT_FOUND` (404, a receipt of another legal entity too),
57
+ * `GOODS_RECEIPT_INVALID` (400, a page out of range).
58
+ */
59
+ ready(options?: BackordersReadyOptions): Promise<Page<BackorderReadyLine>>;
60
+ }
61
+ export declare function createPurchasingBackordersModule(transport: Transport): PurchasingBackordersModule;
@@ -0,0 +1,92 @@
1
+ import { pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
2
+ import { isUnknownFieldRejection } from '../payments/store-credit-fields.js';
3
+ /** The line's fields an engine before purchase-orders step 5.1 serves. */
4
+ const STEP_3_6_LINE_FIELDS = /* GraphQL */ `
5
+ orderId
6
+ orderCode
7
+ orderState
8
+ orderPlacedAt
9
+ orderLineId
10
+ productVariantId
11
+ sku
12
+ name
13
+ quantity
14
+ backorderedQuantity
15
+ readyQuantity
16
+ stockOnHand
17
+ `;
18
+ export const BACKORDER_READY_LINE_FIELDS = /* GraphQL */ `
19
+ orderId
20
+ orderCode
21
+ orderState
22
+ orderPlacedAt
23
+ orderLineId
24
+ productVariantId
25
+ sku
26
+ name
27
+ quantity
28
+ backorderedQuantity
29
+ readyQuantity
30
+ stockOnHand
31
+ supplierCancelled
32
+ `;
33
+ export const BACKORDERS_READY_QUERY = /* GraphQL */ `
34
+ query BackordersReadyToShip($options: BackordersReadyOptions) {
35
+ backordersReadyToShip(options: $options) {
36
+ items { ${BACKORDER_READY_LINE_FIELDS} }
37
+ totalItems
38
+ }
39
+ }
40
+ `;
41
+ /**
42
+ * The list without `supplierCancelled`, for an engine before purchase-orders step 5.1: the SDK
43
+ * retries once with it when the engine refuses the field, and answers it `false` (that engine has
44
+ * no order links, so no supplier cancelled one).
45
+ */
46
+ export const LEGACY_BACKORDERS_READY_QUERY = /* GraphQL */ `
47
+ query BackordersReadyToShipLegacy($options: BackordersReadyOptions) {
48
+ backordersReadyToShip(options: $options) {
49
+ items { ${STEP_3_6_LINE_FIELDS} }
50
+ totalItems
51
+ }
52
+ }
53
+ `;
54
+ const DEFAULT_PAGE_SIZE = 25;
55
+ export function createPurchasingBackordersModule(transport) {
56
+ const readyPage = async (skip, take, options) => {
57
+ const variables = {
58
+ options: {
59
+ skip,
60
+ take,
61
+ ...(options?.goodsReceiptId === undefined
62
+ ? {}
63
+ : { goodsReceiptId: options.goodsReceiptId }),
64
+ },
65
+ };
66
+ let list;
67
+ try {
68
+ const data = await transport.graphql({ api: 'admin', query: BACKORDERS_READY_QUERY, variables }, options);
69
+ list = data.backordersReadyToShip;
70
+ }
71
+ catch (error) {
72
+ if (!isUnknownFieldRejection(error))
73
+ throw error;
74
+ const data = await transport.graphql({ api: 'admin', query: LEGACY_BACKORDERS_READY_QUERY, variables }, options);
75
+ list = {
76
+ items: data.backordersReadyToShip.items.map((line) => ({
77
+ ...line,
78
+ supplierCancelled: false,
79
+ })),
80
+ totalItems: data.backordersReadyToShip.totalItems,
81
+ };
82
+ }
83
+ return pageFromVendureList(list, skip, take, (cursor) => readyPage(readOffsetCursor(cursor), take, options));
84
+ };
85
+ return {
86
+ async ready(options) {
87
+ const take = options?.limit ?? DEFAULT_PAGE_SIZE;
88
+ const skip = options?.after === undefined ? 0 : readOffsetCursor(options.after);
89
+ return readyPage(skip, take, options);
90
+ },
91
+ };
92
+ }