@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,439 @@
1
+ /**
2
+ * `admin.quotes`: B2B quotes, from the draft a sales rep builds to the order it converts into
3
+ * (headless spec §9.2 `quotes`; plan W10-35a).
4
+ *
5
+ * A quote is a store's offer to one customer, in one currency (`currencyCode`, one of the store's
6
+ * currencies). Staff build it (`createDraft`, `lines.*`, `setPricing`), move it through its
7
+ * statuses (`submit`, `approve`, `decline`, `cancel`) and turn an approved one into an order
8
+ * awaiting payment (`convertToOrder`). A customer can also request one from the storefront; it
9
+ * arrives `requested`.
10
+ *
11
+ * **The engine answers what can be done.** `Quote.editable` says whether the lines and prices may
12
+ * still change (every line and pricing method refuses `QUOTE_NOT_EDITABLE` exactly when it is
13
+ * false), and `Quote.convertible` whether `convertToOrder` would create the order now. Show the
14
+ * buttons from them; never re-derive either from the status.
15
+ *
16
+ * **Money** is an integer in minor units of the QUOTE's `currencyCode`. A line's `unitPrice`, its
17
+ * fixed discount, `discountAmount` and `effectiveUnitPrice` are in the store's primary tax mode
18
+ * (gross on a store whose prices include tax, net otherwise); `lineTotal` / `lineTotalWithTax`
19
+ * are the explicit net and gross halves. A percentage discount is in basis points (1000 = 10%).
20
+ * Every figure is the engine's own maths: the quote PDF and the converted order use the same.
21
+ *
22
+ * **Refusals** (`PlatformError`, by `code`): `QUOTE_NOT_FOUND` (404) for a quote or line the store
23
+ * does not have, another store's included; `QUOTE_LINE_INVALID` (400, `details[0].field` names the
24
+ * input) for a quantity, unit price, variant or pricing line it refuses; `QUOTE_DISCOUNT_INVALID`
25
+ * (400); `QUOTE_NOT_EDITABLE` (422); `QUOTE_CONVERT_STATE_INVALID` (422); `QUOTE_CURRENCY_MISMATCH`,
26
+ * `CURRENCY_NOT_AVAILABLE`, `ORDER_LINE_VARIANT_UNPRICED` (a line priced from the catalogue for a
27
+ * variant the store has no price for) and `HUB_HAS_NO_ORDERS` / `ORGANIZATION_WORKSPACE_NOT_A_STORE`
28
+ * (an organization workspace keeps no quotes). Each passes through as the engine sent it.
29
+ *
30
+ * Every value set below grows over time: treat a status this version does not name as one with
31
+ * no action (the engine's `editable` / `convertible` stay authoritative).
32
+ */
33
+ import { idempotencyKey as mintIdempotencyKey, UpstreamError, } from '@sixtyninecommerce/core';
34
+ import { fieldOf } from '../catalog/types.js';
35
+ import { redactionWatcher } from '../customer-data-redaction.js';
36
+ export const QUOTE_STATUSES = [
37
+ 'draft',
38
+ 'requested',
39
+ 'in_review',
40
+ 'approved',
41
+ 'declined',
42
+ 'expired',
43
+ 'converted_to_order',
44
+ 'cancelled',
45
+ ];
46
+ const LINE_FIELDS = /* GraphQL */ `
47
+ id
48
+ productVariantId
49
+ quantity
50
+ unitPrice
51
+ discountType
52
+ discountValue
53
+ discountLabel
54
+ position
55
+ discountAmount
56
+ effectiveUnitPrice
57
+ lineTotal
58
+ lineTotalWithTax
59
+ taxAmount
60
+ taxRate
61
+ createdAt
62
+ updatedAt
63
+ productVariant {
64
+ id
65
+ name
66
+ sku
67
+ storePrice {
68
+ price
69
+ priceWithTax
70
+ }
71
+ product {
72
+ id
73
+ name
74
+ featuredAsset {
75
+ id
76
+ preview
77
+ }
78
+ }
79
+ }
80
+ `;
81
+ const DOCUMENT_FIELDS = /* GraphQL */ `
82
+ id
83
+ documentNumber
84
+ fileSize
85
+ generatedAt
86
+ lastSentAt
87
+ `;
88
+ export const QUOTE_FIELDS = /* GraphQL */ `
89
+ id
90
+ code
91
+ customerId
92
+ currencyCode
93
+ status
94
+ editable
95
+ convertible
96
+ expiresAt
97
+ validUntil
98
+ salesRepUserId
99
+ customerNotes
100
+ internalNotes
101
+ convertedOrderId
102
+ createdAt
103
+ updatedAt
104
+ customer {
105
+ id
106
+ firstName
107
+ lastName
108
+ emailAddress
109
+ addresses {
110
+ company
111
+ defaultBillingAddress
112
+ defaultShippingAddress
113
+ }
114
+ }
115
+ salesRep {
116
+ id
117
+ userId
118
+ firstName
119
+ lastName
120
+ emailAddress
121
+ }
122
+ lines {
123
+ ${LINE_FIELDS}
124
+ }
125
+ document {
126
+ ${DOCUMENT_FIELDS}
127
+ }
128
+ `;
129
+ export const QUOTES_LIST_QUERY = /* GraphQL */ `
130
+ query QuotesList($options: AdminQuoteListOptions) {
131
+ quotes(options: $options) {
132
+ items {
133
+ ${QUOTE_FIELDS}
134
+ }
135
+ totalItems
136
+ }
137
+ }
138
+ `;
139
+ /**
140
+ * The detail read adds what costs per read: the signed PDF link (a storage round trip) and each
141
+ * line's price-list suggestion (the price-list strategy per line). The list never selects them.
142
+ */
143
+ export const QUOTE_GET_QUERY = /* GraphQL */ `
144
+ query QuotesGet($id: ID!) {
145
+ quote(id: $id) {
146
+ ${QUOTE_FIELDS}
147
+ document {
148
+ downloadUrl
149
+ }
150
+ lines {
151
+ id
152
+ suggestedUnitPrice
153
+ }
154
+ statusLogs {
155
+ id
156
+ status
157
+ createdAt
158
+ actorUserId
159
+ metadata
160
+ }
161
+ }
162
+ }
163
+ `;
164
+ export const QUOTE_CREATE_DRAFT_MUTATION = /* GraphQL */ `
165
+ mutation QuotesCreateDraft($input: CreateDraftQuoteInput!) {
166
+ createDraftQuote(input: $input) {
167
+ ${QUOTE_FIELDS}
168
+ }
169
+ }
170
+ `;
171
+ export const QUOTE_ADD_LINE_MUTATION = /* GraphQL */ `
172
+ mutation QuotesAddLine($quoteId: ID!, $input: AddQuoteLineInput!) {
173
+ addQuoteLine(quoteId: $quoteId, input: $input) {
174
+ ${QUOTE_FIELDS}
175
+ }
176
+ }
177
+ `;
178
+ export const QUOTE_UPDATE_LINE_MUTATION = /* GraphQL */ `
179
+ mutation QuotesUpdateLine($lineId: ID!, $input: UpdateQuoteLineInput!) {
180
+ updateQuoteLine(lineId: $lineId, input: $input) {
181
+ ${QUOTE_FIELDS}
182
+ }
183
+ }
184
+ `;
185
+ export const QUOTE_REMOVE_LINE_MUTATION = /* GraphQL */ `
186
+ mutation QuotesRemoveLine($lineId: ID!) {
187
+ removeQuoteLine(lineId: $lineId) {
188
+ ${QUOTE_FIELDS}
189
+ }
190
+ }
191
+ `;
192
+ export const QUOTE_SET_PRICING_MUTATION = /* GraphQL */ `
193
+ mutation QuotesSetPricing($quoteId: ID!, $input: SetQuotePricingInput!) {
194
+ setQuotePricing(quoteId: $quoteId, input: $input) {
195
+ ${QUOTE_FIELDS}
196
+ }
197
+ }
198
+ `;
199
+ export const QUOTE_EXTEND_VALIDITY_MUTATION = /* GraphQL */ `
200
+ mutation QuotesExtendValidity($quoteId: ID!, $input: ExtendQuoteValidityInput!) {
201
+ extendQuoteValidity(quoteId: $quoteId, input: $input) {
202
+ ${QUOTE_FIELDS}
203
+ }
204
+ }
205
+ `;
206
+ export const QUOTE_ASSIGN_SALES_REP_MUTATION = /* GraphQL */ `
207
+ mutation QuotesAssignSalesRep($quoteId: ID!, $salesRepUserId: ID) {
208
+ assignQuoteSalesRep(quoteId: $quoteId, salesRepUserId: $salesRepUserId) {
209
+ ${QUOTE_FIELDS}
210
+ }
211
+ }
212
+ `;
213
+ export const QUOTE_SALES_REPRESENTATIVES_QUERY = /* GraphQL */ `
214
+ query QuotesSalesRepresentatives($search: String) {
215
+ quoteSalesRepresentatives(search: $search) {
216
+ id
217
+ userId
218
+ firstName
219
+ lastName
220
+ emailAddress
221
+ }
222
+ }
223
+ `;
224
+ export const QUOTE_SUBMIT_MUTATION = /* GraphQL */ `
225
+ mutation QuotesSubmit($quoteId: ID!) {
226
+ submitQuoteForReview(quoteId: $quoteId) {
227
+ ${QUOTE_FIELDS}
228
+ }
229
+ }
230
+ `;
231
+ export const QUOTE_APPROVE_MUTATION = /* GraphQL */ `
232
+ mutation QuotesApprove($quoteId: ID!) {
233
+ approveQuote(quoteId: $quoteId) {
234
+ ${QUOTE_FIELDS}
235
+ }
236
+ }
237
+ `;
238
+ export const QUOTE_DECLINE_MUTATION = /* GraphQL */ `
239
+ mutation QuotesDecline($quoteId: ID!, $reason: String!) {
240
+ declineQuote(quoteId: $quoteId, reason: $reason) {
241
+ ${QUOTE_FIELDS}
242
+ }
243
+ }
244
+ `;
245
+ export const QUOTE_CANCEL_MUTATION = /* GraphQL */ `
246
+ mutation QuotesCancel($quoteId: ID!, $reason: String) {
247
+ cancelQuote(quoteId: $quoteId, reason: $reason) {
248
+ ${QUOTE_FIELDS}
249
+ }
250
+ }
251
+ `;
252
+ export const QUOTE_CONVERT_MUTATION = /* GraphQL */ `
253
+ mutation QuotesConvertToOrder($quoteId: ID!, $idempotencyKey: String) {
254
+ convertQuoteToOrder(quoteId: $quoteId, idempotencyKey: $idempotencyKey) {
255
+ id
256
+ code
257
+ state
258
+ currencyCode
259
+ totalWithTax
260
+ }
261
+ }
262
+ `;
263
+ export const QUOTE_GENERATE_DOCUMENT_MUTATION = /* GraphQL */ `
264
+ mutation QuotesGenerateDocument($quoteId: ID!) {
265
+ generateQuoteDocument(quoteId: $quoteId) {
266
+ ${DOCUMENT_FIELDS}
267
+ }
268
+ }
269
+ `;
270
+ export const QUOTE_SEND_TO_CUSTOMER_MUTATION = /* GraphQL */ `
271
+ mutation QuotesSendToCustomer($quoteId: ID!) {
272
+ sendQuoteToCustomer(quoteId: $quoteId)
273
+ }
274
+ `;
275
+ const QUOTE_CONFIG_FIELDS = /* GraphQL */ `
276
+ id
277
+ channelId
278
+ numberPrefix
279
+ numberSuffix
280
+ numberDigits
281
+ yearFormat
282
+ yearReset
283
+ lastResetYear
284
+ documentTitle
285
+ footerText
286
+ validityNoticeTemplate
287
+ columnProduct
288
+ columnSku
289
+ columnQuantity
290
+ columnUnitPriceExcl
291
+ columnUnitPriceDiscount
292
+ columnUnitPriceIncl
293
+ columnUnitPriceAfterDiscount
294
+ columnLineTotal
295
+ columnLineDiscount
296
+ columnTaxRate
297
+ columnTaxAmount
298
+ columnTotal
299
+ showLogo
300
+ defaultLanguage
301
+ termsTitle
302
+ termsBody
303
+ acceptanceInstructions
304
+ `;
305
+ export const QUOTE_CONFIG_QUERY = /* GraphQL */ `
306
+ query QuotesConfig {
307
+ quoteConfig {
308
+ ${QUOTE_CONFIG_FIELDS}
309
+ }
310
+ }
311
+ `;
312
+ export const QUOTE_UPDATE_CONFIG_MUTATION = /* GraphQL */ `
313
+ mutation QuotesUpdateConfig($input: UpdateQuoteConfigInput!) {
314
+ updateQuoteConfig(input: $input) {
315
+ ${QUOTE_CONFIG_FIELDS}
316
+ }
317
+ }
318
+ `;
319
+ export const QUOTE_SETTINGS_QUERY = /* GraphQL */ `
320
+ query QuotesSettings {
321
+ activeChannel {
322
+ id
323
+ defaultCurrencyCode
324
+ availableCurrencyCodes
325
+ customFields {
326
+ quotesEnabled
327
+ }
328
+ }
329
+ }
330
+ `;
331
+ function unexpected(field) {
332
+ return new UpstreamError({
333
+ code: 'UNEXPECTED_RESPONSE',
334
+ message: `The engine answered without "${field}".`,
335
+ status: 502,
336
+ service: 'vendure',
337
+ });
338
+ }
339
+ /** The key a caller retries under, on an error of a call whose key it did not choose. */
340
+ function withAttemptKey(error, key) {
341
+ if (error !== null && typeof error === 'object' && !('idempotencyKey' in error)) {
342
+ Object.defineProperty(error, 'idempotencyKey', { value: key, enumerable: true });
343
+ }
344
+ return error;
345
+ }
346
+ export function createQuotesModule(transport) {
347
+ /** One quote-answering call: the redaction stamp, and the field required. */
348
+ async function quoteCall(document, field, variables, options) {
349
+ const redaction = redactionWatcher();
350
+ const data = await transport.graphql({ api: 'admin', query: document, variables, onExtensions: redaction.onExtensions }, options);
351
+ const quote = data?.[field];
352
+ if (quote == null)
353
+ throw unexpected(field);
354
+ return redaction.mark(quote);
355
+ }
356
+ const lines = {
357
+ add: (quoteId, input, options) => quoteCall(QUOTE_ADD_LINE_MUTATION, 'addQuoteLine', { quoteId, input }, options),
358
+ update: (lineId, input, options) => quoteCall(QUOTE_UPDATE_LINE_MUTATION, 'updateQuoteLine', { lineId, input }, options),
359
+ remove: (lineId, options) => quoteCall(QUOTE_REMOVE_LINE_MUTATION, 'removeQuoteLine', { lineId }, options),
360
+ };
361
+ const config = {
362
+ get: (options) => fieldOf(transport, QUOTE_CONFIG_QUERY, 'quoteConfig', {}, options),
363
+ update: (input, options) => fieldOf(transport, QUOTE_UPDATE_CONFIG_MUTATION, 'updateQuoteConfig', { input }, options),
364
+ };
365
+ const settings = {
366
+ async get(options) {
367
+ const channel = await fieldOf(transport, QUOTE_SETTINGS_QUERY, 'activeChannel', {}, options);
368
+ if (!channel)
369
+ throw unexpected('activeChannel');
370
+ return {
371
+ quotesEnabled: channel.customFields?.quotesEnabled === true,
372
+ defaultCurrencyCode: channel.defaultCurrencyCode,
373
+ availableCurrencyCodes: channel.availableCurrencyCodes ?? [channel.defaultCurrencyCode],
374
+ };
375
+ },
376
+ };
377
+ return {
378
+ async list(options) {
379
+ const { take, skip, status, customerId, code, ...call } = options ?? {};
380
+ const redaction = redactionWatcher();
381
+ const data = await transport.graphql({
382
+ api: 'admin',
383
+ query: QUOTES_LIST_QUERY,
384
+ variables: { options: { take, skip, status, customerId, code } },
385
+ onExtensions: redaction.onExtensions,
386
+ }, call);
387
+ if (data?.quotes == null)
388
+ throw unexpected('quotes');
389
+ return {
390
+ items: data.quotes.items.map((quote) => redaction.mark(quote)),
391
+ totalItems: data.quotes.totalItems,
392
+ };
393
+ },
394
+ async get(quoteId, options) {
395
+ const redaction = redactionWatcher();
396
+ const data = await transport.graphql({
397
+ api: 'admin',
398
+ query: QUOTE_GET_QUERY,
399
+ variables: { id: quoteId },
400
+ onExtensions: redaction.onExtensions,
401
+ }, options);
402
+ const quote = data?.quote ?? null;
403
+ // `document` and `lines` are selected twice; GraphQL merges each into one object.
404
+ return quote ? redaction.mark(quote) : null;
405
+ },
406
+ createDraft: (input, options) => quoteCall(QUOTE_CREATE_DRAFT_MUTATION, 'createDraftQuote', { input }, options),
407
+ lines,
408
+ setPricing: (quoteId, input, options) => quoteCall(QUOTE_SET_PRICING_MUTATION, 'setQuotePricing', { quoteId, input }, options),
409
+ extendValidity: (quoteId, input, options) => quoteCall(QUOTE_EXTEND_VALIDITY_MUTATION, 'extendQuoteValidity', { quoteId, input }, options),
410
+ assignSalesRep: (quoteId, salesRepUserId, options) => quoteCall(QUOTE_ASSIGN_SALES_REP_MUTATION, 'assignQuoteSalesRep', { quoteId, salesRepUserId }, options),
411
+ salesRepresentatives: (term, options) => fieldOf(transport, QUOTE_SALES_REPRESENTATIVES_QUERY, 'quoteSalesRepresentatives', { search: term?.trim() ? term.trim() : null }, options),
412
+ submit: (quoteId, options) => quoteCall(QUOTE_SUBMIT_MUTATION, 'submitQuoteForReview', { quoteId }, options),
413
+ approve: (quoteId, options) => quoteCall(QUOTE_APPROVE_MUTATION, 'approveQuote', { quoteId }, options),
414
+ decline: (quoteId, reason, options) => quoteCall(QUOTE_DECLINE_MUTATION, 'declineQuote', { quoteId, reason }, options),
415
+ cancel: (quoteId, reason, options) => quoteCall(QUOTE_CANCEL_MUTATION, 'cancelQuote', { quoteId, reason: reason ?? null }, options),
416
+ async convertToOrder(quoteId, options) {
417
+ const key = options?.idempotencyKey ?? mintIdempotencyKey();
418
+ try {
419
+ const data = await transport.graphql({
420
+ api: 'admin',
421
+ query: QUOTE_CONVERT_MUTATION,
422
+ variables: { quoteId, idempotencyKey: key },
423
+ }, { ...options, idempotencyKey: key });
424
+ if (data?.convertQuoteToOrder == null)
425
+ throw unexpected('convertQuoteToOrder');
426
+ return { ...data.convertQuoteToOrder, idempotencyKey: key };
427
+ }
428
+ catch (error) {
429
+ throw withAttemptKey(error, key);
430
+ }
431
+ },
432
+ generateDocument: (quoteId, options) => fieldOf(transport, QUOTE_GENERATE_DOCUMENT_MUTATION, 'generateQuoteDocument', { quoteId }, options),
433
+ async sendToCustomer(quoteId, options) {
434
+ await fieldOf(transport, QUOTE_SEND_TO_CUSTOMER_MUTATION, 'sendQuoteToCustomer', { quoteId }, options);
435
+ },
436
+ config,
437
+ settings,
438
+ };
439
+ }
@@ -1,5 +1,6 @@
1
1
  import { type OrderScope, type Page, type Transport } from '@sixtyninecommerce/core';
