@sixtyninecommerce/admin 0.9.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 (258) hide show
  1. package/README.md +4598 -128
  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 +318 -17
  9. package/dist/catalog/products.js +182 -14
  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 +282 -25
  21. package/dist/catalog/variants.js +173 -25
  22. package/dist/catalog/visibility.d.ts +20 -5
  23. package/dist/catalog/visibility.js +19 -1
  24. package/dist/client.d.ts +116 -9
  25. package/dist/client.js +41 -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 +121 -10
  63. package/dist/fulfillments/index.js +120 -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 +129 -30
  71. package/dist/index.js +47 -9
  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 +40 -0
  87. package/dist/integrations/index.js +24 -0
  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 +114 -0
  91. package/dist/integrations/pipelines.js +46 -0
  92. package/dist/integrations/types.d.ts +322 -0
  93. package/dist/integrations/types.js +8 -0
  94. package/dist/jobs/bulk-action-run.d.ts +11 -2
  95. package/dist/jobs/bulk-action-run.js +4 -5
  96. package/dist/locations/index.d.ts +66 -0
  97. package/dist/locations/index.js +31 -0
  98. package/dist/meta/index.d.ts +162 -0
  99. package/dist/orders/bulk.d.ts +75 -0
  100. package/dist/orders/bulk.js +150 -0
  101. package/dist/orders/cancel-with-money.d.ts +227 -0
  102. package/dist/orders/cancel-with-money.js +153 -0
  103. package/dist/orders/commit-edit.d.ts +7 -1
  104. package/dist/orders/commit-edit.js +5 -1
  105. package/dist/orders/deposit-policy.d.ts +103 -0
  106. package/dist/orders/deposit-policy.js +202 -0
  107. package/dist/orders/drafts.d.ts +800 -0
  108. package/dist/orders/drafts.js +771 -0
  109. package/dist/orders/fields.d.ts +245 -0
  110. package/dist/orders/fields.js +188 -0
  111. package/dist/orders/get.d.ts +29 -19
  112. package/dist/orders/get.js +17 -8
  113. package/dist/orders/index.d.ts +125 -5
  114. package/dist/orders/index.js +35 -2
  115. package/dist/orders/lifecycle.d.ts +128 -0
  116. package/dist/orders/lifecycle.js +158 -0
  117. package/dist/orders/line-price-preview.d.ts +7 -2
  118. package/dist/orders/list.d.ts +222 -0
  119. package/dist/orders/list.js +377 -0
  120. package/dist/orders/manual-discounts.d.ts +11 -2
  121. package/dist/orders/manual-discounts.js +2 -0
  122. package/dist/orders/manual-pricing.d.ts +5 -0
  123. package/dist/orders/order-entry.d.ts +20 -7
  124. package/dist/orders/plan-edit.d.ts +1 -1
  125. package/dist/orders/plan-edit.js +4 -1
  126. package/dist/orders/settings.d.ts +201 -0
  127. package/dist/orders/settings.js +325 -0
  128. package/dist/orders/status.d.ts +159 -13
  129. package/dist/orders/status.js +21 -0
  130. package/dist/orders/surcharges.d.ts +8 -1
  131. package/dist/orders/surcharges.js +3 -2
  132. package/dist/payments/account-credit.d.ts +30 -0
  133. package/dist/payments/account-credit.js +19 -0
  134. package/dist/payments/ar.d.ts +292 -0
  135. package/dist/payments/ar.js +223 -0
  136. package/dist/payments/connected-account.d.ts +47 -0
  137. package/dist/payments/connected-account.js +61 -0
  138. package/dist/payments/credit-noted-fields.d.ts +12 -0
  139. package/dist/payments/credit-noted-fields.js +37 -0
  140. package/dist/payments/index.d.ts +263 -10
  141. package/dist/payments/index.js +133 -1
  142. package/dist/payments/methods.d.ts +332 -0
  143. package/dist/payments/methods.js +363 -0
  144. package/dist/payments/mollie-methods.d.ts +159 -0
  145. package/dist/payments/mollie-methods.js +141 -0
  146. package/dist/payments/payment-links.d.ts +180 -0
  147. package/dist/payments/payment-links.js +97 -0
  148. package/dist/payments/provider-keys.d.ts +150 -0
  149. package/dist/payments/provider-keys.js +118 -0
  150. package/dist/payments/providers.d.ts +86 -11
  151. package/dist/payments/providers.js +29 -0
  152. package/dist/payments/requests.d.ts +144 -0
  153. package/dist/payments/requests.js +92 -0
  154. package/dist/payments/store-credit-fields.d.ts +15 -0
  155. package/dist/payments/store-credit-fields.js +51 -0
  156. package/dist/pickups/index.d.ts +104 -0
  157. package/dist/pickups/index.js +105 -0
  158. package/dist/platform/channels.d.ts +132 -0
  159. package/dist/platform/channels.js +128 -0
  160. package/dist/platform/geo.d.ts +123 -0
  161. package/dist/platform/geo.js +144 -0
  162. package/dist/platform/tax.d.ts +154 -0
  163. package/dist/platform/tax.js +139 -0
  164. package/dist/pos/anomalies.d.ts +158 -0
  165. package/dist/pos/anomalies.js +64 -0
  166. package/dist/pos/assisted-selling.d.ts +79 -0
  167. package/dist/pos/assisted-selling.js +38 -0
  168. package/dist/pos/exchanges.d.ts +103 -0
  169. package/dist/pos/exchanges.js +22 -0
  170. package/dist/pos/gift-cards.d.ts +135 -0
  171. package/dist/pos/gift-cards.js +47 -0
  172. package/dist/pos/index.d.ts +2125 -32
  173. package/dist/pos/index.js +742 -4
  174. package/dist/pos/no-receipt.d.ts +94 -0
  175. package/dist/pos/no-receipt.js +25 -0
  176. package/dist/pos/offline-sales.d.ts +187 -0
  177. package/dist/pos/offline-sales.js +116 -0
  178. package/dist/pos/pay-by-link.d.ts +143 -0
  179. package/dist/pos/pay-by-link.js +57 -0
  180. package/dist/pos/quick-keys.d.ts +103 -0
  181. package/dist/pos/quick-keys.js +45 -0
  182. package/dist/pos/reports.d.ts +360 -0
  183. package/dist/pos/reports.js +62 -0
  184. package/dist/pos/returns.d.ts +217 -0
  185. package/dist/pos/returns.js +53 -0
  186. package/dist/pricing/bulk-coupons.d.ts +139 -0
  187. package/dist/pricing/bulk-coupons.js +112 -0
  188. package/dist/pricing/index.d.ts +177 -4
  189. package/dist/pricing/index.js +176 -1
  190. package/dist/pricing/price-lists.d.ts +604 -0
  191. package/dist/pricing/price-lists.js +600 -0
  192. package/dist/pricing/vouchers.d.ts +103 -0
  193. package/dist/pricing/vouchers.js +69 -0
  194. package/dist/purchasing/alerts.d.ts +89 -0
  195. package/dist/purchasing/alerts.js +62 -0
  196. package/dist/purchasing/cost.d.ts +110 -0
  197. package/dist/purchasing/cost.js +86 -0
  198. package/dist/purchasing/index.d.ts +36 -0
  199. package/dist/purchasing/index.js +16 -0
  200. package/dist/purchasing/purchase-orders.d.ts +548 -0
  201. package/dist/purchasing/purchase-orders.js +491 -0
  202. package/dist/purchasing/replenishment.d.ts +281 -0
  203. package/dist/purchasing/replenishment.js +240 -0
  204. package/dist/purchasing/settings.d.ts +152 -0
  205. package/dist/purchasing/settings.js +181 -0
  206. package/dist/purchasing/suppliers.d.ts +476 -0
  207. package/dist/purchasing/suppliers.js +438 -0
  208. package/dist/quotes/index.d.ts +501 -0
  209. package/dist/quotes/index.js +439 -0
  210. package/dist/reports/index.d.ts +71 -8
  211. package/dist/reports/index.js +74 -11
  212. package/dist/returns/index.d.ts +818 -0
  213. package/dist/returns/index.js +844 -0
  214. package/dist/search/index.d.ts +174 -0
  215. package/dist/search/index.js +125 -0
  216. package/dist/shipping/definitions.d.ts +29 -0
  217. package/dist/shipping/definitions.js +47 -0
  218. package/dist/shipping/fulfillment-profiles.d.ts +174 -0
  219. package/dist/shipping/fulfillment-profiles.js +104 -0
  220. package/dist/shipping/hendriks.d.ts +97 -0
  221. package/dist/shipping/hendriks.js +50 -0
  222. package/dist/shipping/index.d.ts +48 -0
  223. package/dist/shipping/index.js +25 -0
  224. package/dist/shipping/methods.d.ts +198 -0
  225. package/dist/shipping/methods.js +240 -0
  226. package/dist/shipping/myparcel.d.ts +254 -0
  227. package/dist/shipping/myparcel.js +178 -0
  228. package/dist/shipping/parcel-count-suggestion.d.ts +2 -0
  229. package/dist/shipping/parcel-rule.d.ts +6 -5
  230. package/dist/shipping/parcel-rule.js +21 -23
  231. package/dist/shipping/rate-tables.d.ts +179 -0
  232. package/dist/shipping/rate-tables.js +184 -0
  233. package/dist/stock/index.d.ts +10 -3
  234. package/dist/stock/index.js +3 -0
  235. package/dist/stock/locations.d.ts +144 -0
  236. package/dist/stock/locations.js +168 -0
  237. package/dist/stock/transfers.d.ts +216 -43
  238. package/dist/stock/transfers.js +140 -13
  239. package/dist/stored-value/index.d.ts +214 -4
  240. package/dist/stored-value/index.js +152 -0
  241. package/dist/subscriptions/index.d.ts +682 -0
  242. package/dist/subscriptions/index.js +815 -0
  243. package/dist/tax/index.d.ts +76 -1
  244. package/dist/tax/index.js +50 -0
  245. package/dist/tenant/email-style.d.ts +102 -0
  246. package/dist/tenant/email-style.js +72 -0
  247. package/dist/tenant/email-templates.d.ts +66 -0
  248. package/dist/tenant/email-templates.js +54 -0
  249. package/dist/tenant/notifications.d.ts +23 -17
  250. package/dist/tenant/settings-sources.d.ts +1 -1
  251. package/dist/tenant/time-zone.d.ts +17 -0
  252. package/dist/tenant/time-zone.js +11 -0
  253. package/dist/types.d.ts +51 -4
  254. package/dist/users/index.d.ts +3 -0
  255. package/dist/users/index.js +2 -0
  256. package/dist/users/warehouses.d.ts +50 -0
  257. package/dist/users/warehouses.js +51 -0
  258. package/package.json +2 -2
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Product reviews (headless spec §9.2 `catalog`: "reviews moderation"; W10-117): the reviews the
3
+ * store's customers wrote, moderating them (what the storefront shows under the store's name),
4
+ * the store's reply to one, the review statistics, and the review attributes (the questions a
5
+ * review form asks: "Quality", "Fit", …).
6
+ *
7
+ * - **What the storefront shows**: only `APPROVED` reviews, with the reviewer's display name,
8
+ * rating, title, text, attribute answers and the store's reply. `PENDING` (waiting), `FLAGGED`
9
+ * (the automatic checks or a shopper's report asked for a look) and `REJECTED` reviews are never
10
+ * shown. Approving publishes the review; rejecting, flagging or re-opening it (`PENDING`) takes it
11
+ * off. Each moderation is recorded in the review's moderation log with the staff member and the
12
+ * reason given.
13
+ * - **The reply** is the store's own text, shown under an approved review on the storefront.
14
+ *
15
+ * Customer data (headless spec §6.3): an app installation below `customerData: full` reads every
16
+ * review with what the customer typed blanked: `title`, `body`, `attributeValues` (ratings and
17
+ * self-declared details such as an age band) and `ipAddressHash`, the edit history's previous
18
+ * texts and the moderation log's reasons and AI analysis answer `null`, and so does
19
+ * `customerDisplayName` below `identity`. Every record of such an answer carries `redacted: true`,
20
+ * so a blanked field never reads as "the customer wrote none". A `search` below `full` is refused
21
+ * `CUSTOMER_DATA_NOT_GRANTED` (`PermissionDeniedError`, 403). The answers of the writes are
22
+ * blanked the same way.
23
+ *
24
+ * Permissions: reads need `ReadCatalog`, writes `UpdateCatalog`. Every row is the store's own: a
25
+ * review or attribute of another store is never answered and never changed.
26
+ *
27
+ * Refusals, GraphQL errors mapped by `core`. An unknown id and another store's get the same
28
+ * answer, so none tells whether another store holds the row:
29
+ * - `ENTITY_NOT_FOUND` → `NotFoundError` (404): a review or attribute the store does not have. An
30
+ * id that is not a uuid gets it too, before anything is read.
31
+ * - `REVIEW_ATTRIBUTE_SLUG_TAKEN` → `ConflictError` (409): another of the store's attributes holds
32
+ * the slug, the one derived from the label included, also when two writes race for it.
33
+ * - `REVIEW_ATTRIBUTE_COLLECTION_NOT_FOUND` → `NotFoundError` (404): `attributes.setCollections`
34
+ * named a collection the store does not hold, each in `details` (`field` `/collectionIds/<i>`).
35
+ * - `BULK_ACTION_INPUT_INVALID` → `ValidationError` (400): a `bulkModerate` selection of no
36
+ * review, more than {@link MAX_BULK_REVIEWS} or one twice, refused before anything is sent.
37
+ * - `USER_INPUT_ERROR` → `ValidationError` (400), in the engine's words: a reply over 2,000
38
+ * characters, an attribute type other than `rating` / `selection`, a selection with fewer than
39
+ * two options.
40
+ * - `FORBIDDEN` → `PermissionDeniedError` (403) without the permission.
41
+ *
42
+ * Release: `bulkModerate` needs a platform release with `moderateReviews` (deploy vendure-server
43
+ * first: an older one refuses the document). Every other method is served by every release this
44
+ * SDK supports; an older release answers each not-found and a taken slug as `USER_INPUT_ERROR`
45
+ * (400), a taken slug as an uncoded database error, and takes another store's collection in
46
+ * `setCollections`. No method deduplicates on `idempotencyKey`.
47
+ */
48
+ import { pageFromVendureList, readOffsetCursor, UpstreamError, ValidationError, } from '@sixtyninecommerce/core';
49
+ import { redactionWatcher } from '../customer-data-redaction.js';
50
+ import { assertDeleted, DELETION_FIELDS, fieldOf } from './types.js';
51
+ export const REVIEW_STATUSES = [
52
+ 'PENDING',
53
+ 'APPROVED',
54
+ 'REJECTED',
55
+ 'FLAGGED',
56
+ ];
57
+ /** How many reviews one list page holds at most: the engine answers no more. */
58
+ export const MAX_REVIEW_PAGE_SIZE = 50;
59
+ /** How many reviews one `bulkModerate` call may name. */
60
+ export const MAX_BULK_REVIEWS = 100;
61
+ const REVIEW_FIELDS = /* GraphQL */ `
62
+ id
63
+ channelId
64
+ customerId
65
+ productId
66
+ productName
67
+ variantId
68
+ rating
69
+ title
70
+ body
71
+ status
72
+ isVerifiedPurchase
73
+ purchaseDate
74
+ deliveryDate
75
+ isEdited
76
+ editCount
77
+ customerDisplayName
78
+ imageAssetIds
79
+ videoAssetIds
80
+ attributeValues
81
+ helpfulCount
82
+ notHelpfulCount
83
+ merchantReply
84
+ merchantReplyAt
85
+ ipAddressHash
86
+ createdAt
87
+ updatedAt
88
+ `;
89
+ const REVIEW_DETAIL_FIELDS = /* GraphQL */ `
90
+ ${REVIEW_FIELDS}
91
+ editHistory {
92
+ id
93
+ previousRating
94
+ previousTitle
95
+ previousBody
96
+ previousAttributeValues
97
+ editedAt
98
+ reEnteredModeration
99
+ createdAt
100
+ }
101
+ moderationLogs {
102
+ id
103
+ step
104
+ action
105
+ reason
106
+ confidence
107
+ aiAnalysis
108
+ performedBy
109
+ tokenUsage
110
+ createdAt
111
+ }
112
+ `;
113
+ const ATTRIBUTE_FIELDS = /* GraphQL */ `
114
+ id
115
+ slug
116
+ label
117
+ inputType
118
+ options
119
+ isRequired
120
+ enabled
121
+ section
122
+ position
123
+ collectionIds
124
+ createdAt
125
+ updatedAt
126
+ `;
127
+ export const PRODUCT_REVIEWS_QUERY = /* GraphQL */ `
128
+ query CatalogProductReviews($options: AdminReviewListOptions) {
129
+ adminProductReviews(options: $options) {
130
+ items { ${REVIEW_FIELDS} }
131
+ totalItems
132
+ }
133
+ }
134
+ `;
135
+ export const PRODUCT_REVIEW_QUERY = /* GraphQL */ `
136
+ query CatalogProductReview($id: ID!) {
137
+ adminProductReview(id: $id) { ${REVIEW_DETAIL_FIELDS} }
138
+ }
139
+ `;
140
+ export const REVIEW_STATS_QUERY = /* GraphQL */ `
141
+ query CatalogReviewStats {
142
+ reviewStats {
143
+ totalReviews
144
+ pendingCount
145
+ approvedCount
146
+ rejectedCount
147
+ flaggedCount
148
+ averageRating
149
+ ratingsThisWeek
150
+ ratingsThisMonth
151
+ }
152
+ }
153
+ `;
154
+ export const MODERATE_REVIEW_MUTATION = /* GraphQL */ `
155
+ mutation CatalogModerateReview($id: ID!, $action: ReviewStatus!, $reason: String) {
156
+ moderateReview(id: $id, action: $action, reason: $reason) { ${REVIEW_FIELDS} }
157
+ }
158
+ `;
159
+ export const MODERATE_REVIEWS_MUTATION = /* GraphQL */ `
160
+ mutation CatalogModerateReviews($input: ModerateReviewsInput!) {
161
+ moderateReviews(input: $input) {
162
+ items { id status code message }
163
+ }
164
+ }
165
+ `;
166
+ export const SET_MERCHANT_REPLY_MUTATION = /* GraphQL */ `
167
+ mutation CatalogSetMerchantReply($reviewId: ID!, $reply: String!) {
168
+ setMerchantReply(reviewId: $reviewId, reply: $reply) { ${REVIEW_FIELDS} }
169
+ }
170
+ `;
171
+ export const REMOVE_MERCHANT_REPLY_MUTATION = /* GraphQL */ `
172
+ mutation CatalogRemoveMerchantReply($reviewId: ID!) {
173
+ removeMerchantReply(reviewId: $reviewId) { ${REVIEW_FIELDS} }
174
+ }
175
+ `;
176
+ export const REVIEW_ATTRIBUTES_QUERY = /* GraphQL */ `
177
+ query CatalogReviewAttributes {
178
+ adminReviewAttributes { ${ATTRIBUTE_FIELDS} }
179
+ }
180
+ `;
181
+ export const CREATE_REVIEW_ATTRIBUTE_MUTATION = /* GraphQL */ `
182
+ mutation CatalogCreateReviewAttribute($input: AdminReviewAttributeInput!) {
183
+ createReviewAttribute(input: $input) { ${ATTRIBUTE_FIELDS} }
184
+ }
185
+ `;
186
+ export const UPDATE_REVIEW_ATTRIBUTE_MUTATION = /* GraphQL */ `
187
+ mutation CatalogUpdateReviewAttribute($id: ID!, $input: UpdateReviewAttributeInput!) {
188
+ updateReviewAttribute(id: $id, input: $input) { ${ATTRIBUTE_FIELDS} }
189
+ }
190
+ `;
191
+ export const DELETE_REVIEW_ATTRIBUTE_MUTATION = /* GraphQL */ `
192
+ mutation CatalogDeleteReviewAttribute($id: ID!) {
193
+ deleteReviewAttribute(id: $id) { ${DELETION_FIELDS} }
194
+ }
195
+ `;
196
+ export const REORDER_REVIEW_ATTRIBUTES_MUTATION = /* GraphQL */ `
197
+ mutation CatalogReorderReviewAttributes($ids: [ID!]!) {
198
+ reorderReviewAttributes(ids: $ids) { ${ATTRIBUTE_FIELDS} }
199
+ }
200
+ `;
201
+ export const SET_REVIEW_ATTRIBUTE_COLLECTIONS_MUTATION = /* GraphQL */ `
202
+ mutation CatalogSetReviewAttributeCollections($attributeId: ID!, $collectionIds: [ID!]!) {
203
+ setReviewAttributeCollections(attributeId: $attributeId, collectionIds: $collectionIds) {
204
+ ${ATTRIBUTE_FIELDS}
205
+ }
206
+ }
207
+ `;
208
+ function unexpected(field) {
209
+ return new UpstreamError({
210
+ code: 'UNEXPECTED_RESPONSE',
211
+ message: `The engine answered without "${field}".`,
212
+ status: 502,
213
+ service: 'vendure',
214
+ });
215
+ }
216
+ function invalid(code, message) {
217
+ return new ValidationError({ code, message, status: 400, service: 'vendure' });
218
+ }
219
+ /** Attributes in `position` order, the older first on a tie (the engine orders by position only). */
220
+ function byPosition(attributes) {
221
+ return [...attributes].sort((a, b) => a.position - b.position || a.createdAt.localeCompare(b.createdAt));
222
+ }
223
+ /** The selection of a bulk call, checked as the engine checks it, before anything is sent. */
224
+ function bulkVariables(input) {
225
+ const refused = (message) => invalid('BULK_ACTION_INPUT_INVALID', message);
226
+ if (typeof input !== 'object' || input === null) {
227
+ throw refused('A bulk moderation needs ids and an action.');
228
+ }
229
+ if (!REVIEW_STATUSES.includes(input.action)) {
230
+ throw refused(`action must be one of ${REVIEW_STATUSES.join(', ')}.`);
231
+ }
232
+ const ids = input.ids;
233
+ if (!Array.isArray(ids) || !ids.every((id) => typeof id === 'string')) {
234
+ throw refused('ids must be a list of strings.');
235
+ }
236
+ if (ids.length === 0)
237
+ throw refused('Name at least one review.');
238
+ if (ids.length > MAX_BULK_REVIEWS) {
239
+ throw refused(`A bulk moderation takes at most ${String(MAX_BULK_REVIEWS)} reviews.`);
240
+ }
241
+ if (new Set(ids.map((id) => id.toLowerCase())).size !== ids.length) {
242
+ throw refused('Name each review once.');
243
+ }
244
+ return {
245
+ input: {
246
+ ids: [...ids],
247
+ action: input.action,
248
+ ...(input.reason === undefined ? {} : { reason: input.reason }),
249
+ },
250
+ };
251
+ }
252
+ export function createReviewsModule(transport) {
253
+ async function reviewCall(document, field, variables, options, nullable = false) {
254
+ const redaction = redactionWatcher();
255
+ const data = await transport.graphql({ api: 'admin', query: document, variables, onExtensions: redaction.onExtensions }, options);
256
+ if (data === null || typeof data !== 'object' || !Object.hasOwn(data, field)) {
257
+ throw unexpected(field);
258
+ }
259
+ const review = data[field] ?? null;
260
+ if (review === null && !nullable)
261
+ throw unexpected(field);
262
+ return review === null ? null : redaction.mark(review);
263
+ }
264
+ async function reviewsPage(filters, skip, take, call) {
265
+ const redaction = redactionWatcher();
266
+ const data = await transport.graphql({
267
+ api: 'admin',
268
+ query: PRODUCT_REVIEWS_QUERY,
269
+ variables: { options: { ...filters, skip, take } },
270
+ onExtensions: redaction.onExtensions,
271
+ }, call);
272
+ const list = data?.adminProductReviews;
273
+ if (!list)
274
+ throw unexpected('adminProductReviews');
275
+ return pageFromVendureList({ items: list.items.map((review) => redaction.mark(review)), totalItems: list.totalItems }, skip, take, (cursor) => reviewsPage(filters, readOffsetCursor(cursor), take, call));
276
+ }
277
+ return {
278
+ // `async`: a refused option rejects the promise, as an engine refusal does.
279
+ async list(options) {
280
+ const { status, productId, customerId, search, rating, sort, limit, after, ...call } = options ?? {};
281
+ const take = limit ?? 25;
282
+ if (!Number.isInteger(take) || take < 1 || take > MAX_REVIEW_PAGE_SIZE) {
283
+ throw invalid('INVALID_PAGE_SIZE', `limit must be a whole number from 1 to ${String(MAX_REVIEW_PAGE_SIZE)}; got ${String(take)}.`);
284
+ }
285
+ const filters = Object.fromEntries(Object.entries({ status, productId, customerId, search, rating, sort }).filter(([, value]) => value !== undefined && value !== ''));
286
+ return reviewsPage(filters, after === undefined ? 0 : readOffsetCursor(after), take, call);
287
+ },
288
+ get: (id, options) => reviewCall(PRODUCT_REVIEW_QUERY, 'adminProductReview', { id }, options, true),
289
+ async stats(options) {
290
+ const stats = await fieldOf(transport, REVIEW_STATS_QUERY, 'reviewStats', {}, options);
291
+ if (stats === null)
292
+ throw unexpected('reviewStats');
293
+ return stats;
294
+ },
295
+ moderate: (id, input, options) => reviewCall(MODERATE_REVIEW_MUTATION, 'moderateReview', { id, action: input.action, reason: input.reason ?? null }, options),
296
+ async bulkModerate(input, options) {
297
+ const data = await fieldOf(transport, MODERATE_REVIEWS_MUTATION, 'moderateReviews', bulkVariables(input), options);
298
+ if (data === null)
299
+ throw unexpected('moderateReviews');
300
+ return data.items;
301
+ },
302
+ reply: {
303
+ set: (reviewId, reply, options) => reviewCall(SET_MERCHANT_REPLY_MUTATION, 'setMerchantReply', { reviewId, reply }, options),
304
+ remove: (reviewId, options) => reviewCall(REMOVE_MERCHANT_REPLY_MUTATION, 'removeMerchantReply', { reviewId }, options),
305
+ },
306
+ attributes: {
307
+ list: async (options) => byPosition(await fieldOf(transport, REVIEW_ATTRIBUTES_QUERY, 'adminReviewAttributes', {}, options)),
308
+ create: (input, options) => fieldOf(transport, CREATE_REVIEW_ATTRIBUTE_MUTATION, 'createReviewAttribute', { input }, options),
309
+ update: (id, input, options) => fieldOf(transport, UPDATE_REVIEW_ATTRIBUTE_MUTATION, 'updateReviewAttribute', { id, input }, options),
310
+ async delete(id, options) {
311
+ const response = await fieldOf(transport, DELETE_REVIEW_ATTRIBUTE_MUTATION, 'deleteReviewAttribute', { id }, options);
312
+ assertDeleted('REVIEW_ATTRIBUTE_NOT_DELETED', [response]);
313
+ },
314
+ reorder: async (ids, options) => byPosition(await fieldOf(transport, REORDER_REVIEW_ATTRIBUTES_MUTATION, 'reorderReviewAttributes', { ids }, options)),
315
+ setCollections: (attributeId, collectionIds, options) => fieldOf(transport, SET_REVIEW_ATTRIBUTE_COLLECTIONS_MUTATION, 'setReviewAttributeCollections', { attributeId, collectionIds }, options),
316
+ },
317
+ };
318
+ }
@@ -9,7 +9,23 @@ export interface AssetRef {
9
9
  /** A preview URL the asset server scales; absolute or relative to the engine. */
10
10
  preview: string;
11
11
  source: string;
12
+ /** A string on the wire: a newer engine may add a type. */
13
+ type: 'IMAGE' | 'VIDEO' | 'BINARY' | (string & {});
14
+ mimeType: string;
15
+ /**
16
+ * The image's alt text: the store's default-language description
17
+ * (`catalog.assets.descriptions`); null when it has none.
18
+ */
19
+ altText: string | null;
20
+ }
21
+ /** An {@link AssetRef} as the Admin API answers it, before {@link toAssetRef}. */
22
+ export interface WireAssetRef extends Omit<AssetRef, 'altText'> {
23
+ customFields: {
24
+ altText?: string | null;
25
+ } | null;
12
26
  }
27
+ /** Lift the alt text out of the asset's custom fields. */
28
+ export declare function toAssetRef(wire: WireAssetRef): AssetRef;
13
29
  export interface FacetValueRef {
14
30
  id: string;
15
31
  code: string;
@@ -121,7 +137,7 @@ export interface CatalogueOwnership {
121
137
  }
122
138
  /** `catalogueOwnership` on the top-level row of each catalogue read (never on nested refs). */
123
139
  export declare const CATALOGUE_OWNERSHIP_FIELDS = "\n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n";
124
- export declare const ASSET_REF_FIELDS = "\n id\n name\n preview\n source\n";
140
+ export declare const ASSET_REF_FIELDS = "\n id\n name\n preview\n source\n type\n mimeType\n customFields {\n altText\n }\n";
125
141
  export declare const FACET_VALUE_REF_FIELDS = "\n id\n code\n name\n facet {\n id\n code\n name\n }\n";
126
142
  /**
127
143
  * Refuse a datetime with no offset, or one that is not a date at all (spec
@@ -1,4 +1,9 @@
1
1
  import { ConflictError, errorFromErrorResult, isErrorResult, pageFromVendureList, readOffsetCursor, UpstreamError, ValidationError, } from '@sixtyninecommerce/core';
2
+ /** Lift the alt text out of the asset's custom fields. */
3
+ export function toAssetRef(wire) {
4
+ const { customFields, ...rest } = wire;
5
+ return { ...rest, altText: customFields?.altText ?? null };
6
+ }
2
7
  /** `catalogueOwnership` on the top-level row of each catalogue read (never on nested refs). */
3
8
  export const CATALOGUE_OWNERSHIP_FIELDS = /* GraphQL */ `
4
9
  catalogueOwnership {
@@ -12,6 +17,11 @@ export const ASSET_REF_FIELDS = /* GraphQL */ `
12
17
  name
13
18
  preview
14
19
  source
20
+ type
21
+ mimeType
22
+ customFields {
23
+ altText
24
+ }
15
25
  `;
16
26
  export const FACET_VALUE_REF_FIELDS = /* GraphQL */ `
17
27
  id
@@ -0,0 +1,136 @@
1
+ import { type Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ import type { InventoryFlag, VariantStorePrice } from './variants.js';
4
+ /** The most variants one `variants.find` answers. */
5
+ export declare const MAX_VARIANT_FIND_TAKE = 500;
6
+ /** The most products one `variants.find` search reads (`take / perProduct`, rounded up). */
7
+ export declare const MAX_VARIANT_FIND_PRODUCTS = 100;
8
+ /** Which variants `variants.find` answers: exactly one of `ids`, `term` and `skuPrefix`. */
9
+ export interface FindVariantsInput {
10
+ /**
11
+ * Exactly these variants, in the order asked. An id the store does not hold (another store's,
12
+ * a deleted variant, a hidden carrier variant) is left out, never an error; a repeated id is
13
+ * answered once. More than `take` distinct ids is refused.
14
+ */
15
+ ids?: readonly string[];
16
+ /**
17
+ * Full text over the store's product index (a product's name, SKUs, slug, variant names and
18
+ * description): the variants of the best-matching products, best product first.
19
+ */
20
+ term?: string;
21
+ /**
22
+ * The products that have a SKU starting with this text, by name, and of each the variants
23
+ * whose own SKU starts with it. Capitals do not matter.
24
+ */
25
+ skuPrefix?: string;
26
+ /** The most variants the answer holds, 1 to {@link MAX_VARIANT_FIND_TAKE}. */
27
+ take: number;
28
+ /**
29
+ * A search only: the most variants of ONE product, so a product line of thousands cannot fill
30
+ * the answer. From 1 to `take`; a tenth of `take`, rounded up, when omitted. The search reads
31
+ * the best `take / perProduct` products (rounded up, at most
32
+ * {@link MAX_VARIANT_FIND_PRODUCTS}).
33
+ */
34
+ perProduct?: number;
35
+ /**
36
+ * Price the variants in this currency. The store's default when omitted. The engine refuses a
37
+ * currency the store does not sell.
38
+ */
39
+ currencyCode?: string;
40
+ }
41
+ /** A found variant's stock at one of the store's locations. */
42
+ export interface FoundVariantStockLevel {
43
+ stockLocationId: string;
44
+ stockLocationName: string;
45
+ stockOnHand: number;
46
+ /** Units open orders have reserved there. */
47
+ stockAllocated: number;
48
+ }
49
+ /** The product a found variant belongs to. */
50
+ export interface FoundVariantProduct {
51
+ id: string;
52
+ name: string;
53
+ slug: string;
54
+ featuredAsset: {
55
+ id: string;
56
+ preview: string;
57
+ } | null;
58
+ }
59
+ /** A variant as a picker shows it: what it is, what it costs here and what is in stock. */
60
+ export interface FoundVariant {
61
+ id: string;
62
+ name: string;
63
+ sku: string;
64
+ /** A disabled variant is listed too; the engine refuses to add it to an order. */
65
+ enabled: boolean;
66
+ /**
67
+ * The variant's price in the store, in the currency asked for, or null when the store has no
68
+ * price for it there. Never 0 for a missing price.
69
+ */
70
+ storePrice: VariantStorePrice | null;
71
+ trackInventory: InventoryFlag;
72
+ /** The variant's own threshold; the platform's applies instead when the flag below is true. */
73
+ outOfStockThreshold: number;
74
+ useGlobalOutOfStockThreshold: boolean;
75
+ /**
76
+ * Units on hand and reserved across the store's locations, as the engine's stock rule reads
77
+ * them. What an order can still take is `stockOnHand − stockAllocated − threshold`.
78
+ */
79
+ stockOnHand: number;
80
+ stockAllocated: number;
81
+ /** The same per location; the store's own locations only. */
82
+ stockLevels: FoundVariantStockLevel[];
83
+ product: FoundVariantProduct;
84
+ }
85
+ /** One product of a search's answer. */
86
+ export interface FoundVariantsProduct {
87
+ id: string;
88
+ /** How many of the product's variants `items` holds. */
89
+ shown: number;
90
+ /** How many of its variants match: all of them for `term`, the prefix matches for `skuPrefix`. */
91
+ total: number;
92
+ }
93
+ export interface FoundVariants {
94
+ /**
95
+ * `ids`: in the order asked. A search: best product first, each product's variants together,
96
+ * in a stable order (by id), which is not the order a merchant arranged them in.
97
+ */
98
+ items: FoundVariant[];
99
+ /**
100
+ * A search: one entry per product in `items`, in their order. `total` above `shown` means the
101
+ * product has more matching variants than the answer holds. Empty for `ids`.
102
+ */
103
+ products: FoundVariantsProduct[];
104
+ }
105
+ /**
106
+ * What a picker reads of a variant. `storePrice`, never core's `price` / `priceWithTax`: those
107
+ * throw for a variant the store has no price for, and one such variant would empty the answer.
108
+ */
109
+ export declare const FOUND_VARIANT_FIELDS = "\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";
110
+ /** A found variant with its product: the selection of every list that answers variants flat. */
111
+ export declare const FOUND_VARIANT_WITH_PRODUCT_FIELDS = "\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";
112
+ export declare const FIND_VARIANTS_BY_IDS_QUERY = "\n query CatalogFindVariantsByIds($options: ProductVariantListOptions) {\n productVariants(options: $options) {\n items {\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 totalItems\n }\n }\n";
113
+ /**
114
+ * The variants of some products, bounded PER PRODUCT (`variantList`, never `variants`, which the
115
+ * engine answers whole and then cuts at 1,000 without saying so).
116
+ */
117
+ export declare const FIND_VARIANTS_OF_PRODUCTS_QUERY = "\n query CatalogFindVariantsOfProducts(\n $options: ProductListOptions\n $variantOptions: ProductVariantListOptions\n ) {\n products(options: $options) {\n items {\n \n id\n name\n slug\n featuredAsset {\n id\n preview\n }\n\n variantList(options: $variantOptions) {\n items {\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 }\n totalItems\n }\n }\n totalItems\n }\n }\n";
118
+ interface WireFoundVariant extends Omit<FoundVariant, 'stockLevels' | 'product'> {
119
+ stockLevels: Array<{
120
+ stockLocationId: string;
121
+ stockOnHand: number;
122
+ stockAllocated: number;
123
+ stockLocation: {
124
+ id: string;
125
+ name: string;
126
+ } | null;
127
+ }>;
128
+ }
129
+ /** A found variant as a flat list answers it, its product on the row. */
130
+ export type WireFoundVariantWithProduct = WireFoundVariant & {
131
+ product: FoundVariantProduct;
132
+ };
133
+ export declare function toFoundVariant(wire: WireFoundVariantWithProduct): FoundVariant;
134
+ /** `catalog.variants.find`; its contract is on `VariantsModule.find`. */
135
+ export declare function findVariants(transport: Transport, input: FindVariantsInput, options?: Call): Promise<FoundVariants>;
136
+ export {};