@sixtyninecommerce/admin 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/README.md +4347 -171
  2. package/dist/catalog/assets.d.ts +120 -8
  3. package/dist/catalog/assets.js +98 -12
  4. package/dist/catalog/collections.d.ts +279 -14
  5. package/dist/catalog/collections.js +246 -15
  6. package/dist/catalog/index.d.ts +9 -2
  7. package/dist/catalog/index.js +4 -0
  8. package/dist/catalog/products.d.ts +159 -19
  9. package/dist/catalog/products.js +88 -13
  10. package/dist/catalog/properties.d.ts +102 -14
  11. package/dist/catalog/properties.js +13 -0
  12. package/dist/catalog/recommendations.d.ts +261 -0
  13. package/dist/catalog/recommendations.js +145 -0
  14. package/dist/catalog/reviews.d.ts +368 -0
  15. package/dist/catalog/reviews.js +318 -0
  16. package/dist/catalog/types.d.ts +17 -1
  17. package/dist/catalog/types.js +10 -0
  18. package/dist/catalog/variant-find.d.ts +136 -0
  19. package/dist/catalog/variant-find.js +239 -0
  20. package/dist/catalog/variants.d.ts +233 -26
  21. package/dist/catalog/variants.js +125 -7
  22. package/dist/catalog/visibility.d.ts +20 -5
  23. package/dist/catalog/visibility.js +19 -1
  24. package/dist/client.d.ts +95 -9
  25. package/dist/client.js +35 -7
  26. package/dist/content/assets.d.ts +286 -0
  27. package/dist/content/assets.js +181 -0
  28. package/dist/content/items.d.ts +269 -0
  29. package/dist/content/items.js +170 -0
  30. package/dist/content/types.d.ts +282 -0
  31. package/dist/content/types.js +100 -0
  32. package/dist/customers/age-verification.d.ts +66 -0
  33. package/dist/customers/age-verification.js +79 -0
  34. package/dist/customers/credit.d.ts +263 -0
  35. package/dist/customers/credit.js +244 -0
  36. package/dist/customers/delivery-preferences.d.ts +120 -0
  37. package/dist/customers/delivery-preferences.js +97 -0
  38. package/dist/customers/groups.d.ts +207 -0
  39. package/dist/customers/groups.js +438 -0
  40. package/dist/customers/index.d.ts +447 -37
  41. package/dist/customers/index.js +313 -40
  42. package/dist/customers/invitation-status.d.ts +57 -0
  43. package/dist/customers/invitation-status.js +21 -0
  44. package/dist/customers/notes.d.ts +67 -0
  45. package/dist/customers/notes.js +51 -0
  46. package/dist/customers/notification-recipients.d.ts +97 -0
  47. package/dist/customers/notification-recipients.js +84 -0
  48. package/dist/customers/saved-lists.d.ts +42 -0
  49. package/dist/customers/saved-lists.js +61 -0
  50. package/dist/customers/vat.d.ts +83 -0
  51. package/dist/customers/vat.js +46 -0
  52. package/dist/customers/wire.d.ts +20 -0
  53. package/dist/customers/wire.js +35 -0
  54. package/dist/documents/ar.d.ts +307 -0
  55. package/dist/documents/ar.js +254 -0
  56. package/dist/documents/index.d.ts +731 -21
  57. package/dist/documents/index.js +436 -24
  58. package/dist/documents/pdf.d.ts +2 -0
  59. package/dist/documents/pdf.js +11 -0
  60. package/dist/fulfillments/create.d.ts +167 -0
  61. package/dist/fulfillments/create.js +216 -0
  62. package/dist/fulfillments/index.d.ts +44 -10
  63. package/dist/fulfillments/index.js +11 -2
  64. package/dist/fulfillments/payment-policy.d.ts +64 -0
  65. package/dist/fulfillments/payment-policy.js +140 -0
  66. package/dist/fulfillments/warehouse.d.ts +724 -0
  67. package/dist/fulfillments/warehouse.js +914 -0
  68. package/dist/geo/index.d.ts +119 -0
  69. package/dist/geo/index.js +107 -0
  70. package/dist/index.d.ts +123 -35
  71. package/dist/index.js +45 -12
  72. package/dist/integrations/afas-actions.d.ts +234 -0
  73. package/dist/integrations/afas-actions.js +310 -0
  74. package/dist/integrations/afas-types.d.ts +483 -0
  75. package/dist/integrations/afas-types.js +5 -0
  76. package/dist/integrations/afas.d.ts +234 -0
  77. package/dist/integrations/afas.js +290 -0
  78. package/dist/integrations/connections.d.ts +103 -0
  79. package/dist/integrations/connections.js +55 -0
  80. package/dist/integrations/esp.d.ts +313 -0
  81. package/dist/integrations/esp.js +169 -0
  82. package/dist/integrations/exact-online-types.d.ts +423 -0
  83. package/dist/integrations/exact-online-types.js +6 -0
  84. package/dist/integrations/exact-online.d.ts +248 -0
  85. package/dist/integrations/exact-online.js +376 -0
  86. package/dist/integrations/index.d.ts +29 -3
  87. package/dist/integrations/index.js +18 -3
  88. package/dist/integrations/pipeline-keys.d.ts +13 -0
  89. package/dist/integrations/pipeline-keys.js +22 -0
  90. package/dist/integrations/pipelines.d.ts +15 -14
  91. package/dist/integrations/pipelines.js +6 -22
  92. package/dist/integrations/types.d.ts +146 -0
  93. package/dist/locations/index.d.ts +66 -0
  94. package/dist/locations/index.js +31 -0
  95. package/dist/meta/index.d.ts +162 -0
  96. package/dist/orders/cancel-with-money.d.ts +227 -0
  97. package/dist/orders/cancel-with-money.js +153 -0
  98. package/dist/orders/commit-edit.d.ts +7 -1
  99. package/dist/orders/commit-edit.js +5 -1
  100. package/dist/orders/drafts.d.ts +800 -0
  101. package/dist/orders/drafts.js +771 -0
  102. package/dist/orders/fields.d.ts +245 -0
  103. package/dist/orders/fields.js +188 -0
  104. package/dist/orders/get.d.ts +29 -19
  105. package/dist/orders/get.js +17 -8
  106. package/dist/orders/index.d.ts +49 -9
  107. package/dist/orders/index.js +22 -2
  108. package/dist/orders/lifecycle.d.ts +128 -0
  109. package/dist/orders/lifecycle.js +158 -0
  110. package/dist/orders/line-price-preview.d.ts +7 -2
  111. package/dist/orders/list.d.ts +222 -0
  112. package/dist/orders/list.js +377 -0
  113. package/dist/orders/manual-pricing.d.ts +5 -0
  114. package/dist/orders/order-entry.d.ts +20 -7
  115. package/dist/orders/plan-edit.d.ts +1 -1
  116. package/dist/orders/plan-edit.js +4 -1
  117. package/dist/orders/settings.d.ts +201 -0
  118. package/dist/orders/settings.js +325 -0
  119. package/dist/orders/status.d.ts +159 -13
  120. package/dist/orders/status.js +21 -0
  121. package/dist/orders/surcharges.d.ts +8 -1
  122. package/dist/orders/surcharges.js +3 -2
  123. package/dist/payments/account-credit.d.ts +30 -0
  124. package/dist/payments/account-credit.js +19 -0
  125. package/dist/payments/ar.d.ts +292 -0
  126. package/dist/payments/ar.js +223 -0
  127. package/dist/payments/connected-account.d.ts +47 -0
  128. package/dist/payments/connected-account.js +61 -0
  129. package/dist/payments/credit-noted-fields.d.ts +12 -0
  130. package/dist/payments/credit-noted-fields.js +37 -0
  131. package/dist/payments/index.d.ts +257 -11
  132. package/dist/payments/index.js +131 -1
  133. package/dist/payments/methods.d.ts +303 -8
  134. package/dist/payments/methods.js +337 -7
  135. package/dist/payments/mollie-methods.d.ts +159 -0
  136. package/dist/payments/mollie-methods.js +141 -0
  137. package/dist/payments/payment-links.d.ts +180 -0
  138. package/dist/payments/payment-links.js +97 -0
  139. package/dist/payments/provider-keys.d.ts +150 -0
  140. package/dist/payments/provider-keys.js +118 -0
  141. package/dist/payments/providers.d.ts +86 -11
  142. package/dist/payments/providers.js +29 -0
  143. package/dist/payments/requests.d.ts +144 -0
  144. package/dist/payments/requests.js +92 -0
  145. package/dist/payments/store-credit-fields.d.ts +15 -0
  146. package/dist/payments/store-credit-fields.js +51 -0
  147. package/dist/pickups/index.d.ts +104 -0
  148. package/dist/pickups/index.js +105 -0
  149. package/dist/platform/channels.d.ts +132 -0
  150. package/dist/platform/channels.js +128 -0
  151. package/dist/platform/geo.d.ts +123 -0
  152. package/dist/platform/geo.js +144 -0
  153. package/dist/platform/tax.d.ts +154 -0
  154. package/dist/platform/tax.js +139 -0
  155. package/dist/pos/anomalies.d.ts +158 -0
  156. package/dist/pos/anomalies.js +64 -0
  157. package/dist/pos/assisted-selling.d.ts +79 -0
  158. package/dist/pos/assisted-selling.js +38 -0
  159. package/dist/pos/exchanges.d.ts +103 -0
  160. package/dist/pos/exchanges.js +22 -0
  161. package/dist/pos/gift-cards.d.ts +135 -0
  162. package/dist/pos/gift-cards.js +47 -0
  163. package/dist/pos/index.d.ts +2125 -32
  164. package/dist/pos/index.js +742 -4
  165. package/dist/pos/no-receipt.d.ts +94 -0
  166. package/dist/pos/no-receipt.js +25 -0
  167. package/dist/pos/offline-sales.d.ts +187 -0
  168. package/dist/pos/offline-sales.js +116 -0
  169. package/dist/pos/pay-by-link.d.ts +143 -0
  170. package/dist/pos/pay-by-link.js +57 -0
  171. package/dist/pos/quick-keys.d.ts +103 -0
  172. package/dist/pos/quick-keys.js +45 -0
  173. package/dist/pos/reports.d.ts +360 -0
  174. package/dist/pos/reports.js +62 -0
  175. package/dist/pos/returns.d.ts +217 -0
  176. package/dist/pos/returns.js +53 -0
  177. package/dist/pricing/bulk-coupons.d.ts +139 -0
  178. package/dist/pricing/bulk-coupons.js +112 -0
  179. package/dist/pricing/index.d.ts +174 -4
  180. package/dist/pricing/index.js +174 -1
  181. package/dist/pricing/price-lists.d.ts +604 -0
  182. package/dist/pricing/price-lists.js +600 -0
  183. package/dist/purchasing/alerts.d.ts +89 -0
  184. package/dist/purchasing/alerts.js +62 -0
  185. package/dist/purchasing/cost.d.ts +110 -0
  186. package/dist/purchasing/cost.js +86 -0
  187. package/dist/purchasing/index.d.ts +36 -0
  188. package/dist/purchasing/index.js +16 -0
  189. package/dist/purchasing/purchase-orders.d.ts +548 -0
  190. package/dist/purchasing/purchase-orders.js +491 -0
  191. package/dist/purchasing/replenishment.d.ts +281 -0
  192. package/dist/purchasing/replenishment.js +240 -0
  193. package/dist/purchasing/settings.d.ts +152 -0
  194. package/dist/purchasing/settings.js +181 -0
  195. package/dist/purchasing/suppliers.d.ts +476 -0
  196. package/dist/purchasing/suppliers.js +438 -0
  197. package/dist/quotes/index.d.ts +501 -0
  198. package/dist/quotes/index.js +439 -0
  199. package/dist/reports/index.d.ts +71 -8
  200. package/dist/reports/index.js +74 -11
  201. package/dist/returns/index.d.ts +614 -21
  202. package/dist/returns/index.js +695 -11
  203. package/dist/search/index.d.ts +76 -1
  204. package/dist/search/index.js +63 -0
  205. package/dist/shipping/definitions.d.ts +29 -0
  206. package/dist/shipping/definitions.js +47 -0
  207. package/dist/shipping/fulfillment-profiles.d.ts +174 -0
  208. package/dist/shipping/fulfillment-profiles.js +104 -0
  209. package/dist/shipping/hendriks.d.ts +97 -0
  210. package/dist/shipping/hendriks.js +50 -0
  211. package/dist/shipping/index.d.ts +48 -0
  212. package/dist/shipping/index.js +25 -0
  213. package/dist/shipping/methods.d.ts +198 -0
  214. package/dist/shipping/methods.js +240 -0
  215. package/dist/shipping/myparcel.d.ts +254 -0
  216. package/dist/shipping/myparcel.js +178 -0
  217. package/dist/shipping/parcel-count-suggestion.d.ts +2 -0
  218. package/dist/shipping/parcel-rule.d.ts +6 -5
  219. package/dist/shipping/parcel-rule.js +21 -23
  220. package/dist/shipping/rate-tables.d.ts +179 -0
  221. package/dist/shipping/rate-tables.js +184 -0
  222. package/dist/stock/index.d.ts +10 -3
  223. package/dist/stock/index.js +3 -0
  224. package/dist/stock/locations.d.ts +144 -0
  225. package/dist/stock/locations.js +168 -0
  226. package/dist/stock/transfers.d.ts +216 -43
  227. package/dist/stock/transfers.js +140 -13
  228. package/dist/stored-value/index.d.ts +214 -4
  229. package/dist/stored-value/index.js +152 -0
  230. package/dist/subscriptions/index.d.ts +682 -0
  231. package/dist/subscriptions/index.js +815 -0
  232. package/dist/tax/index.d.ts +76 -1
  233. package/dist/tax/index.js +50 -0
  234. package/dist/tenant/email-style.d.ts +102 -0
  235. package/dist/tenant/email-style.js +72 -0
  236. package/dist/tenant/email-templates.d.ts +66 -0
  237. package/dist/tenant/email-templates.js +54 -0
  238. package/dist/tenant/notifications.d.ts +23 -17
  239. package/dist/tenant/settings-sources.d.ts +1 -1
  240. package/dist/tenant/time-zone.d.ts +17 -0
  241. package/dist/tenant/time-zone.js +11 -0
  242. package/dist/types.d.ts +50 -3
  243. package/dist/users/index.d.ts +3 -0
  244. package/dist/users/index.js +2 -0
  245. package/dist/users/warehouses.d.ts +50 -0
  246. package/dist/users/warehouses.js +51 -0
  247. package/package.json +2 -2