2
2
  import type { BulkModule, BulkOperation } from '../bulk/index.js';
3
+ import { type FoundVariant } from '../catalog/variant-find.js';
3
4
  import type { JobHandle, JobsModule } from '../jobs/index.js';
4
5
  import type { Call, ListOptions } from '../types.js';
5
6
  /**
@@ -19,8 +20,22 @@ export interface ReportPeriod {
19
20
  from: string;
20
21
  to: string;
21
22
  }
23
+ /**
24
+ * The last `lastDays` days of the STORE's calendar, ending on the store's today:
25
+ * the day it is now in the store's time zone (`Channel.customFields.timezone`,
26
+ * else Europe/Amsterdam), never yours, never UTC. `{ lastDays: 1 }` is the
27
+ * store's today so far. Whole local days, so a DST day is 23 or 25 hours long;
28
+ * compared against the `lastDays` days before. A whole number from 1 to 366.
29
+ *
30
+ * Needs an engine that knows `lastDays` (vendure-server deployed with it); an
31
+ * older one refuses the request as a GraphQL validation error.
32
+ */
33
+ export interface ReportLastDays {
34
+ lastDays: number;
35
+ }
22
36
  export interface ReportOverviewInput {
23
- period: ReportPeriod;
37
+ /** A `from`/`to` range, or the store's last N days. */
38
+ period: ReportPeriod | ReportLastDays;
24
39
  /**
25
40
  * One stock location of this store (`stockLocationId`). Keeps the orders whose
26
41
  * stock was allocated or sold there, or that stated it at entry; an order
@@ -100,11 +115,12 @@ export interface PopularVariantsInput {
100
115
  /** 1–3650, default 90. */
101
116
  sinceDays?: number;
102
117
  }
