@labdigital/commercetools-mock 2.17.1 → 2.18.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 (178) hide show
  1. package/dist/index.cjs +4186 -3974
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +266 -413
  4. package/dist/index.d.ts +266 -413
  5. package/dist/index.js +4186 -3974
  6. package/dist/index.js.map +1 -1
  7. package/package.json +44 -46
  8. package/src/constants.ts +2 -2
  9. package/src/ctMock.test.ts +11 -11
  10. package/src/ctMock.ts +141 -127
  11. package/src/deprecation.ts +8 -0
  12. package/src/exceptions.ts +17 -15
  13. package/src/helpers.ts +32 -32
  14. package/src/index.test.ts +128 -128
  15. package/src/index.ts +3 -3
  16. package/src/lib/expandParser.ts +13 -13
  17. package/src/lib/haversine.test.ts +9 -9
  18. package/src/lib/haversine.ts +11 -11
  19. package/src/lib/masking.ts +11 -11
  20. package/src/lib/parser.ts +2 -2
  21. package/src/lib/password.ts +23 -3
  22. package/src/lib/predicateParser.test.ts +185 -183
  23. package/src/lib/predicateParser.ts +234 -234
  24. package/src/lib/projectionSearchFilter.test.ts +103 -101
  25. package/src/lib/projectionSearchFilter.ts +152 -150
  26. package/src/lib/proxy.ts +5 -5
  27. package/src/oauth/errors.ts +4 -4
  28. package/src/oauth/helpers.ts +6 -6
  29. package/src/oauth/server.test.ts +86 -86
  30. package/src/oauth/server.ts +158 -144
  31. package/src/oauth/store.ts +44 -43
  32. package/src/priceSelector.test.ts +35 -35
  33. package/src/priceSelector.ts +30 -30
  34. package/src/product-projection-search.ts +136 -134
  35. package/src/projectAPI.test.ts +7 -7
  36. package/src/projectAPI.ts +24 -22
  37. package/src/repositories/abstract.ts +168 -116
  38. package/src/repositories/associate-role.ts +90 -77
  39. package/src/repositories/attribute-group.ts +51 -40
  40. package/src/repositories/business-unit.ts +168 -148
  41. package/src/repositories/cart/actions.ts +489 -0
  42. package/src/repositories/cart/helpers.ts +30 -0
  43. package/src/repositories/cart/index.ts +180 -0
  44. package/src/repositories/cart-discount/actions.ts +148 -0
  45. package/src/repositories/cart-discount/index.ts +86 -0
  46. package/src/repositories/category/actions.ts +231 -0
  47. package/src/repositories/category/index.ts +52 -0
  48. package/src/repositories/channel.ts +88 -90
  49. package/src/repositories/custom-object.ts +46 -45
  50. package/src/repositories/customer/actions.ts +165 -0
  51. package/src/repositories/customer/index.ts +79 -0
  52. package/src/repositories/customer-group.ts +66 -55
  53. package/src/repositories/discount-code/actions.ts +149 -0
  54. package/src/repositories/discount-code/index.ts +50 -0
  55. package/src/repositories/errors.ts +10 -10
  56. package/src/repositories/extension.ts +64 -62
  57. package/src/repositories/helpers.ts +117 -118
  58. package/src/repositories/index.ts +80 -79
  59. package/src/repositories/inventory-entry/actions.ts +84 -0
  60. package/src/repositories/inventory-entry/index.ts +44 -0
  61. package/src/repositories/my-customer.ts +114 -0
  62. package/src/repositories/my-order.ts +8 -8
  63. package/src/repositories/order/actions.ts +281 -0
  64. package/src/repositories/{order.test.ts → order/index.test.ts} +77 -77
  65. package/src/repositories/order/index.ts +260 -0
  66. package/src/repositories/order-edit.ts +10 -23
  67. package/src/repositories/payment/actions.ts +305 -0
  68. package/src/repositories/payment/helpers.ts +17 -0
  69. package/src/repositories/payment/index.ts +56 -0
  70. package/src/repositories/product/actions.ts +943 -0
  71. package/src/repositories/product/helpers.ts +98 -0
  72. package/src/repositories/product/index.ts +130 -0
  73. package/src/repositories/product-discount.ts +127 -117
  74. package/src/repositories/product-projection.ts +56 -62
  75. package/src/repositories/product-selection.ts +31 -28
  76. package/src/repositories/product-type.ts +136 -134
  77. package/src/repositories/project.ts +133 -118
  78. package/src/repositories/quote-request.ts +7 -19
  79. package/src/repositories/quote.ts +7 -22
  80. package/src/repositories/review.ts +13 -26
  81. package/src/repositories/shipping-method/actions.ts +198 -0
  82. package/src/repositories/shipping-method/helpers.ts +10 -0
  83. package/src/repositories/shipping-method/index.ts +138 -0
  84. package/src/repositories/shopping-list/actions.ts +295 -0
  85. package/src/repositories/shopping-list/index.ts +122 -0
  86. package/src/repositories/staged-quote.ts +7 -20
  87. package/src/repositories/standalone-price.ts +57 -44
  88. package/src/repositories/state.ts +113 -68
  89. package/src/repositories/store.ts +106 -94
  90. package/src/repositories/subscription.ts +46 -22
  91. package/src/repositories/tax-category/actions.ts +94 -0
  92. package/src/repositories/tax-category/helpers.ts +8 -0
  93. package/src/repositories/tax-category/index.ts +25 -0
  94. package/src/repositories/type/actions.ts +162 -0
  95. package/src/repositories/type/index.ts +24 -0
  96. package/src/repositories/zone.ts +62 -58
  97. package/src/server.ts +9 -9
  98. package/src/services/abstract.ts +75 -72
  99. package/src/services/associate-roles.test.ts +27 -27
  100. package/src/services/associate-roles.ts +7 -7
  101. package/src/services/attribute-group.ts +7 -7
  102. package/src/services/business-units.test.ts +28 -28
  103. package/src/services/business-units.ts +7 -7
  104. package/src/services/cart-discount.test.ts +199 -199
  105. package/src/services/cart-discount.ts +7 -7
  106. package/src/services/cart.test.ts +261 -261
  107. package/src/services/cart.ts +22 -21
  108. package/src/services/category.test.ts +121 -121
  109. package/src/services/category.ts +7 -7
  110. package/src/services/channel.ts +7 -7
  111. package/src/services/custom-object.test.ts +130 -130
  112. package/src/services/custom-object.ts +34 -31
  113. package/src/services/customer-group.ts +7 -7
  114. package/src/services/customer.test.ts +205 -205
  115. package/src/services/customer.ts +23 -36
  116. package/src/services/discount-code.ts +7 -7
  117. package/src/services/extension.ts +7 -7
  118. package/src/services/index.ts +85 -81
  119. package/src/services/inventory-entry.test.ts +106 -106
  120. package/src/services/inventory-entry.ts +7 -7
  121. package/src/services/my-cart.test.ts +56 -56
  122. package/src/services/my-cart.ts +20 -20
  123. package/src/services/my-customer.test.ts +155 -104
  124. package/src/services/my-customer.ts +61 -75
  125. package/src/services/my-order.ts +16 -16
  126. package/src/services/my-payment.test.ts +40 -40
  127. package/src/services/my-payment.ts +7 -7
  128. package/src/services/my-shopping-list.ts +7 -7
  129. package/src/services/order.test.ts +243 -243
  130. package/src/services/order.ts +23 -18
  131. package/src/services/payment.test.ts +40 -40
  132. package/src/services/payment.ts +7 -7
  133. package/src/services/product-discount.ts +7 -7
  134. package/src/services/product-projection.test.ts +190 -190
  135. package/src/services/product-projection.ts +34 -32
  136. package/src/services/product-selection.test.ts +19 -19
  137. package/src/services/product-selection.ts +7 -7
  138. package/src/services/product-type.test.ts +38 -38
  139. package/src/services/product-type.ts +7 -7
  140. package/src/services/product.test.ts +658 -656
  141. package/src/services/product.ts +7 -7
  142. package/src/services/project.test.ts +24 -24
  143. package/src/services/project.ts +17 -17
  144. package/src/services/reviews.ts +7 -7
  145. package/src/services/shipping-method.test.ts +78 -78
  146. package/src/services/shipping-method.ts +16 -16
  147. package/src/services/shopping-list.test.ts +170 -170
  148. package/src/services/shopping-list.ts +7 -7
  149. package/src/services/standalone-price.test.ts +112 -112
  150. package/src/services/standalone-price.ts +7 -7
  151. package/src/services/state.test.ts +30 -30
  152. package/src/services/state.ts +7 -7
  153. package/src/services/store.test.ts +40 -40
  154. package/src/services/store.ts +7 -7
  155. package/src/services/subscription.ts +7 -7
  156. package/src/services/tax-category.test.ts +43 -43
  157. package/src/services/tax-category.ts +7 -7
  158. package/src/services/type.ts +7 -7
  159. package/src/services/zone.ts +7 -7
  160. package/src/shippingCalculator.test.ts +43 -43
  161. package/src/shippingCalculator.ts +23 -23
  162. package/src/storage/abstract.ts +36 -34
  163. package/src/storage/in-memory.ts +237 -233
  164. package/src/storage/index.ts +2 -2
  165. package/src/types.ts +91 -91
  166. package/src/repositories/cart-discount.ts +0 -219
  167. package/src/repositories/cart.ts +0 -659
  168. package/src/repositories/category.ts +0 -256
  169. package/src/repositories/customer.ts +0 -228
  170. package/src/repositories/discount-code.ts +0 -181
  171. package/src/repositories/inventory-entry.ts +0 -109
  172. package/src/repositories/order.ts +0 -514
  173. package/src/repositories/payment.ts +0 -342
  174. package/src/repositories/product.ts +0 -1106
  175. package/src/repositories/shipping-method.ts +0 -312
  176. package/src/repositories/shopping-list.ts +0 -392
  177. package/src/repositories/tax-category.ts +0 -111
  178. package/src/repositories/type.ts +0 -172