@@ -0,0 +1,600 @@
1
+ /**
2
+ * Price lists (headless spec §9.2 `pricing`: "price lists"; W10-14): a store's B2B price lists,
3
+ * their entries (what each target costs), who they are assigned to, the order rules they carry,
4
+ * and enabling, disabling or deleting many at once.
5
+ *
6
+ * - **Money** is integer MINOR UNITS of the store's DEFAULT currency, unless an entry's
7
+ * `currencyCode` names another; an entry with no `currencyCode` applies in the default
8
+ * currency only. Its basis differs per figure:
9
+ * - a `fixed_price` / `fixed_amount` entry: the store's price basis (gross on a tax-inclusive
10
+ * store, net on a tax-exclusive one);
11
+ * - a list's order minimum (`minOrderAmount`): its own `minOrderAmountBasis`, `net` or
12
+ * `gross`, and `gross` when omitted, whatever the store's basis;
13
+ * - an order rule's money (a `min_order_total` threshold, a `fixed_amount` discount): the
14
+ * store's price basis, as the dashboard labels it (USER DECISION N7, 2026-10-13).
15
+ * Percentages are basis points (`1000` = 10 %).
16
+ * - **Order rules apply** (USER DECISION N1). A customer a list reaches gets the single best rule
17
+ * their order meets (the most money off), as one line-level discount named "Order discount" in
18
+ * the order's language. Only the most specific level of lists with rules decides (contract →
19
+ * customer → group → store-wide); lists at that level are compared together. A total threshold
20
+ * reads the goods at the customer's own prices before any other discount, in the store's price
21
+ * basis, leaving out shipping, fees, gift cards and stated-price lines (imports, accepted
22
+ * quotes, till manager prices); a quantity threshold counts the units of those same lines.
23
+ * Money converts into the order's currency at the order's rate (no rate: no discount). Drafts,
24
+ * storefront carts and till sales with a customer get it; quote, import and subscription
25
+ * orders never do. A placed order keeps the rule SET it was placed with: an edit takes the
26
+ * discount off below the threshold and gives it above it, and later rule changes never reach
27
+ * it. The order minimum is checked before this discount. `orderRulesCombineWithPromotions`
28
+ * (off by default): on, the discount and the store's coupons and promotions add up; off, a
29
+ * coupon or promotion on the order wins and the list's rules step aside for that order.
30
+ * - **Times** are ISO-8601 instants in UTC (`startsAt`, `endsAt`).
31
+ * - **A list locked to an order** (an order's one-time discount, read as `Order.priceList`) is
32
+ * never answered here: `get` answers `null`, and every write refuses it
33
+ * `PRICE_LIST_LOCKED_TO_ORDER` (`BusinessRuleError`, 422). Change it from its order
34
+ * (`orders.setOneTimeDiscounts`).
35
+ * - **Organizations:** a price list is the store's own row; a workspace's lists price only the
36
+ * workspace.
37
+ *
38
+ * Permissions: reads need `ReadCatalog`, writes `UpdateCatalog` (deleting too, as the engine's
39
+ * single door asks). The names behind assignments also need `ReadCustomer` (a customer) or
40
+ * `ReadCustomer`/`ReadCustomerGroup` (a group); without it the name is `null` and the id stays.
41
+ */
42
+ import { pageFromVendureList, readOffsetCursor, ValidationError, } from '@sixtyninecommerce/core';
43
+ import { assertDeleted, DELETION_FIELDS, fieldOf, } from '../catalog/types.js';
44
+ import { redactionWatcher } from '../customer-data-redaction.js';
45
+ /**
46
+ * What a list search matches the term against:
47
+ * - `all` (the default): every facet below;
48
+ * - `name`: the list's own name and description;
49
+ * - `sku`: lists pricing a variant whose SKU STARTS WITH the term;
50
+ * - `product`: lists pricing a product or variant whose name contains the term;
51
+ * - `customer`: lists assigned to a matching customer, directly or through a group holding them
52
+ * (or a group whose name matches).
53
+ * `sku` and `product` reach a list through variant, product, collection and facet-value entries
54
+ * alike. A list that prices everything (a default discount, or assigned to every customer) is
55
+ * never matched: it would answer every search.
56
+ */
57
+ export const PRICE_LIST_SEARCH_MODES = ['all', 'name', 'sku', 'product', 'customer'];
58
+ /** What an entries search matches: the entry's own label, or (`sku`) every entry pricing the SKU. */
59
+ export const PRICE_LIST_ENTRY_SEARCH_MODES = ['all', 'sku'];
60
+ /** The engine's `adminListQueryLimit`: a larger page of lists is refused. */
61
+ export const MAX_PRICE_LIST_LIST_TAKE = 1000;
62
+ /** The largest page of entries the engine answers. */
63
+ export const MAX_PRICE_LIST_ENTRY_TAKE = 200;
64
+ /** How many entries `entries.export` answers at most; `total` still counts every match. */
65
+ export const MAX_PRICE_LIST_ENTRY_EXPORT = 10_000;
66
+ /** How many lists one `bulk` call may name. */
67
+ export const MAX_BULK_PRICE_LISTS = 100;
68
+ /** A search term shorter than this is ignored by the engine: the whole list answers. */
69
+ export const PRICE_LIST_SEARCH_MIN_TERM_LENGTH = 2;
70
+ /** How many entries `forCustomer` answers per list at most (`entriesTake`). */
71
+ export const MAX_CUSTOMER_PRICE_LIST_ENTRIES = 100;
72
+ export const PRICE_LIST_BULK_ACTIONS = [
73
+ 'ENABLE',
74
+ 'DISABLE',
75
+ 'DELETE',
76
+ ];
77
+ const FIELDS = /* GraphQL */ `
78
+ id
79
+ name
80
+ description
81
+ isContract
82
+ priority
83
+ defaultDiscountBps
84
+ startsAt
85
+ endsAt
86
+ enabled
87
+ appliesToAllCustomers
88
+ paymentTermsDays
89
+ paymentTermsCode
90
+ minOrderAmount
91
+ minOrderAmountBasis
92
+ minOrderQuantity
93
+ orderRulesCombineWithPromotions
94
+ createdAt
95
+ updatedAt
96
+ `;
97
+ const ASSIGNMENT_FIELDS = /* GraphQL */ `
98
+ id
99
+ priceListId
100
+ customerGroupId
101
+ customerId
102
+ customerGroup {
103
+ id
104
+ name
105
+ }
106
+ customer {
107
+ id
108
+ firstName
109
+ lastName
110
+ emailAddress
111
+ company
112
+ }
113
+ createdAt
114
+ updatedAt
115
+ `;
116
+ /** A list as `get`, `create` and `update` read it: with its assignments' names. */
117
+ export const PRICE_LIST_FIELDS = /* GraphQL */ `
118
+ ${FIELDS}
119
+ assignments { ${ASSIGNMENT_FIELDS} }
120
+ `;
121
+ export const PRICE_LISTS_QUERY = /* GraphQL */ `
122
+ query PriceLists($options: PriceListListOptions) {
123
+ priceLists(options: $options) {
124
+ items {
125
+ ${FIELDS}
126
+ assignments {
127
+ id
128
+ priceListId
129
+ customerGroupId
130
+ customerId
131
+ }
132
+ searchMatches {
133
+ kind
134
+ label
135
+ }
136
+ searchMatchCount
137
+ }
138
+ totalItems
139
+ }
140
+ }
141
+ `;
142
+ export const PRICE_LIST_QUERY = /* GraphQL */ `
143
+ query PriceList($id: ID!) {
144
+ priceList(id: $id) { ${PRICE_LIST_FIELDS} }
145
+ }
146
+ `;
147
+ export const CREATE_PRICE_LIST_MUTATION = /* GraphQL */ `
148
+ mutation CreatePriceList($input: CreatePriceListInput!) {
149
+ createPriceList(input: $input) { ${PRICE_LIST_FIELDS} }
150
+ }
151
+ `;
152
+ export const UPDATE_PRICE_LIST_MUTATION = /* GraphQL */ `
153
+ mutation UpdatePriceList($id: ID!, $input: UpdatePriceListInput!) {
154
+ updatePriceList(id: $id, input: $input) { ${PRICE_LIST_FIELDS} }
155
+ }
156
+ `;
157
+ export const DELETE_PRICE_LIST_MUTATION = /* GraphQL */ `
158
+ mutation DeletePriceList($id: ID!) {
159
+ deletePriceList(id: $id) { ${DELETION_FIELDS} }
160
+ }
161
+ `;
162
+ export const ASSIGN_PRICE_LIST_MUTATION = /* GraphQL */ `
163
+ mutation AssignPriceList($priceListId: ID!, $input: AssignPriceListInput!) {
164
+ assignPriceList(priceListId: $priceListId, input: $input) { ${ASSIGNMENT_FIELDS} }
165
+ }
166
+ `;
167
+ export const UNASSIGN_PRICE_LIST_MUTATION = /* GraphQL */ `
168
+ mutation UnassignPriceList($assignmentId: ID!) {
169
+ unassignPriceList(assignmentId: $assignmentId) { ${DELETION_FIELDS} }
170
+ }
171
+ `;
172
+ export const BULK_PRICE_LIST_ACTION_MUTATION = /* GraphQL */ `
173
+ mutation BulkPriceListAction($input: BulkPriceListActionInput!) {
174
+ bulkPriceListAction(input: $input) {
175
+ items {
176
+ id
177
+ status
178
+ code
179
+ message
180
+ }
181
+ }
182
+ }
183
+ `;
184
+ const ENTRY_WRITE_FIELDS = /* GraphQL */ `
185
+ id
186
+ priceListId
187
+ discountType
188
+ targetType
189
+ variantId
190
+ productId
191
+ collectionId
192
+ facetValueId
193
+ discountValue
194
+ chainValues
195
+ minQuantity
196
+ currencyCode
197
+ createdAt
198
+ updatedAt
199
+ `;
200
+ const ENTRY_FIELDS = /* GraphQL */ `
201
+ ${ENTRY_WRITE_FIELDS}
202
+ variantName
203
+ variantSku
204
+ targetName
205
+ `;
206
+ export const PRICE_LIST_ENTRIES_QUERY = /* GraphQL */ `
207
+ query PriceListEntries($priceListId: ID!, $options: PriceListEntryListOptions) {
208
+ priceListEntries(priceListId: $priceListId, options: $options) {
209
+ items { ${ENTRY_FIELDS} }
210
+ totalItems
211
+ }
212
+ }
213
+ `;
214
+ const FACET_COUNT_FIELDS = 'id label count';
215
+ export const PRICE_LIST_ENTRY_FACETS_QUERY = /* GraphQL */ `
216
+ query PriceListEntryFacets($priceListId: ID!, $search: String, $searchMode: String) {
217
+ priceListEntryFacets(priceListId: $priceListId, search: $search, searchMode: $searchMode) {
218
+ targetTypes { ${FACET_COUNT_FIELDS} }
219
+ collections { ${FACET_COUNT_FIELDS} }
220
+ facetValues { ${FACET_COUNT_FIELDS} }
221
+ total
222
+ }
223
+ }
224
+ `;
225
+ export const SET_PRICE_LIST_ENTRY_MUTATION = /* GraphQL */ `
226
+ mutation SetPriceListEntry($priceListId: ID!, $input: SetPriceListEntryInput!) {
227
+ setPriceListEntry(priceListId: $priceListId, input: $input) { ${ENTRY_FIELDS} }
228
+ }
229
+ `;
230
+ export const BULK_SET_PRICE_LIST_ENTRIES_MUTATION = /* GraphQL */ `
231
+ mutation BulkSetPriceListEntries($priceListId: ID!, $entries: [SetPriceListEntryInput!]!) {
232
+ bulkSetPriceListEntries(priceListId: $priceListId, entries: $entries) {
233
+ ${ENTRY_WRITE_FIELDS}
234
+ }
235
+ }
236
+ `;
237
+ export const REMOVE_PRICE_LIST_ENTRY_MUTATION = /* GraphQL */ `
238
+ mutation RemovePriceListEntry($id: ID!) {
239
+ removePriceListEntry(id: $id) { ${DELETION_FIELDS} }
240
+ }
241
+ `;
242
+ const ORDER_RULE_FIELDS = /* GraphQL */ `
243
+ id
244
+ priceListId
245
+ discountType
246
+ conditionType
247
+ conditionValue
248
+ discountValue
249
+ createdAt
250
+ updatedAt
251
+ `;
252
+ export const PRICE_LIST_ORDER_RULES_QUERY = /* GraphQL */ `
253
+ query PriceListOrderRules($priceListId: ID!) {
254
+ priceListOrderRules(priceListId: $priceListId) { ${ORDER_RULE_FIELDS} }
255
+ }
256
+ `;
257
+ export const SET_PRICE_LIST_ORDER_RULE_MUTATION = /* GraphQL */ `
258
+ mutation SetPriceListOrderRule($priceListId: ID!, $input: SetPriceListOrderRuleInput!) {
259
+ setPriceListOrderRule(priceListId: $priceListId, input: $input) { ${ORDER_RULE_FIELDS} }
260
+ }
261
+ `;
262
+ export const REMOVE_PRICE_LIST_ORDER_RULE_MUTATION = /* GraphQL */ `
263
+ mutation RemovePriceListOrderRule($id: ID!) {
264
+ removePriceListOrderRule(id: $id) { ${DELETION_FIELDS} }
265
+ }
266
+ `;
267
+ export const CUSTOMER_PRICE_LISTS_QUERY = /* GraphQL */ `
268
+ query CustomerPriceLists($customerId: ID!, $options: CustomerPriceListsOptions) {
269
+ customerPriceLists(customerId: $customerId, options: $options) {
270
+ priceList { ${FIELDS} }
271
+ assignmentType
272
+ entries {
273
+ items { ${ENTRY_FIELDS} }
274
+ totalItems
275
+ }
276
+ orderRules { ${ORDER_RULE_FIELDS} }
277
+ }
278
+ }
279
+ `;
280
+ const DEFAULT_LIST_TAKE = 25;
281
+ const DEFAULT_ENTRY_TAKE = 50;
282
+ function invalid(code, message) {
283
+ return new ValidationError({ code, message, status: 400, service: 'vendure' });
284
+ }
285
+ const invalidListOptions = (message) => invalid('INVALID_PRICE_LIST_LIST_OPTIONS', message);
286
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
287
+ const isIdList = (value) => Array.isArray(value) && value.every((id) => typeof id === 'string');
288
+ const isWhole = (value, min, max) => typeof value === 'number' && Number.isInteger(value) && value >= min && value <= max;
289
+ /** The page of one list call: `skip` or `after`, and `take` (else `limit`) within `max`. */
290
+ function pageOf(options, defaultTake, max) {
291
+ if (options?.after !== undefined && options.skip !== undefined) {
292
+ throw invalidListOptions('Page with after or with skip, not both.');
293
+ }
294
+ const take = options?.take ?? options?.limit ?? defaultTake;
295
+ const skip = options?.skip ?? (options?.after === undefined ? 0 : readOffsetCursor(options.after));
296
+ if (!isWhole(take, 1, max)) {
297
+ throw invalidListOptions(`take must be a whole number from 1 to ${String(max)}.`);
298
+ }
299
+ if (!isWhole(skip, 0, Number.MAX_SAFE_INTEGER)) {
300
+ throw invalidListOptions('skip must be a whole number of 0 or more.');
301
+ }
302
+ return { skip, take };
303
+ }
304
+ /** A list search as the engine reads it; a term it would ignore is not sent. */
305
+ function searchOf(options) {
306
+ const mode = options?.searchMode;
307
+ if (mode !== undefined && !PRICE_LIST_SEARCH_MODES.includes(mode)) {
308
+ throw invalidListOptions(`searchMode must be one of ${PRICE_LIST_SEARCH_MODES.join(', ')}.`);
309
+ }
310
+ const search = options?.search;
311
+ if (search === undefined)
312
+ return {};
313
+ if (typeof search !== 'string')
314
+ throw invalidListOptions('search must be a string.');
315
+ const term = search.trim();
316
+ if (term.length < PRICE_LIST_SEARCH_MIN_TERM_LENGTH)
317
+ return {};
318
+ return { search: term, ...(mode === undefined ? {} : { searchMode: mode }) };
319
+ }
320
+ /** An `assignedTo` filter as the engine reads it, or nothing; exactly one target, else refused. */
321
+ function assignedToOf(options) {
322
+ const assignedTo = options?.assignedTo;
323
+ if (assignedTo === undefined)
324
+ return {};
325
+ const refused = () => invalidListOptions('assignedTo names exactly one of customerId and customerGroupId.');
326
+ if (!isObject(assignedTo))
327
+ throw refused();
328
+ const { customerId, customerGroupId, includeChannelWide } = assignedTo;
329
+ const hasCustomer = customerId !== undefined && customerId !== null;
330
+ const hasGroup = customerGroupId !== undefined && customerGroupId !== null;
331
+ if (hasCustomer === hasGroup)
332
+ throw refused();
333
+ const id = hasCustomer ? customerId : customerGroupId;
334
+ if (typeof id !== 'string' || id === '')
335
+ throw refused();
336
+ if (includeChannelWide !== undefined && typeof includeChannelWide !== 'boolean') {
337
+ throw invalidListOptions('assignedTo.includeChannelWide must be true or false.');
338
+ }
339
+ return {
340
+ filter: {
341
+ assignedTo: {
342
+ ...(hasCustomer ? { customerId: id } : { customerGroupId: id }),
343
+ ...(includeChannelWide === undefined ? {} : { includeChannelWide }),
344
+ },
345
+ },
346
+ };
347
+ }
348
+ /** An entries filter as the engine reads it; anything else is refused before it is sent. */
349
+ function entryFilterOf(filter) {
350
+ if (filter === undefined)
351
+ return {};
352
+ const out = {};
353
+ const { targetType, search, searchMode, collectionIds, facetValueIds } = filter;
354
+ if (targetType !== undefined) {
355
+ if (!['variant', 'product', 'collection', 'facet_value'].includes(targetType)) {
356
+ throw invalidListOptions('targetType must be variant, product, collection or facet_value.');
357
+ }
358
+ out.targetType = targetType;
359
+ }
360
+ if (searchMode !== undefined) {
361
+ if (!PRICE_LIST_ENTRY_SEARCH_MODES.includes(searchMode)) {
362
+ throw invalidListOptions(`searchMode must be one of ${PRICE_LIST_ENTRY_SEARCH_MODES.join(', ')}.`);
363
+ }
364
+ out.searchMode = searchMode;
365
+ }
366
+ if (search !== undefined) {
367
+ if (typeof search !== 'string')
368
+ throw invalidListOptions('search must be a string.');
369
+ if (search.trim() !== '')
370
+ out.search = search.trim();
371
+ }
372
+ for (const [key, ids] of [
373
+ ['collectionIds', collectionIds],
374
+ ['facetValueIds', facetValueIds],
375
+ ]) {
376
+ if (ids === undefined)
377
+ continue;
378
+ if (!isIdList(ids))
379
+ throw invalidListOptions(`${key} must be a list of strings.`);
380
+ if (ids.length > 0)
381
+ out[key] = [...ids];
382
+ }
383
+ return out;
384
+ }
385
+ async function listPage(transport, skip, take, search, options) {
386
+ const redaction = redactionWatcher();
387
+ const data = await transport.graphql({
388
+ api: 'admin',
389
+ query: PRICE_LISTS_QUERY,
390
+ variables: { options: { skip, take, ...search } },
391
+ onExtensions: redaction.onExtensions,
392
+ }, options);
393
+ return pageFromVendureList({
394
+ items: data.priceLists.items.map((item) => redaction.mark({ ...item })),
395
+ totalItems: data.priceLists.totalItems,
396
+ }, skip, take,
397
+ // Each following page re-sends the same search and per-call options.
398
+ (cursor) => listPage(transport, readOffsetCursor(cursor), take, search, options));
399
+ }
400
+ async function entriesPage(transport, priceListId, skip, take, filter, options) {
401
+ const data = await fieldOf(transport, PRICE_LIST_ENTRIES_QUERY, 'priceListEntries', { priceListId, options: { skip, take, ...filter } }, options);
402
+ return pageFromVendureList(data, skip, take, (cursor) => entriesPage(transport, priceListId, readOffsetCursor(cursor), take, filter, options));
403
+ }
404
+ /** A list read with its customers' names: each named customer is marked when withheld. */
405
+ async function readList(transport, query, field, variables, options) {
406
+ const redaction = redactionWatcher();
407
+ const data = await transport.graphql({ api: 'admin', query, variables, onExtensions: redaction.onExtensions }, options);
408
+ const list = data[field] ?? null;
409
+ if (list === null)
410
+ return null;
411
+ return { ...list, assignments: list.assignments.map((row) => markCustomer(row, redaction)) };
412
+ }
413
+ function markCustomer(assignment, redaction) {
414
+ return assignment.customer === null
415
+ ? assignment
416
+ : {
417
+ ...assignment,
418
+ customer: redaction.mark({ ...assignment.customer }),
419
+ };
420
+ }
421
+ /** The largest value GraphQL's `Int` carries; the smallest is its negation minus one. */
422
+ const GRAPHQL_INT_MAX = 2_147_483_647;
423
+ /**
424
+ * An order rule's two values as the wire can carry them. `SetPriceListOrderRuleInput` types them
425
+ * `Int!`, so a fraction or a value outside 32 bits is answered by GraphQL's variable coercion with
426
+ * an uncoded `BAD_USER_INPUT` before the engine sees the rule, and the engine's own refusal (which
427
+ * names the field) never comes. The SDK refuses exactly those shapes the way the engine would,
428
+ * `PRICE_LIST_ORDER_RULE_INVALID` with a detail per field, before anything is sent. Every other
429
+ * rule (types, bounds, a discount below 1) stays the engine's.
430
+ */
431
+ function assertRuleValuesFitTheWire(input) {
432
+ const problems = [];
433
+ for (const field of ['conditionValue', 'discountValue']) {
434
+ const value = isObject(input) ? input[field] : undefined;
435
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
436
+ problems.push({
437
+ field,
438
+ code: 'VALUE_NOT_AN_INTEGER',
439
+ message: `${field} must be a whole number`,
440
+ });
441
+ }
442
+ else if (value > GRAPHQL_INT_MAX) {
443
+ problems.push({
444
+ field,
445
+ code: 'VALUE_TOO_LARGE',
446
+ message: `${field} must not exceed ${String(GRAPHQL_INT_MAX)}`,
447
+ });
448
+ }
449
+ else if (value < -GRAPHQL_INT_MAX - 1) {
450
+ problems.push(field === 'conditionValue'
451
+ ? { field, code: 'VALUE_NEGATIVE', message: 'conditionValue must not be negative' }
452
+ : {
453
+ field,
454
+ code: 'DISCOUNT_NOT_POSITIVE',
455
+ message: 'discountValue must be at least 1: a rule must take something off',
456
+ });
457
+ }
458
+ }
459
+ const first = problems[0];
460
+ if (first === undefined)
461
+ return;
462
+ throw new ValidationError({
463
+ code: 'PRICE_LIST_ORDER_RULE_INVALID',
464
+ message: first.message,
465
+ status: 400,
466
+ service: 'vendure',
467
+ details: problems,
468
+ });
469
+ }
470
+ /** The selection of a bulk call, checked as the engine checks it, before anything is sent. */
471
+ function bulkVariables(input) {
472
+ const refused = (message) => invalid('BULK_ACTION_INPUT_INVALID', message);
473
+ if (!isObject(input))
474
+ throw refused('A bulk action needs an action and priceListIds.');
475
+ if (!PRICE_LIST_BULK_ACTIONS.includes(input.action)) {
476
+ throw refused(`action must be one of ${PRICE_LIST_BULK_ACTIONS.join(', ')}.`);
477
+ }
478
+ if (!isIdList(input.priceListIds))
479
+ throw refused('priceListIds must be a list of strings.');
480
+ const ids = input.priceListIds;
481
+ if (ids.length === 0)
482
+ throw refused('Name at least one price list.');
483
+ if (ids.length > MAX_BULK_PRICE_LISTS) {
484
+ throw refused(`A bulk action takes at most ${String(MAX_BULK_PRICE_LISTS)} price lists.`);
485
+ }
486
+ if (new Set(ids.map((id) => id.toLowerCase())).size !== ids.length) {
487
+ throw refused('Name each price list once.');
488
+ }
489
+ return { input: { action: input.action, priceListIds: [...ids] } };
490
+ }
491
+ export function createPricingPriceListsModule(transport) {
492
+ const entries = {
493
+ // `async`: a refused option rejects the promise, as an engine refusal does.
494
+ async list(priceListId, options) {
495
+ const { skip, take } = pageOf(options, DEFAULT_ENTRY_TAKE, MAX_PRICE_LIST_ENTRY_TAKE);
496
+ return entriesPage(transport, priceListId, skip, take, entryFilterOf(options), options);
497
+ },
498
+ async export(priceListId, filter, options) {
499
+ const data = await fieldOf(transport, PRICE_LIST_ENTRIES_QUERY, 'priceListEntries', { priceListId, options: { ...entryFilterOf(filter), export: true } }, options);
500
+ return { items: data.items, total: data.totalItems };
501
+ },
502
+ async facets(priceListId, search, options) {
503
+ const filter = entryFilterOf({ search: search?.search, searchMode: search?.searchMode });
504
+ return fieldOf(transport, PRICE_LIST_ENTRY_FACETS_QUERY, 'priceListEntryFacets', { priceListId, search: filter.search ?? null, searchMode: filter.searchMode ?? null }, options);
505
+ },
506
+ set(priceListId, input, options) {
507
+ return fieldOf(transport, SET_PRICE_LIST_ENTRY_MUTATION, 'setPriceListEntry', { priceListId, input }, options);
508
+ },
509
+ bulkSet(priceListId, inputs, options) {
510
+ return fieldOf(transport, BULK_SET_PRICE_LIST_ENTRIES_MUTATION, 'bulkSetPriceListEntries', { priceListId, entries: [...inputs] }, options);
511
+ },
512
+ async remove(entryId, options) {
513
+ const response = await fieldOf(transport, REMOVE_PRICE_LIST_ENTRY_MUTATION, 'removePriceListEntry', { id: entryId }, options);
514
+ assertDeleted('PRICE_LIST_ENTRY_NOT_DELETED', [response]);
515
+ },
516
+ };
517
+ const orderRules = {
518
+ list(priceListId, options) {
519
+ return fieldOf(transport, PRICE_LIST_ORDER_RULES_QUERY, 'priceListOrderRules', { priceListId }, options);
520
+ },
521
+ async set(priceListId, input, options) {
522
+ assertRuleValuesFitTheWire(input);
523
+ return fieldOf(transport, SET_PRICE_LIST_ORDER_RULE_MUTATION, 'setPriceListOrderRule', { priceListId, input }, options);
524
+ },
525
+ async remove(ruleId, options) {
526
+ const response = await fieldOf(transport, REMOVE_PRICE_LIST_ORDER_RULE_MUTATION, 'removePriceListOrderRule', { id: ruleId }, options);
527
+ assertDeleted('PRICE_LIST_ORDER_RULE_NOT_DELETED', [response]);
528
+ },
529
+ };
530
+ return {
531
+ async list(options) {
532
+ const { skip, take } = pageOf(options, DEFAULT_LIST_TAKE, MAX_PRICE_LIST_LIST_TAKE);
533
+ // The filter is sent only when asked for: an engine without it refuses the whole document.
534
+ const narrowed = { ...searchOf(options), ...assignedToOf(options) };
535
+ return listPage(transport, skip, take, narrowed, options);
536
+ },
537
+ async forCustomer(customerId, options) {
538
+ const { entriesTake, ...call } = options ?? {};
539
+ if (entriesTake !== undefined && !isWhole(entriesTake, 1, MAX_CUSTOMER_PRICE_LIST_ENTRIES)) {
540
+ throw invalidListOptions(`entriesTake must be a whole number from 1 to ${String(MAX_CUSTOMER_PRICE_LIST_ENTRIES)}.`);
541
+ }
542
+ const rows = await fieldOf(transport, CUSTOMER_PRICE_LISTS_QUERY, 'customerPriceLists', { customerId, options: entriesTake === undefined ? null : { entriesTake } }, call);
543
+ return rows.map((row) => ({
544
+ priceList: row.priceList,
545
+ assignmentType: row.assignmentType,
546
+ entries: { items: row.entries.items, total: row.entries.totalItems },
547
+ orderRules: row.orderRules,
548
+ }));
549
+ },
550
+ get(id, options) {
551
+ return readList(transport, PRICE_LIST_QUERY, 'priceList', { id }, options);
552
+ },
553
+ async create(input, options) {
554
+ if (!isObject(input) || typeof input.name !== 'string') {
555
+ throw invalid('INVALID_PRICE_LIST_INPUT', 'A price list needs a name.');
556
+ }
557
+ const list = await readList(transport, CREATE_PRICE_LIST_MUTATION, 'createPriceList', { input }, options);
558
+ return list;
559
+ },
560
+ async update(id, input, options) {
561
+ if (!isObject(input))
562
+ throw invalid('INVALID_PRICE_LIST_INPUT', 'input must be an object.');
563
+ const list = await readList(transport, UPDATE_PRICE_LIST_MUTATION, 'updatePriceList', { id, input }, options);
564
+ return list;
565
+ },
566
+ async delete(id, options) {
567
+ const response = await fieldOf(transport, DELETE_PRICE_LIST_MUTATION, 'deletePriceList', { id }, options);
568
+ assertDeleted('PRICE_LIST_NOT_DELETED', [response]);
569
+ },
570
+ async assign(priceListId, target, options) {
571
+ const named = isObject(target) ? target : {};
572
+ const groupId = named.customerGroupId;
573
+ const customerId = named.customerId;
574
+ if ((typeof groupId === 'string') === (typeof customerId === 'string')) {
575
+ throw invalid('INVALID_PRICE_LIST_INPUT', 'Assign a price list to exactly one customerGroupId or customerId.');
576
+ }
577
+ const redaction = redactionWatcher();
578
+ const data = await transport.graphql({
579
+ api: 'admin',
580
+ query: ASSIGN_PRICE_LIST_MUTATION,
581
+ variables: {
582
+ priceListId,
583
+ input: typeof groupId === 'string' ? { customerGroupId: groupId } : { customerId },
584
+ },
585
+ onExtensions: redaction.onExtensions,
586
+ }, options);
587
+ return markCustomer(data.assignPriceList, redaction);
588
+ },
589
+ async unassign(assignmentId, options) {
590
+ const response = await fieldOf(transport, UNASSIGN_PRICE_LIST_MUTATION, 'unassignPriceList', { assignmentId }, options);
591
+ assertDeleted('PRICE_LIST_ASSIGNMENT_NOT_DELETED', [response]);
592
+ },
593
+ async bulk(input, options) {
594
+ const data = await fieldOf(transport, BULK_PRICE_LIST_ACTION_MUTATION, 'bulkPriceListAction', bulkVariables(input), options);
595
+ return data.items;
596
+ },
597
+ entries,
598
+ orderRules,
599
+ };
600
+ }
@@ -0,0 +1,89 @@
1
+ import type { Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ /**
4
+ * A purchasing staff alert (purchase-orders spec §8.14), named as its email under Management →
5
+ * Notifications. A string on the wire: more alerts may come (a provider push failing, phase 6).
6
+ */
7
+ export type PurchasingAlert = 'purchase_order_approval_requested' | 'purchase_order_lines_overdue' | 'reorder_suggestions_digest' | (string & {});
8
+ /**
9
+ * What needs attention in the active store's purchasing, counted over its legal entity: the
10
+ * dashboard's nav badges and Overview cards.
11
+ */
12
+ export interface PurchasingAlertSummary {
13
+ /** `YYYY-MM-DD` in the legal entity's time zone: the day overdue lines are judged against. */
14
+ localDay: string;
15
+ /**
16
+ * Purchase orders waiting for an approver: submitted above the approval threshold, or sent with
17
+ * a revision above it.
18
+ */
19
+ awaitingApproval: number;
20
+ /**
21
+ * Lines of sent purchase orders with something still to come whose expected day (the supplier's
22
+ * confirmed day, else the requested one) is before `localDay`.
23
+ */
24
+ overdueLines: number;
25
+ /** Sent purchase orders with at least one overdue line. */
26
+ overdueOrders: number;
27
+ /** Purchase orders awaiting approval or with an overdue line, each counted once. */
28
+ attentionOrders: number;
29
+ /** Reorder suggestions of the last compute that are listed (not dismissed). */
30
+ reorderSuggestions: number;
31
+ /** Of those, the ones the last compute found new (not suggested before, or grown). */
32
+ newReorderSuggestions: number;
33
+ }
34
+ /** Who one purchasing alert email goes to. */
35
+ export interface PurchasingAlertRecipients {
36
+ alert: PurchasingAlert;
37
+ /**
38
+ * The permission whose holders get the alert when no addresses are set:
39
+ * `ApprovePurchaseOrder`, `UpdatePurchaseOrder` or `CreatePurchaseOrder`.
40
+ */
41
+ permission: string;
42
+ /** The addresses set for this alert; empty when it goes to the permission's holders. */
43
+ emails: string[];
44
+ /**
45
+ * Who the alert goes to now: the addresses set, else the administrators of the legal entity's
46
+ * stores holding the permission (never a platform administrator or an API credential).
47
+ */
48
+ recipients: string[];
49
+ }
50
+ /** One alert's addresses: up to 20; an empty list sends it to the permission's holders again. */
51
+ export interface PurchasingAlertRecipientsInput {
52
+ alert: PurchasingAlert;
53
+ emails: string[];
54
+ }
55
+ export declare const PURCHASING_ALERT_SUMMARY_FIELDS = "\n localDay\n awaitingApproval\n overdueLines\n overdueOrders\n attentionOrders\n reorderSuggestions\n newReorderSuggestions\n";
56
+ 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";
58
+ export declare const PURCHASING_ALERT_RECIPIENTS_QUERY = "\n query PurchasingAlertRecipients {\n purchasingAlertRecipients { \n alert\n permission\n emails\n recipients\n }\n }\n";
59
+ 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
+ export interface PurchasingAlertRecipientsModule {
61
+ /**
62
+ * Who each alert goes to, in a fixed order (approval requests, overdue lines, reorder digest).
63
+ * Needs `ManagePurchasingSettings`.
64
+ */
65
+ list(call?: Call): Promise<PurchasingAlertRecipients[]>;
66
+ /**
67
+ * Sets who the named alerts go to; an alert not named keeps its own, and an empty `emails`
68
+ * sends it to the permission's holders again. Answers every alert, as `list` does. Needs
69
+ * `ManagePurchasingSettings` on EVERY store of the legal entity (`SHARED_SELLER_NOT_ALLOWED`,
70
+ * 403). Refusals: `PURCHASING_SETTINGS_INVALID` (400: an unknown alert, an alert named twice, an
71
+ * address that is not one, more than 20 addresses).
72
+ */
73
+ update(input: PurchasingAlertRecipientsInput[], call?: Call): Promise<PurchasingAlertRecipients[]>;
74
+ }
75
+ /**
76
+ * `admin.purchasing.alerts` (purchase-orders spec §8.14): what needs attention in the legal
77
+ * entity's purchasing, and who its alert emails go to. Every method needs the Purchasing plugin
78
+ * (`PURCHASING_NOT_ENABLED`, 422) and a store that purchases (`HUB_HAS_NO_PURCHASING`,
79
+ * `LEGAL_ENTITY_REQUIRED`, 422).
80
+ */
81
+ export interface PurchasingAlertsModule {
82
+ /**
83
+ * 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.
85
+ */
86
+ summary(call?: Call): Promise<PurchasingAlertSummary | null>;
87
+ recipients: PurchasingAlertRecipientsModule;
88
+ }
89
+ export declare function createPurchasingAlertsModule(transport: Transport): PurchasingAlertsModule;