103
- export interface PopularVariant {
104
- id: string;
118
+ /**
119
+ * A best-selling variant, as a picker shows it: `catalog.variants.find`'s variant (store price,
120
+ * stock, product), plus its `productId`.
121
+ */
122
+ export interface PopularVariant extends FoundVariant {
105
123
  productId: string;
106
- name: string;
107
- sku: string;
108
124
  }
109
125
  export interface LowStockInput extends ListOptions {
110
126
  /** Rows whose `available` is at or below this; a whole number, 0–1,000,000. */
@@ -166,12 +182,27 @@ export type ReportExport = Omit<BulkOperation, 'entity'> & {
166
182
  * `admin.platform.reports.fleet` (SuperAdmin session only).
167
183
  */
168
184
  export interface ReportsModule {
169
- /** Orders, revenue and customers of a period, by the store's own days. */
185
+ /**
186
+ * Orders, revenue and customers of a period, by the store's own days. Needs `ReadOrder`.
187
+ *
188
+ * Refused before anything is asked, `INVALID_REPORT_PERIOD` (`ValidationError`, 400):
189
+ * `lastDays` that is not a whole number from 1 to 366, or a period carrying both `lastDays`
190
+ * and `from`/`to`. The engine refuses the same, and a malformed `from`/`to`.
191
+ *
192
+ * @effect read
193
+ */
170
194
  overview(input: ReportOverviewInput, options?: Call): Promise<ReportOverview>;
171
195
  /**
172
196
  * Variants ranked by units sold in placed orders of the last `sinceDays`
173
197
  * days, most first. A `Page` because every list is one; it is always a
174
198
  * single, final page.
199
+ *
200
+ * Each variant carries what a picker shows: its store price (null when the store has none,
201
+ * priced in the call's `tenant.currency` when one is named), its stock and its product. A
202
+ * hidden carrier variant is never ranked, and a variant the store no longer holds is left out,
203
+ * so the list can be shorter than `take`. Needs `ReadOrder`.
204
+ *
205
+ * @effect read
175
206
  */
176
207
  popularVariants(input?: PopularVariantsInput, options?: Call): Promise<Page<PopularVariant>>;
177
208
  /**
@@ -189,10 +220,42 @@ export interface ReportsModule {
189
220
  * restriction withholds.
190
221
  */
191
222
  export(kind: ReportExportKind, filter: ReportExportFilter, options?: Call): Promise<JobHandle<ReportExport>>;
223
+ /**
224
+ * Accounts-receivable aging: every customer who owes on an AR invoice, their outstanding split
225
+ * into buckets by days past due (against the store's today), one row per customer and
226
+ * currency, never summed across currencies. An ERP-linked customer's figures come from the
227
+ * ERP's mirrored invoices (`erpLinked`). Needs `ReadOrder`.
228
+ *
229
+ * @effect read
230
+ * @permission ReadOrder
231
+ */
232
+ arAging(options?: Call): Promise<ArAgingRow[]>;
192
233
  }
234
+ /** One customer's outstanding AR, in one currency, by days past due. Minor units. */
235
+ export interface ArAgingRow {
236
+ customerId: string;
237
+ customerName: string;
238
+ /** Not yet due. */
239
+ current: number;
240
+ d1_30: number;
241
+ d31_60: number;
242
+ d61_90: number;
243
+ d90_plus: number;
244
+ /** The sum of the buckets. */
245
+ total: number;
246
+ currencyCode: string;
247
+ /** True when the figures are the ERP's. */
248
+ erpLinked: boolean;
249
+ }
250
+ export declare const AR_AGING_QUERY = "\n query ArAgingReport {\n b2bArAgingReport {\n customerId\n customerName\n current\n d1_30\n d31_60\n d61_90\n d90_plus\n total\n currencyCode\n erpLinked\n }\n }\n";
193
251
  /** Only the §9.3 generation is selected — never the deprecated Float/String fields. */
194
- export declare const REPORT_OVERVIEW_QUERY = "\n query ReportOverview($range: ReportPeriodInput!, $locationId: ID, $orderScope: OrderScope) {\n commerceOverviewMetrics(range: $range, locationId: $locationId, orderScope: $orderScope) {\n currencyCode\n timeZone\n from\n to\n previousFrom\n previousTo\n current { \n orderCount\n cancelledCount\n revenueMinor\n revenueNetMinor\n averageOrderValueMinor\n averageOrderValueNetMinor\n ordersInOtherCurrencies\n }\n previous { \n orderCount\n cancelledCount\n revenueMinor\n revenueNetMinor\n averageOrderValueMinor\n averageOrderValueNetMinor\n ordersInOtherCurrencies\n }\n revenueSeries { startsAt valueMinor valueNetMinor }\n orderSeries { startsAt value }\n newCustomers\n previousNewCustomers\n awaitingFulfillment\n statusBreakdown { state count }\n topProducts { productVariantId productId name sku unitsSold }\n }\n }\n";
195
- export declare const POPULAR_VARIANTS_QUERY = "\n query ReportPopularVariants($take: Int, $sinceDays: Int) {\n popularProductVariantsInChannel(take: $take, sinceDays: $sinceDays) {\n id\n productId\n name\n sku\n }\n }\n";
252
+ export declare const REPORT_OVERVIEW_QUERY = "\n query ReportOverview($range: ReportPeriodInput!, $locationId: ID, $orderScope: OrderScope) {\n commerceOverviewMetrics(range: $range, locationId: $locationId, orderScope: $orderScope) {\n \n currencyCode\n timeZone\n from\n to\n previousFrom\n previousTo\n current { \n orderCount\n cancelledCount\n revenueMinor\n revenueNetMinor\n averageOrderValueMinor\n averageOrderValueNetMinor\n ordersInOtherCurrencies\n }\n previous { \n orderCount\n cancelledCount\n revenueMinor\n revenueNetMinor\n averageOrderValueMinor\n averageOrderValueNetMinor\n ordersInOtherCurrencies\n }\n revenueSeries { startsAt valueMinor valueNetMinor }\n orderSeries { startsAt value }\n newCustomers\n previousNewCustomers\n awaitingFulfillment\n statusBreakdown { state count }\n topProducts { productVariantId productId name sku unitsSold }\n\n }\n }\n";
253
+ /**
254
+ * `lastDays` has a document of its own, so a `from`/`to` caller never sends an
255
+ * argument an engine without `lastDays` would refuse (each service deploys alone).
256
+ */
257
+ export declare const REPORT_OVERVIEW_LAST_DAYS_QUERY = "\n query ReportOverviewLastDays($lastDays: Int!, $locationId: ID, $orderScope: OrderScope) {\n commerceOverviewMetrics(lastDays: $lastDays, locationId: $locationId, orderScope: $orderScope) {\n \n currencyCode\n timeZone\n from\n to\n previousFrom\n previousTo\n current { \n orderCount\n cancelledCount\n revenueMinor\n revenueNetMinor\n averageOrderValueMinor\n averageOrderValueNetMinor\n ordersInOtherCurrencies\n }\n previous { \n orderCount\n cancelledCount\n revenueMinor\n revenueNetMinor\n averageOrderValueMinor\n averageOrderValueNetMinor\n ordersInOtherCurrencies\n }\n revenueSeries { startsAt valueMinor valueNetMinor }\n orderSeries { startsAt value }\n newCustomers\n previousNewCustomers\n awaitingFulfillment\n statusBreakdown { state count }\n topProducts { productVariantId productId name sku unitsSold }\n\n }\n }\n";
258
+ export declare const POPULAR_VARIANTS_QUERY = "\n query ReportPopularVariants($take: Int, $sinceDays: Int) {\n popularProductVariantsInChannel(take: $take, sinceDays: $sinceDays) {\n productId\n \n \n id\n name\n sku\n enabled\n storePrice {\n price\n priceWithTax\n currencyCode\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockOnHand\n stockAllocated\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n stockLocation {\n id\n name\n }\n }\n\n product {\n \n id\n name\n slug\n featuredAsset {\n id\n preview\n }\n\n }\n\n }\n }\n";
196
259
  export declare const LOW_STOCK_QUERY = "\n query ReportLowStock($threshold: Int!, $locationId: ID, $options: LowStockVariantListOptions) {\n lowStockVariants(threshold: $threshold, locationId: $locationId, options: $options) {\n items {\n productVariantId\n productId\n sku\n name\n stockLocationId\n stockLocationName\n stockOnHand\n stockAllocated\n reserved\n available\n }\n totalItems\n }\n }\n";
197
260
  export declare function createReportsModule(transport: Transport, bulk: BulkModule, jobs: JobsModule,
198
261
  /** The client's `tenant.location`, read per call (a derived client has its own). */
@@ -1,4 +1,23 @@
1
- import { createPage, pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
1
+ import { createPage, pageFromVendureList, readOffsetCursor, ValidationError, } from '@sixtyninecommerce/core';
2
+ import { FOUND_VARIANT_WITH_PRODUCT_FIELDS, toFoundVariant, } from '../catalog/variant-find.js';
3
+ /** The longest period a report covers, in days (the engine's bound). */
4
+ const MAX_REPORT_DAYS = 366;
5
+ export const AR_AGING_QUERY = /* GraphQL */ `
6
+ query ArAgingReport {
7
+ b2bArAgingReport {
8
+ customerId
9
+ customerName
10
+ current
11
+ d1_30
12
+ d31_60
13
+ d61_90
14
+ d90_plus
15
+ total
16
+ currencyCode
17
+ erpLinked
18
+ }
19
+ }
20
+ `;
2
21
  const TOTALS_FIELDS = /* GraphQL */ `
3
22
  orderCount
4
23
  cancelledCount
@@ -8,10 +27,7 @@ const TOTALS_FIELDS = /* GraphQL */ `
8
27
  averageOrderValueNetMinor
9
28
  ordersInOtherCurrencies
10
29
  `;
11
- /** Only the §9.3 generation is selected — never the deprecated Float/String fields. */
12
- export const REPORT_OVERVIEW_QUERY = /* GraphQL */ `
13
- query ReportOverview($range: ReportPeriodInput!, $locationId: ID, $orderScope: OrderScope) {
14
- commerceOverviewMetrics(range: $range, locationId: $locationId, orderScope: $orderScope) {
30
+ const OVERVIEW_FIELDS = /* GraphQL */ `
15
31
  currencyCode
16
32
  timeZone
17
33
  from
@@ -27,16 +43,31 @@ export const REPORT_OVERVIEW_QUERY = /* GraphQL */ `
27
43
  awaitingFulfillment
28
44
  statusBreakdown { state count }
29
45
  topProducts { productVariantId productId name sku unitsSold }
46
+ `;
47
+ /** Only the §9.3 generation is selected — never the deprecated Float/String fields. */
48
+ export const REPORT_OVERVIEW_QUERY = /* GraphQL */ `
49
+ query ReportOverview($range: ReportPeriodInput!, $locationId: ID, $orderScope: OrderScope) {
50
+ commerceOverviewMetrics(range: $range, locationId: $locationId, orderScope: $orderScope) {
51
+ ${OVERVIEW_FIELDS}
52
+ }
53
+ }
54
+ `;
55
+ /**
56
+ * `lastDays` has a document of its own, so a `from`/`to` caller never sends an
57
+ * argument an engine without `lastDays` would refuse (each service deploys alone).
58
+ */
59
+ export const REPORT_OVERVIEW_LAST_DAYS_QUERY = /* GraphQL */ `
60
+ query ReportOverviewLastDays($lastDays: Int!, $locationId: ID, $orderScope: OrderScope) {
61
+ commerceOverviewMetrics(lastDays: $lastDays, locationId: $locationId, orderScope: $orderScope) {
62
+ ${OVERVIEW_FIELDS}
30
63
  }
31
64
  }
32
65
  `;
33
66
  export const POPULAR_VARIANTS_QUERY = /* GraphQL */ `
34
67
  query ReportPopularVariants($take: Int, $sinceDays: Int) {
35
68
  popularProductVariantsInChannel(take: $take, sinceDays: $sinceDays) {
36
- id
37
69
  productId
38
- name
39
- sku
70
+ ${FOUND_VARIANT_WITH_PRODUCT_FIELDS}
40
71
  }
41
72
  }
42
73
  `;
@@ -60,6 +91,32 @@ export const LOW_STOCK_QUERY = /* GraphQL */ `
60
91
  }
61
92
  `;
62
93
  const DEFAULT_LOW_STOCK_PAGE = 50;
94
+ function invalidPeriod(message) {
95
+ return new ValidationError({
96
+ code: 'INVALID_REPORT_PERIOD',
97
+ message,
98
+ status: 400,
99
+ service: 'vendure',
100
+ });
101
+ }
102
+ /** The overview's request for its period form, refused at the boundary like the engine would. */
103
+ function overviewRequest(period) {
104
+ const given = (period ?? {});
105
+ if (given.lastDays === undefined) {
106
+ return {
107
+ query: REPORT_OVERVIEW_QUERY,
108
+ variables: { range: { from: given.from, to: given.to } },
109
+ };
110
+ }
111
+ if (given.from !== undefined || given.to !== undefined) {
112
+ throw invalidPeriod('Give either `lastDays` or `from`/`to`, not both: each one names the whole period.');
113
+ }
114
+ const { lastDays } = given;
115
+ if (!Number.isInteger(lastDays) || lastDays < 1 || lastDays > MAX_REPORT_DAYS) {
116
+ throw invalidPeriod(`\`lastDays\` must be a whole number from 1 to ${MAX_REPORT_DAYS}.`);
117
+ }
118
+ return { query: REPORT_OVERVIEW_LAST_DAYS_QUERY, variables: { lastDays } };
119
+ }
63
120
  /** The bulk entity a report kind is exported as. */
64
121
  const EXPORT_ENTITY = { overview: 'report_overview' };
65
122
  /** Filter keys travel as the service spells them — see `bulk/index.ts`'s `FILTER_PASSTHROUGH`. */
@@ -79,13 +136,19 @@ clientLocation = () => undefined) {
79
136
  }, input);
80
137
  return pageFromVendureList(data.lowStockVariants, skip, take, (cursor) => lowStockPage(input, readOffsetCursor(cursor), take));
81
138
  };