@@ -0,0 +1,943 @@
1
+ import type {
2
+ CategoryReference,
3
+ InvalidJsonInputError,
4
+ InvalidOperationError,
5
+ Price,
6
+ Product,
7
+ ProductAddExternalImageAction,
8
+ ProductAddPriceAction,
9
+ ProductAddToCategoryAction,
10
+ ProductAddVariantAction,
11
+ ProductChangeMasterVariantAction,
12
+ ProductChangeNameAction,
13
+ ProductChangePriceAction,
14
+ ProductChangeSlugAction,
15
+ ProductData,
16
+ ProductMoveImageToPositionAction,
17
+ ProductPublishAction,
18
+ ProductRemoveFromCategoryAction,
19
+ ProductRemoveImageAction,
20
+ ProductRemovePriceAction,
21
+ ProductRemoveVariantAction,
22
+ ProductSetAttributeAction,
23
+ ProductSetAttributeInAllVariantsAction,
24
+ ProductSetDescriptionAction,
25
+ ProductSetKeyAction,
26
+ ProductSetMetaDescriptionAction,
27
+ ProductSetMetaKeywordsAction,
28
+ ProductSetMetaTitleAction,
29
+ ProductSetTaxCategoryAction,
30
+ ProductTransitionStateAction,
31
+ ProductUpdateAction,
32
+ ProductVariantDraft,
33
+ StateReference,
34
+ TaxCategoryReference,
35
+ } from "@commercetools/platform-sdk";
36
+ import { CommercetoolsError } from "~src/exceptions";
37
+ import type { Writable } from "~src/types";
38
+ import { AbstractUpdateHandler, RepositoryContext } from "../abstract";
39
+ import { getReferenceFromResourceIdentifier } from "../helpers";
40
+ import {
41
+ checkForStagedChanges,
42
+ getVariant,
43
+ priceFromDraft,
44
+ variantFromDraft,
45
+ } from "./helpers";
46
+
47
+ type ProductUpdateHandlerMethod<T> = (
48
+ context: RepositoryContext,
49
+ resource: Writable<Product>,
50
+ action: T,
51
+ ) => void;
52
+
53
+ type ProductUpdateActions = Partial<{
54
+ [P in ProductUpdateAction as P["action"]]: ProductUpdateHandlerMethod<P>;
55
+ }>;
56
+
57
+ export class ProductUpdateHandler
58
+ extends AbstractUpdateHandler
59
+ implements ProductUpdateActions
60
+ {
61
+ addExternalImage(
62
+ context: RepositoryContext,
63
+ resource: Writable<Product>,
64
+ { variantId, sku, image, staged }: ProductAddExternalImageAction,
65
+ ) {
66
+ const addImg = (data: Writable<ProductData>) => {
67
+ const { variant, isMasterVariant, variantIndex } = getVariant(
68
+ data,
69
+ variantId,
70
+ sku,
71
+ );
72
+ if (!variant) {
73
+ throw new Error(
74
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`,
75
+ );
76
+ }
77
+
78
+ if (!variant.images) {
79
+ variant.images = [];
80
+ } else {
81
+ const existingImage = variant.images.find((x) => x.url === image.url);
82
+ if (existingImage) {
83
+ throw new Error(
84
+ `Cannot add image '${image.url}' because product '${resource.id}' already has that image.`,
85
+ );
86
+ }
87
+ }
88
+
89
+ // Add image
90
+ variant.images.push(image);
91
+
92
+ if (isMasterVariant) {
93
+ data.masterVariant = variant;
94
+ } else {
95
+ data.variants[variantIndex] = variant;
96
+ }
97
+ };
98
+
99
+ // If true, only the staged Attribute is set. If false, both current and
100
+ // staged Attribute is set. Default is true
101
+ const onlyStaged = staged !== undefined ? staged : true;
102
+
103
+ // Write the attribute to the staged data
104
+ addImg(resource.masterData.staged);
105
+
106
+ // Also write to published data is isStaged = false
107
+ // if isStaged is false we set the attribute on both the staged and
108
+ // published data.
109
+ if (!onlyStaged) {
110
+ addImg(resource.masterData.current);
111
+ }
112
+ checkForStagedChanges(resource);
113
+
114
+ return resource;
115
+ }
116
+
117
+ addPrice(
118
+ context: RepositoryContext,
119
+ resource: Writable<Product>,
120
+ { variantId, sku, price, staged }: ProductAddPriceAction,
121
+ ) {
122
+ const addVariantPrice = (
123
+ data: Writable<ProductData>,
124
+ priceToAdd: Price,
125
+ ) => {
126
+ const { variant, isMasterVariant, variantIndex } = getVariant(
127
+ data,
128
+ variantId,
129
+ sku,
130
+ );
131
+ if (!variant) {
132
+ throw new Error(
133
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`,
134
+ );
135
+ }
136
+
137
+ if (variant.prices === undefined) {
138
+ variant.prices = [priceToAdd];
139
+ } else {
140
+ variant.prices.push(priceToAdd);
141
+ }
142
+
143
+ if (isMasterVariant) {
144
+ data.masterVariant = variant;
145
+ } else {
146
+ data.variants[variantIndex] = variant;
147
+ }
148
+ };
149
+
150
+ // Pre-creating the price object ensures consistency between staged and current versions
151
+ const priceToAdd = priceFromDraft(context, this._storage, price);
152
+
153
+ // If true, only the staged Attribute is set. If false, both current and
154
+ // staged Attribute is set. Default is true
155
+ const onlyStaged = staged !== undefined ? staged : true;
156
+
157
+ // Write the attribute to the staged data
158
+ addVariantPrice(resource.masterData.staged, priceToAdd);
159
+
160
+ // Also write to published data is isStaged = false
161
+ // if isStaged is false we set the attribute on both the staged and
162
+ // published data.
163
+ if (!onlyStaged) {
164
+ addVariantPrice(resource.masterData.current, priceToAdd);
165
+ }
166
+ checkForStagedChanges(resource);
167
+
168
+ return resource;
169
+ }
170
+
171
+ addToCategory(
172
+ context: RepositoryContext,
173
+ resource: Writable<Product>,
174
+ { category, staged, orderHint }: ProductAddToCategoryAction,
175
+ ) {
176
+ const addCategory = (data: Writable<ProductData>) => {
177
+ if (category) {
178
+ data.categories.push(
179
+ getReferenceFromResourceIdentifier<CategoryReference>(
180
+ category,
181
+ context.projectKey,
182
+ this._storage,
183
+ ),
184
+ );
185
+ } else {
186
+ throw new CommercetoolsError<InvalidJsonInputError>(
187
+ {
188
+ code: "InvalidJsonInput",
189
+ message: "Request body does not contain valid JSON.",
190
+ detailedErrorMessage: "actions -> category: Missing required value",
191
+ },
192
+ 400,
193
+ );
194
+ }
195
+ };
196
+
197
+ const onlyStaged = staged !== undefined ? staged : true;
198
+
199
+ addCategory(resource.masterData.staged);
200
+
201
+ if (!onlyStaged) {
202
+ addCategory(resource.masterData.current);
203
+ }
204
+ checkForStagedChanges(resource);
205
+
206
+ return resource;
207
+ }
208
+
209
+ addVariant(
210
+ context: RepositoryContext,
211
+ resource: Writable<Product>,
212
+ {
213
+ sku,
214
+ key,
215
+ prices,
216
+ images,
217
+ attributes,
218
+ staged,
219
+ assets,
220
+ }: ProductAddVariantAction,
221
+ ) {
222
+ const variantDraft: ProductVariantDraft = {
223
+ sku: sku,
224
+ key: key,
225
+ prices: prices,
226
+ images: images,
227
+ attributes: attributes,
228
+ assets: assets,
229
+ };
230
+
231
+ const dataStaged = resource.masterData.staged;
232
+ const allVariants = [
233
+ dataStaged.masterVariant,
234
+ ...(dataStaged.variants ?? []),
235
+ ];
236
+ const maxId = allVariants.reduce(
237
+ (max, element) => (element.id > max ? element.id : max),
238
+ 0,
239
+ );
240
+ const variant = variantFromDraft(
241
+ context,
242
+ this._storage,
243
+ maxId + 1,
244
+ variantDraft,
245
+ );
246
+ dataStaged.variants.push(variant);
247
+
248
+ const onlyStaged = staged !== undefined ? staged : true;
249
+
250
+ if (!onlyStaged) {
251
+ resource.masterData.current.variants.push(variant);
252
+ }
253
+ checkForStagedChanges(resource);
254
+
255
+ return resource;
256
+ }
257
+
258
+ changeMasterVariant(
259
+ context: RepositoryContext,
260
+ resource: Writable<Product>,
261
+ { variantId, sku, staged }: ProductChangeMasterVariantAction,
262
+ ) {
263
+ const setMaster = (data: Writable<ProductData>) => {
264
+ const { variant, isMasterVariant, variantIndex } = getVariant(
265
+ data,
266
+ variantId,
267
+ sku,
268
+ );
269
+ if (!variant) {
270
+ throw new Error(
271
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`,
272
+ );
273
+ }
274
+
275
+ if (!isMasterVariant) {
276
+ // Save previous master variant
277
+ const masterVariantPrev = data.masterVariant;
278
+ data.masterVariant = variant;
279
+ // Remove new master from variants
280
+ data.variants.splice(variantIndex, 1);
281
+ // Add previous master to variants
282
+ data.variants.push(masterVariantPrev);
283
+ }
284
+ };
285
+
286
+ const onlyStaged = staged !== undefined ? staged : true;
287
+
288
+ setMaster(resource.masterData.staged);
289
+
290
+ if (!onlyStaged) {
291
+ setMaster(resource.masterData.current);
292
+ }
293
+ checkForStagedChanges(resource);
294
+
295
+ return resource;
296
+ }
297
+
298
+ changeName(
299
+ context: RepositoryContext,
300
+ resource: Writable<Product>,
301
+ { name, staged }: ProductChangeNameAction,
302
+ ) {
303
+ const onlyStaged = staged !== undefined ? staged : true;
304
+ resource.masterData.staged.name = name;
305
+ if (!onlyStaged) {
306
+ resource.masterData.current.name = name;
307
+ }
308
+ checkForStagedChanges(resource);
309
+ return resource;
310
+ }
311
+
312
+ changePrice(
313
+ context: RepositoryContext,
314
+ resource: Writable<Product>,
315
+ { priceId, price, staged }: ProductChangePriceAction,
316
+ ) {
317
+ const changeVariantPrice = (data: Writable<ProductData>) => {
318
+ const allVariants = [data.masterVariant, ...(data.variants ?? [])];
319
+ const priceVariant = allVariants.find((variant) =>
320
+ variant.prices?.some((x) => x.id === priceId),
321
+ );
322
+ if (!priceVariant) {
323
+ throw new Error(
324
+ `Price with id ${priceId} not found on product ${resource.id}`,
325
+ );
326
+ }
327
+
328
+ const { variant, isMasterVariant, variantIndex } = getVariant(
329
+ data,
330
+ priceVariant.id,
331
+ priceVariant.sku,
332
+ );
333
+ if (!variant) {
334
+ throw new Error(
335
+ `Variant with id ${priceVariant.id} or sku ${priceVariant.sku} not found on product ${resource.id}`,
336
+ );
337
+ }
338
+
339
+ variant.prices = variant.prices?.map((x) => {
340
+ if (x.id === priceId) {
341
+ return { ...x, ...price } as Price;
342
+ }
343
+ return x;
344
+ });
345
+
346
+ if (isMasterVariant) {
347
+ data.masterVariant = variant;
348
+ } else {
349
+ data.variants[variantIndex] = variant;
350
+ }
351
+ };
352
+
353
+ // If true, only the staged Attribute is set. If false, both current and
354
+ // staged Attribute is set. Default is true
355
+ const onlyStaged = staged !== undefined ? staged : true;
356
+
357
+ // Write the attribute to the staged data
358
+ changeVariantPrice(resource.masterData.staged);
359
+
360
+ // Also write to published data is isStaged = false
361
+ // if isStaged is false we set the attribute on both the staged and
362
+ // published data.
363
+ if (!onlyStaged) {
364
+ changeVariantPrice(resource.masterData.current);
365
+ }
366
+ checkForStagedChanges(resource);
367
+
368
+ return resource;
369
+ }
370
+
371
+ changeSlug(
372
+ context: RepositoryContext,
373
+ resource: Writable<Product>,
374
+ { slug, staged }: ProductChangeSlugAction,
375
+ ) {
376
+ const onlyStaged = staged !== undefined ? staged : true;
377
+ resource.masterData.staged.slug = slug;
378
+ if (!onlyStaged) {
379
+ resource.masterData.current.slug = slug;
380
+ }
381
+ checkForStagedChanges(resource);
382
+ return resource;
383
+ }
384
+
385
+ moveImageToPosition(
386
+ context: RepositoryContext,
387
+ resource: Writable<Product>,
388
+ {
389
+ variantId,
390
+ sku,
391
+ imageUrl,
392
+ position,
393
+ staged,
394
+ }: ProductMoveImageToPositionAction,
395
+ ) {
396
+ const moveImg = (data: Writable<ProductData>) => {
397
+ const { variant, isMasterVariant, variantIndex } = getVariant(
398
+ data,
399
+ variantId,
400
+ sku,
401
+ );
402
+ if (!variant) {
403
+ throw new Error(
404
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`,
405
+ );
406
+ }
407
+
408
+ const variantImages = variant.images ?? [];
409
+ const existingImage = variantImages.find((x) => x.url === imageUrl);
410
+ if (!existingImage) {
411
+ throw new Error(
412
+ `Cannot move image '${imageUrl}' because product '${resource.id}' does not have that image.`,
413
+ );
414
+ }
415
+
416
+ if (position >= variantImages.length) {
417
+ throw new Error(
418
+ `Invalid position given. Position in images where the image should be moved. Must be between 0 and the total number of images minus 1.`,
419
+ );
420
+ }
421
+
422
+ // Remove image
423
+ variant.images = variantImages.filter((image) => image.url !== imageUrl);
424
+
425
+ // Re-add image to the correct position
426
+ variant.images.splice(position, 0, existingImage);
427
+
428
+ if (isMasterVariant) {
429
+ data.masterVariant = variant;
430
+ } else {
431
+ data.variants[variantIndex] = variant;
432
+ }
433
+ };
434
+
435
+ // If true, only the staged Attribute is set. If false, both current and
436
+ // staged Attribute is set. Default is true
437
+ const onlyStaged = staged !== undefined ? staged : true;
438
+
439
+ // Write the attribute to the staged data
440
+ moveImg(resource.masterData.staged);
441
+
442
+ // Also write to published data is isStaged = false
443
+ // if isStaged is false we set the attribute on both the staged and
444
+ // published data.
445
+ if (!onlyStaged) {
446
+ moveImg(resource.masterData.current);
447
+ }
448
+ checkForStagedChanges(resource);
449
+
450
+ return resource;
451
+ }
452
+
453
+ publish(
454
+ context: RepositoryContext,
455
+ resource: Writable<Product>,
456
+ { scope }: ProductPublishAction,
457
+ ) {
458
+ resource.masterData.current = resource.masterData.staged;
459
+ resource.masterData.published = true;
460
+ checkForStagedChanges(resource);
461
+ }
462
+
463
+ removeFromCategory(
464
+ context: RepositoryContext,
465
+ resource: Writable<Product>,
466
+ { category, staged }: ProductRemoveFromCategoryAction,
467
+ ) {
468
+ const removeCategory = (data: Writable<ProductData>) => {
469
+ if (category) {
470
+ const resolvedCategory =
471
+ getReferenceFromResourceIdentifier<CategoryReference>(
472
+ category,
473
+ context.projectKey,
474
+ this._storage,
475
+ );
476
+
477
+ const foundCategory = data.categories.find(
478
+ (productCategory: CategoryReference) => {
479
+ if (productCategory.id == resolvedCategory.id) {
480
+ return productCategory;
481
+ }
482
+ return false;
483
+ },
484
+ );
485
+
486
+ if (!foundCategory) {
487
+ throw new CommercetoolsError<InvalidOperationError>(
488
+ {
489
+ code: "InvalidOperation",
490
+ message:
491
+ `Cannot remove from category '${resolvedCategory.id}' because product ` +
492
+ `'${resource.masterData.current.name}' is not in that category.`,
493
+ },
494
+ 400,
495
+ );
496
+ }
497
+
498
+ data.categories = data.categories.filter(
499
+ (productCategory: CategoryReference) => {
500
+ if (productCategory.id == resolvedCategory.id) {
501
+ return false;
502
+ }
503
+ return true;
504
+ },
505
+ );
506
+ } else {
507
+ throw new CommercetoolsError<InvalidJsonInputError>(
508
+ {
509
+ code: "InvalidJsonInput",
510
+ message: "Request body does not contain valid JSON.",
511
+ detailedErrorMessage: "actions -> category: Missing required value",
512
+ },
513
+ 400,
514
+ );
515
+ }
516
+ };
517
+
518
+ const onlyStaged = staged !== undefined ? staged : true;
519
+ removeCategory(resource.masterData.staged);
520
+
521
+ if (!onlyStaged) {
522
+ removeCategory(resource.masterData.current);
523
+ }
524
+ checkForStagedChanges(resource);
525
+
526
+ return resource;
527
+ }
528
+
529
+ removeImage(
530
+ context: RepositoryContext,
531
+ resource: Writable<Product>,
532
+ { variantId, sku, imageUrl, staged }: ProductRemoveImageAction,
533
+ ) {
534
+ const removeImg = (data: Writable<ProductData>) => {
535
+ const { variant, isMasterVariant, variantIndex } = getVariant(
536
+ data,
537
+ variantId,
538
+ sku,
539
+ );
540
+ if (!variant) {
541
+ throw new Error(
542
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`,
543
+ );
544
+ }
545
+
546
+ const variantImages = variant.images ?? [];
547
+ const existingImage = variantImages.find((x) => x.url === imageUrl);
548
+ if (!existingImage) {
549
+ throw new Error(
550
+ `Cannot remove image '${imageUrl}' because product '${resource.id}' does not have that image.`,
551
+ );
552
+ }
553
+
554
+ // Remove image
555
+ variant.images = variantImages.filter((image) => image.url !== imageUrl);
556
+
557
+ if (isMasterVariant) {
558
+ data.masterVariant = variant;
559
+ } else {
560
+ data.variants[variantIndex] = variant;
561
+ }
562
+ };
563
+
564
+ // If true, only the staged Attribute is set. If false, both current and
565
+ // staged Attribute is set. Default is true
566
+ const onlyStaged = staged !== undefined ? staged : true;
567
+
568
+ // Write the attribute to the staged data
569
+ removeImg(resource.masterData.staged);
570
+
571
+ // Also write to published data is isStaged = false
572
+ // if isStaged is false we set the attribute on both the staged and
573
+ // published data.
574
+ if (!onlyStaged) {
575
+ removeImg(resource.masterData.current);
576
+ }
577
+ checkForStagedChanges(resource);
578
+
579
+ return resource;
580
+ }
581
+
582
+ removePrice(
583
+ context: RepositoryContext,
584
+ resource: Writable<Product>,
585
+ { priceId, staged }: ProductRemovePriceAction,
586
+ ) {
587
+ const removeVariantPrice = (data: Writable<ProductData>) => {
588
+ const allVariants = [data.masterVariant, ...(data.variants ?? [])];
589
+ const priceVariant = allVariants.find((variant) =>
590
+ variant.prices?.some((x) => x.id === priceId),
591
+ );
592
+ if (!priceVariant) {
593
+ throw new Error(
594
+ `Price with id ${priceId} not found on product ${resource.id}`,
595
+ );
596
+ }
597
+
598
+ const { variant, isMasterVariant, variantIndex } = getVariant(
599
+ data,
600
+ priceVariant.id,
601
+ priceVariant.sku,
602
+ );
603
+ if (!variant) {
604
+ throw new Error(
605
+ `Variant with id ${priceVariant.id} or sku ${priceVariant.sku} not found on product ${resource.id}`,
606
+ );
607
+ }
608
+
609
+ variant.prices = variant.prices?.filter((x) => x.id !== priceId);
610
+
611
+ if (isMasterVariant) {
612
+ data.masterVariant = variant;
613
+ } else {
614
+ data.variants[variantIndex] = variant;
615
+ }
616
+ };
617
+
618
+ // If true, only the staged Attribute is set. If false, both current and
619
+ // staged Attribute is set. Default is true
620
+ const onlyStaged = staged !== undefined ? staged : true;
621
+
622
+ // Write the attribute to the staged data
623
+ removeVariantPrice(resource.masterData.staged);
624
+
625
+ // Also write to published data is isStaged = false
626
+ // if isStaged is false we set the attribute on both the staged and
627
+ // published data.
628
+ if (!onlyStaged) {
629
+ removeVariantPrice(resource.masterData.current);
630
+ }
631
+ checkForStagedChanges(resource);
632
+
633
+ return resource;
634
+ }
635
+
636
+ removeVariant(
637
+ context: RepositoryContext,
638
+ resource: Writable<Product>,
639
+ { id, sku, staged }: ProductRemoveVariantAction,
640
+ ) {
641
+ const removeVariant = (data: Writable<ProductData>) => {
642
+ const { variant, isMasterVariant, variantIndex } = getVariant(
643
+ data,
644
+ id,
645
+ sku,
646
+ );
647
+ if (!variant) {
648
+ throw new Error(
649
+ `Variant with id ${id} or sku ${sku} not found on product ${resource.id}`,
650
+ );
651
+ }
652
+ if (isMasterVariant) {
653
+ throw new Error(
654
+ `Can not remove the variant [ID:${id}] for [Product:${resource.id}] since it's the master variant`,
655
+ );
656
+ }
657
+
658
+ data.variants.splice(variantIndex, 1);
659
+ };
660
+
661
+ const onlyStaged = staged !== undefined ? staged : true;
662
+
663
+ removeVariant(resource.masterData.staged);
664
+
665
+ if (!onlyStaged) {
666
+ removeVariant(resource.masterData.current);
667
+ }
668
+ checkForStagedChanges(resource);
669
+
670
+ return resource;
671
+ }
672
+
673
+ setAttribute(
674
+ context: RepositoryContext,
675
+ resource: Writable<Product>,
676
+ { variantId, sku, name, value, staged }: ProductSetAttributeAction,
677
+ ) {
678
+ const setAttr = (data: Writable<ProductData>) => {
679
+ const { variant, isMasterVariant, variantIndex } = getVariant(
680
+ data,
681
+ variantId,
682
+ sku,
683
+ );
684
+ if (!variant) {
685
+ throw new Error(
686
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`,
687
+ );
688
+ }
689
+
690
+ if (!variant.attributes) {
691
+ variant.attributes = [];
692
+ }
693
+
694
+ const existingAttr = variant.attributes.find(
695
+ (attr) => attr.name === name,
696
+ );
697
+ if (existingAttr) {
698
+ existingAttr.value = value;
699
+ } else {
700
+ variant.attributes.push({
701
+ name,
702
+ value,
703
+ });
704
+ }
705
+ if (isMasterVariant) {
706
+ data.masterVariant = variant;
707
+ } else {
708
+ data.variants[variantIndex] = variant;
709
+ }
710
+ };
711
+
712
+ // If true, only the staged Attribute is set. If false, both current and
713
+ // staged Attribute is set. Default is true
714
+ const onlyStaged = staged !== undefined ? staged : true;
715
+
716
+ // Write the attribute to the staged data
717
+ setAttr(resource.masterData.staged);
718
+
719
+ // Also write to published data is isStaged = false
720
+ // if isStaged is false we set the attribute on both the staged and
721
+ // published data.
722
+ if (!onlyStaged) {
723
+ setAttr(resource.masterData.current);
724
+ }
725
+ checkForStagedChanges(resource);
726
+
727
+ return resource;
728
+ }
729
+
730
+ setAttributeInAllVariants(
731
+ context: RepositoryContext,
732
+ resource: Writable<Product>,
733
+ { name, value, staged }: ProductSetAttributeInAllVariantsAction,
734
+ ) {
735
+ const setAttrInAllVariants = (data: Writable<ProductData>) => {
736
+ if (!data.masterVariant.attributes) {
737
+ data.masterVariant.attributes = [];
738
+ }
739
+
740
+ const existingAttr = data.masterVariant.attributes?.find(
741
+ (attr) => attr.name === name,
742
+ );
743
+
744
+ if (existingAttr) {
745
+ existingAttr.value = value;
746
+ } else {
747
+ data.masterVariant.attributes.push({
748
+ name,
749
+ value,
750
+ });
751
+ }
752
+
753
+ data.variants.forEach((variant) => {
754
+ if (!variant.attributes) {
755
+ variant.attributes = [];
756
+ }
757
+
758
+ const existingAttr = variant.attributes.find(
759
+ (attr) => attr.name === name,
760
+ );
761
+ if (existingAttr) {
762
+ existingAttr.value = value;
763
+ } else {
764
+ variant.attributes.push({
765
+ name,
766
+ value,
767
+ });
768
+ }
769
+ });
770
+ };
771
+
772
+ // If true, only the staged Attribute is set. If false, both current and
773
+ // staged Attribute is set. Default is true
774
+ const onlyStaged = staged !== undefined ? staged : true;
775
+
776
+ // Write the attribute to the staged data
777
+ setAttrInAllVariants(resource.masterData.staged);
778
+
779
+ // Also write to published data is isStaged = false
780
+ // if isStaged is false we set the attribute on both the staged and
781
+ // published data.
782
+ if (!onlyStaged) {
783
+ setAttrInAllVariants(resource.masterData.current);
784
+ }
785
+ checkForStagedChanges(resource);
786
+
787
+ return resource;
788
+ }
789
+
790
+ setDescription(
791
+ context: RepositoryContext,
792
+ resource: Writable<Product>,
793
+ { description, staged }: ProductSetDescriptionAction,
794
+ ) {
795
+ const onlyStaged = staged !== undefined ? staged : true;
796
+
797
+ resource.masterData.staged.description = description;
798
+ if (!onlyStaged) {
799
+ resource.masterData.current.description = description;
800
+ }
801
+ checkForStagedChanges(resource);
802
+ return resource;
803
+ }
804
+
805
+ setKey(
806
+ context: RepositoryContext,
807
+ resource: Writable<Product>,
808
+ { key }: ProductSetKeyAction,
809
+ ) {
810
+ resource.key = key;
811
+ return resource;
812
+ }
813
+
814
+ setMetaDescription(
815
+ context: RepositoryContext,
816
+ resource: Writable<Product>,
817
+ { metaDescription, staged }: ProductSetMetaDescriptionAction,
818
+ ) {
819
+ const onlyStaged = staged !== undefined ? staged : true;
820
+ resource.masterData.staged.metaDescription = metaDescription;
821
+ if (!onlyStaged) {
822
+ resource.masterData.current.metaDescription = metaDescription;
823
+ }
824
+ checkForStagedChanges(resource);
825
+ return resource;
826
+ }
827
+
828
+ setMetaKeywords(
829
+ context: RepositoryContext,
830
+ resource: Writable<Product>,
831
+ { metaKeywords, staged }: ProductSetMetaKeywordsAction,
832
+ ) {
833
+ const onlyStaged = staged !== undefined ? staged : true;
834
+ resource.masterData.staged.metaKeywords = metaKeywords;
835
+ if (!onlyStaged) {
836
+ resource.masterData.current.metaKeywords = metaKeywords;
837
+ }
838
+ checkForStagedChanges(resource);
839
+ return resource;
840
+ }
841
+
842
+ setMetaTitle(
843
+ context: RepositoryContext,
844
+ resource: Writable<Product>,
845
+ { metaTitle, staged }: ProductSetMetaTitleAction,
846
+ ) {
847
+ const onlyStaged = staged !== undefined ? staged : true;
848
+ resource.masterData.staged.metaTitle = metaTitle;
849
+ if (!onlyStaged) {
850
+ resource.masterData.current.metaTitle = metaTitle;
851
+ }
852
+ checkForStagedChanges(resource);
853
+ return resource;
854
+ }
855
+
856
+ setTaxCategory(
857
+ context: RepositoryContext,
858
+ resource: Writable<Product>,
859
+ { taxCategory }: ProductSetTaxCategoryAction,
860
+ ) {
861
+ let taxCategoryReference: TaxCategoryReference | undefined = undefined;
862
+ if (taxCategory) {
863
+ taxCategoryReference =
864
+ getReferenceFromResourceIdentifier<TaxCategoryReference>(
865
+ taxCategory,
866
+ context.projectKey,
867
+ this._storage,
868
+ );
869
+ } else {
870
+ throw new CommercetoolsError<InvalidJsonInputError>(
871
+ {
872
+ code: "InvalidJsonInput",
873
+ message: "Request body does not contain valid JSON.",
874
+ detailedErrorMessage:
875
+ "actions -> taxCategory: Missing required value",
876
+ },
877
+ 400,
878
+ );
879
+ }
880
+ resource.taxCategory = taxCategoryReference;
881
+ return resource;
882
+ }
883
+
884
+ transitionState(
885
+ context: RepositoryContext,
886
+ resource: Writable<Product>,
887
+ { state, force }: ProductTransitionStateAction,
888
+ ) {
889
+ let productStateReference: StateReference | undefined = undefined;
890
+ if (state) {
891
+ productStateReference =
892
+ getReferenceFromResourceIdentifier<StateReference>(
893
+ state,
894
+ context.projectKey,
895
+ this._storage,
896
+ );
897
+ resource.state = productStateReference;
898
+ } else {
899
+ throw new CommercetoolsError<InvalidJsonInputError>(
900
+ {
901
+ code: "InvalidJsonInput",
902
+ message: "Request body does not contain valid JSON.",
903
+ detailedErrorMessage: "actions -> state: Missing required value",
904
+ },
905
+ 400,
906
+ );
907
+ }
908
+
909
+ return resource;
910
+ }
911
+
912
+ unpublish(
913
+ context: RepositoryContext,
914
+ resource: Writable<Product>,
915
+ // { action }: ProductUnpublishAction
916
+ ) {
917
+ resource.masterData.published = false;
918
+ checkForStagedChanges(resource);
919
+ }
920
+
921
+ // 'setPrices': () => {},
922
+ // 'setProductPriceCustomType': () => {},
923
+ // 'setProductPriceCustomField': () => {},
924
+ // 'setDiscountedPrice': () => {},
925
+ // 'setAttributeInAllVariants': () => {},
926
+ // 'setCategoryOrderHint': () => {},
927
+ // 'setSku': () => {},
928
+ // 'setProductVariantKey': () => {},
929
+ // 'setImageLabel': () => {},
930
+ // 'addAsset': () => {},
931
+ // 'removeAsset': () => {},
932
+ // 'setAssetKey': () => {},
933
+ // 'changeAssetOrder': () => {},
934
+ // 'changeAssetName': () => {},
935
+ // 'setAssetDescription': () => {},
936
+ // 'setAssetTags': () => {},
937
+ // 'setAssetSources': () => {},
938
+ // 'setAssetCustomType': () => {},
939
+ // 'setAssetCustomField': () => {},
940
+ // 'setSearchKeywords': () => {},
941
+ // 'revertStagedChanges': () => {},
942
+ // 'revertStagedVariantChanges': () => {},
943
+ }