139
+ const arAging = async (options) => {
140
+ const data = await transport.graphql({ api: 'admin', query: AR_AGING_QUERY }, options);
141
+ return data.b2bArAgingReport;
142
+ };
82
143
  return {
144
+ arAging,
83
145
  async overview(input, options) {
146
+ const request = overviewRequest(input.period);
84
147
  const data = await transport.graphql({
85
148
  api: 'admin',
86
- query: REPORT_OVERVIEW_QUERY,
149
+ query: request.query,
87
150
  variables: {
88
- range: { from: input.period.from, to: input.period.to },
151
+ ...request.variables,
89
152
  locationId: input.locationId ?? null,
90
153
  orderScope: input.orderScope ?? null,
91
154
  },
@@ -111,7 +174,7 @@ clientLocation = () => undefined) {
111
174
  query: POPULAR_VARIANTS_QUERY,
112
175
  variables: { take: input?.take ?? null, sinceDays: input?.sinceDays ?? null },
113
176
  }, options);
114
- const items = data.popularProductVariantsInChannel;
177
+ const items = data.popularProductVariantsInChannel.map((row) => ({ ...toFoundVariant(row), productId: row.productId }));
115
178
  return createPage(items, { nextCursor: null, hasNext: false, total: items.length }, () => {
116
179
  // Unreachable while `hasNext` is false — `Page.next()` short-circuits.
117
180
  throw new Error('popularVariants returns one ranked list; there is no next page');