@labdigital/commercetools-mock 1.5.0 → 1.6.1

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 (129) hide show
  1. package/README.md +5 -4
  2. package/dist/index.cjs +117 -17
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +30 -7
  5. package/dist/index.d.ts +30 -7
  6. package/dist/index.js +117 -17
  7. package/dist/index.js.map +1 -1
  8. package/package.json +4 -3
  9. package/src/constants.ts +2 -2
  10. package/src/ctMock.ts +176 -176
  11. package/src/exceptions.ts +10 -10
  12. package/src/helpers.ts +26 -26
  13. package/src/index.test.ts +173 -173
  14. package/src/index.ts +3 -3
  15. package/src/lib/expandParser.ts +19 -19
  16. package/src/lib/haversine.test.ts +13 -13
  17. package/src/lib/haversine.ts +14 -14
  18. package/src/lib/masking.ts +15 -15
  19. package/src/lib/parser.ts +2 -2
  20. package/src/lib/predicateParser.test.ts +204 -204
  21. package/src/lib/predicateParser.ts +398 -398
  22. package/src/lib/projectionSearchFilter.test.ts +168 -168
  23. package/src/lib/projectionSearchFilter.ts +272 -269
  24. package/src/lib/proxy.ts +8 -8
  25. package/src/oauth/errors.ts +4 -4
  26. package/src/oauth/helpers.ts +6 -6
  27. package/src/oauth/server.ts +103 -101
  28. package/src/oauth/store.ts +27 -27
  29. package/src/priceSelector.test.ts +68 -68
  30. package/src/priceSelector.ts +70 -70
  31. package/src/product-projection-search.ts +296 -296
  32. package/src/projectAPI.test.ts +3 -3
  33. package/src/projectAPI.ts +46 -46
  34. package/src/repositories/abstract.ts +190 -190
  35. package/src/repositories/associate-role.ts +10 -7
  36. package/src/repositories/attribute-group.ts +63 -8
  37. package/src/repositories/business-unit.ts +10 -7
  38. package/src/repositories/cart-discount.ts +134 -134
  39. package/src/repositories/cart.ts +517 -514
  40. package/src/repositories/category.ts +170 -167
  41. package/src/repositories/channel.ts +114 -111
  42. package/src/repositories/custom-object.ts +66 -63
  43. package/src/repositories/customer-group.ts +72 -69
  44. package/src/repositories/customer.ts +90 -90
  45. package/src/repositories/discount-code.ts +171 -168
  46. package/src/repositories/errors.ts +15 -15
  47. package/src/repositories/extension.ts +79 -76
  48. package/src/repositories/helpers.ts +180 -180
  49. package/src/repositories/index.ts +39 -39
  50. package/src/repositories/inventory-entry.ts +98 -95
  51. package/src/repositories/my-order.ts +11 -11
  52. package/src/repositories/order-edit.ts +29 -29
  53. package/src/repositories/order.test.ts +191 -191
  54. package/src/repositories/order.ts +393 -393
  55. package/src/repositories/payment.ts +155 -155
  56. package/src/repositories/product-discount.ts +149 -149
  57. package/src/repositories/product-projection.ts +135 -50
  58. package/src/repositories/product-selection.ts +31 -31
  59. package/src/repositories/product-type.ts +156 -156
  60. package/src/repositories/product.ts +600 -597
  61. package/src/repositories/project.ts +136 -135
  62. package/src/repositories/quote-request.ts +19 -19
  63. package/src/repositories/quote.ts +19 -19
  64. package/src/repositories/review.ts +24 -24
  65. package/src/repositories/shipping-method.ts +217 -217
  66. package/src/repositories/shopping-list.ts +49 -49
  67. package/src/repositories/staged-quote.ts +20 -20
  68. package/src/repositories/standalone-price.ts +72 -61
  69. package/src/repositories/state.ts +84 -84
  70. package/src/repositories/store.ts +114 -114
  71. package/src/repositories/subscription.ts +40 -40
  72. package/src/repositories/tax-category.ts +98 -98
  73. package/src/repositories/type.ts +157 -157
  74. package/src/repositories/zone.ts +71 -71
  75. package/src/server.ts +2 -2
  76. package/src/services/abstract.ts +173 -173
  77. package/src/services/attribute-group.ts +16 -0
  78. package/src/services/cart-discount.ts +8 -8
  79. package/src/services/cart.test.ts +409 -409
  80. package/src/services/cart.ts +50 -50
  81. package/src/services/category.test.ts +25 -25
  82. package/src/services/category.ts +8 -8
  83. package/src/services/channel.ts +8 -8
  84. package/src/services/custom-object.test.ts +184 -184
  85. package/src/services/custom-object.ts +48 -48
  86. package/src/services/customer-group.ts +8 -8
  87. package/src/services/customer.test.ts +151 -151
  88. package/src/services/customer.ts +27 -27
  89. package/src/services/discount-code.ts +8 -8
  90. package/src/services/extension.ts +8 -8
  91. package/src/services/index.ts +52 -44
  92. package/src/services/inventory-entry.test.ts +162 -162
  93. package/src/services/inventory-entry.ts +8 -8
  94. package/src/services/my-cart.test.ts +78 -78
  95. package/src/services/my-cart.ts +28 -28
  96. package/src/services/my-customer.test.ts +44 -44
  97. package/src/services/my-customer.ts +53 -53
  98. package/src/services/my-order.ts +20 -20
  99. package/src/services/my-payment.test.ts +65 -65
  100. package/src/services/my-payment.ts +8 -8
  101. package/src/services/order.test.ts +527 -527
  102. package/src/services/order.ts +31 -31
  103. package/src/services/payment.test.ts +65 -65
  104. package/src/services/payment.ts +8 -8
  105. package/src/services/product-discount.ts +8 -8
  106. package/src/services/product-projection.test.ts +502 -428
  107. package/src/services/product-projection.ts +32 -18
  108. package/src/services/product-type.test.ts +56 -56
  109. package/src/services/product-type.ts +8 -8
  110. package/src/services/product.test.ts +510 -510
  111. package/src/services/product.ts +8 -8
  112. package/src/services/project.ts +34 -34
  113. package/src/services/shipping-method.test.ts +81 -81
  114. package/src/services/shipping-method.ts +12 -12
  115. package/src/services/shopping-list.ts +8 -8
  116. package/src/services/standalone-price.test.ts +256 -256
  117. package/src/services/standalone-price.ts +8 -8
  118. package/src/services/state.test.ts +42 -42
  119. package/src/services/state.ts +8 -8
  120. package/src/services/store.test.ts +57 -57
  121. package/src/services/store.ts +8 -8
  122. package/src/services/subscription.ts +8 -8
  123. package/src/services/tax-category.test.ts +61 -61
  124. package/src/services/tax-category.ts +8 -8
  125. package/src/services/type.ts +8 -8
  126. package/src/services/zone.ts +8 -8
  127. package/src/storage/abstract.ts +58 -58
  128. package/src/storage/in-memory.ts +419 -419
  129. package/src/types.ts +82 -82
@@ -1,626 +1,629 @@
1
1
  import type {
2
- Price,
3
- PriceDraft,
4
- Product,
5
- ProductData,
6
- ProductDraft,
7
- ProductPublishAction,
8
- ProductSetAttributeAction,
9
- ProductSetDescriptionAction,
10
- ProductAddExternalImageAction,
11
- ProductRemoveImageAction,
12
- ProductSetKeyAction,
13
- ProductTypeReference,
14
- ProductUpdateAction,
15
- ProductVariant,
16
- ProductVariantDraft,
17
- ProductMoveImageToPositionAction,
18
- ProductChangePriceAction,
19
- ProductAddPriceAction,
20
- ProductRemovePriceAction,
2
+ Price,
3
+ PriceDraft,
4
+ Product,
5
+ ProductData,
6
+ ProductDraft,
7
+ ProductPublishAction,
8
+ ProductSetAttributeAction,
9
+ ProductSetDescriptionAction,
10
+ ProductAddExternalImageAction,
11
+ ProductRemoveImageAction,
12
+ ProductSetKeyAction,
13
+ ProductTypeReference,
14
+ ProductUpdateAction,
15
+ ProductVariant,
16
+ ProductVariantDraft,
17
+ ProductMoveImageToPositionAction,
18
+ ProductChangePriceAction,
19
+ ProductAddPriceAction,
20
+ ProductRemovePriceAction,
21
21
  } from '@commercetools/platform-sdk'
22
22
  import { v4 as uuidv4 } from 'uuid'
23
23
  import type { Writable } from '../types.js'
24
24
  import { getBaseResourceProperties } from '../helpers.js'
25
25
  import { AbstractResourceRepository, RepositoryContext } from './abstract.js'
26
- import { createTypedMoney, getReferenceFromResourceIdentifier } from './helpers.js'
26
+ import {
27
+ createTypedMoney,
28
+ getReferenceFromResourceIdentifier,
29
+ } from './helpers.js'
27
30
  import deepEqual from 'deep-equal'
28
31
 
29
32
  export class ProductRepository extends AbstractResourceRepository<'product'> {
30
- getTypeId() {
31
- return 'product' as const
32
- }
33
-
34
- create(context: RepositoryContext, draft: ProductDraft): Product {
35
- if (!draft.masterVariant) {
36
- throw new Error('Missing master variant')
37
- }
38
-
39
- let productType: ProductTypeReference | undefined = undefined
40
- try {
41
- productType = getReferenceFromResourceIdentifier<ProductTypeReference>(
42
- draft.productType,
43
- context.projectKey,
44
- this._storage
45
- )
46
- } catch (err) {
47
- // For now accept missing product types (but warn)
48
- console.warn(
49
- `Error resolving product-type '${draft.productType.id}'. This will be throw an error in later releases.`
50
- )
51
- productType = {
52
- typeId: 'product-type',
53
- id: draft.productType.id || '',
54
- }
55
- }
56
-
57
- const productData: ProductData = {
58
- name: draft.name,
59
- slug: draft.slug,
60
- categories: [],
61
- masterVariant: variantFromDraft(1, draft.masterVariant),
62
- variants:
63
- draft.variants?.map((variant, index) =>
64
- variantFromDraft(index + 2, variant)
65
- ) ?? [],
66
-
67
- searchKeywords: draft.searchKeywords ?? {},
68
- }
69
-
70
- const resource: Product = {
71
- ...getBaseResourceProperties(),
72
- key: draft.key,
73
- productType: productType,
74
- masterData: {
75
- current: productData,
76
- staged: productData,
77
- hasStagedChanges: false,
78
- published: draft.publish ?? false,
79
- },
80
- }
81
-
82
- this.saveNew(context, resource)
83
-
84
- return resource
85
- }
86
-
87
- actions: Partial<
88
- Record<
89
- ProductUpdateAction['action'],
90
- (
91
- context: RepositoryContext,
92
- resource: Writable<Product>,
93
- action: any
94
- ) => void
95
- >
96
- > = {
97
- publish: (
98
- context: RepositoryContext,
99
- resource: Writable<Product>,
100
- { scope }: ProductPublishAction
101
- ) => {
102
- resource.masterData.current = resource.masterData.staged
103
- resource.masterData.published = true
104
- checkForStagedChanges(resource)
105
- },
106
- unpublish: (
107
- context: RepositoryContext,
108
- resource: Writable<Product>
109
- // { action }: ProductUnpublishAction
110
- ) => {
111
- resource.masterData.published = false
112
- checkForStagedChanges(resource)
113
- },
114
- setAttribute: (
115
- context: RepositoryContext,
116
- resource: Writable<Product>,
117
- { variantId, sku, name, value, staged }: ProductSetAttributeAction
118
- ) => {
119
- const setAttr = (data: Writable<ProductData>) => {
120
- const { variant, isMasterVariant, variantIndex } = getVariant(
121
- data,
122
- variantId,
123
- sku
124
- )
125
- if (!variant) {
126
- throw new Error(
127
- `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
128
- )
129
- }
130
-
131
- if (!variant.attributes) {
132
- variant.attributes = []
133
- }
134
-
135
- const existingAttr = variant.attributes.find(
136
- (attr) => attr.name === name
137
- )
138
- if (existingAttr) {
139
- existingAttr.value = value
140
- } else {
141
- variant.attributes.push({
142
- name,
143
- value,
144
- })
145
- }
146
- if (isMasterVariant) {
147
- data.masterVariant = variant
148
- } else {
149
- data.variants[variantIndex] = variant
150
- }
151
- }
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
- setAttr(resource.masterData.staged)
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
- setAttr(resource.masterData.current)
165
- }
166
- checkForStagedChanges(resource)
167
-
168
- return resource
169
- },
170
- setDescription: (
171
- context: RepositoryContext,
172
- resource: Writable<Product>,
173
- { description, staged }: ProductSetDescriptionAction
174
- ) => {
175
- const onlyStaged = staged !== undefined ? staged : true
176
-
177
- resource.masterData.staged.description = description
178
- if (!onlyStaged) {
179
- resource.masterData.current.description = description
180
- }
181
- checkForStagedChanges(resource)
182
- return resource
183
- },
184
- setKey: (
185
- context: RepositoryContext,
186
- resource: Writable<Product>,
187
- { key }: ProductSetKeyAction
188
- ) => {
189
- resource.key = key
190
- return resource
191
- },
192
- addExternalImage: (
193
- context: RepositoryContext,
194
- resource: Writable<Product>,
195
- { variantId, sku, image, staged }: ProductAddExternalImageAction
196
- ) => {
197
- const addImg = (data: Writable<ProductData>) => {
198
- const { variant, isMasterVariant, variantIndex } = getVariant(
199
- data,
200
- variantId,
201
- sku
202
- )
203
- if (!variant) {
204
- throw new Error(
205
- `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
206
- )
207
- }
208
-
209
- if (!variant.images) {
210
- variant.images = []
211
- } else {
212
- const existingImage = variant.images.find((x) => x.url === image.url)
213
- if (existingImage) {
214
- throw new Error(
215
- `Cannot add image '${image.url}' because product '${resource.id}' already has that image.`
216
- )
217
- }
218
- }
219
-
220
- // Add image
221
- variant.images.push(image)
222
-
223
- if (isMasterVariant) {
224
- data.masterVariant = variant
225
- } else {
226
- data.variants[variantIndex] = variant
227
- }
228
- }
229
-
230
- // If true, only the staged Attribute is set. If false, both current and
231
- // staged Attribute is set. Default is true
232
- const onlyStaged = staged !== undefined ? staged : true
233
-
234
- // Write the attribute to the staged data
235
- addImg(resource.masterData.staged)
236
-
237
- // Also write to published data is isStaged = false
238
- // if isStaged is false we set the attribute on both the staged and
239
- // published data.
240
- if (!onlyStaged) {
241
- addImg(resource.masterData.current)
242
- }
243
- checkForStagedChanges(resource)
244
-
245
- return resource
246
- },
247
- removeImage: (
248
- context: RepositoryContext,
249
- resource: Writable<Product>,
250
- { variantId, sku, imageUrl, staged }: ProductRemoveImageAction
251
- ) => {
252
- const removeImg = (data: Writable<ProductData>) => {
253
- const { variant, isMasterVariant, variantIndex } = getVariant(
254
- data,
255
- variantId,
256
- sku
257
- )
258
- if (!variant) {
259
- throw new Error(
260
- `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
261
- )
262
- }
263
-
264
- const variantImages = variant.images ?? []
265
- const existingImage = variantImages.find((x) => x.url === imageUrl)
266
- if (!existingImage) {
267
- throw new Error(
268
- `Cannot remove image '${imageUrl}' because product '${resource.id}' does not have that image.`
269
- )
270
- }
271
-
272
- // Remove image
273
- variant.images = variantImages.filter((image) => image.url !== imageUrl)
274
-
275
- if (isMasterVariant) {
276
- data.masterVariant = variant
277
- } else {
278
- data.variants[variantIndex] = variant
279
- }
280
- }
281
-
282
- // If true, only the staged Attribute is set. If false, both current and
283
- // staged Attribute is set. Default is true
284
- const onlyStaged = staged !== undefined ? staged : true
285
-
286
- // Write the attribute to the staged data
287
- removeImg(resource.masterData.staged)
288
-
289
- // Also write to published data is isStaged = false
290
- // if isStaged is false we set the attribute on both the staged and
291
- // published data.
292
- if (!onlyStaged) {
293
- removeImg(resource.masterData.current)
294
- }
295
- checkForStagedChanges(resource)
296
-
297
- return resource
298
- },
299
- moveImageToPosition: (
300
- context: RepositoryContext,
301
- resource: Writable<Product>,
302
- {
303
- variantId,
304
- sku,
305
- imageUrl,
306
- position,
307
- staged,
308
- }: ProductMoveImageToPositionAction
309
- ) => {
310
- const moveImg = (data: Writable<ProductData>) => {
311
- const { variant, isMasterVariant, variantIndex } = getVariant(
312
- data,
313
- variantId,
314
- sku
315
- )
316
- if (!variant) {
317
- throw new Error(
318
- `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
319
- )
320
- }
321
-
322
- const variantImages = variant.images ?? []
323
- const existingImage = variantImages.find((x) => x.url === imageUrl)
324
- if (!existingImage) {
325
- throw new Error(
326
- `Cannot move image '${imageUrl}' because product '${resource.id}' does not have that image.`
327
- )
328
- }
329
-
330
- if (position >= variantImages.length) {
331
- throw new Error(
332
- `Invalid position given. Position in images where the image should be moved. Must be between 0 and the total number of images minus 1.`
333
- )
334
- }
335
-
336
- // Remove image
337
- variant.images = variantImages.filter((image) => image.url !== imageUrl)
338
-
339
- // Re-add image to the correct position
340
- variant.images.splice(position, 0, existingImage)
341
-
342
- if (isMasterVariant) {
343
- data.masterVariant = variant
344
- } else {
345
- data.variants[variantIndex] = variant
346
- }
347
- }
348
-
349
- // If true, only the staged Attribute is set. If false, both current and
350
- // staged Attribute is set. Default is true
351
- const onlyStaged = staged !== undefined ? staged : true
352
-
353
- // Write the attribute to the staged data
354
- moveImg(resource.masterData.staged)
355
-
356
- // Also write to published data is isStaged = false
357
- // if isStaged is false we set the attribute on both the staged and
358
- // published data.
359
- if (!onlyStaged) {
360
- moveImg(resource.masterData.current)
361
- }
362
- checkForStagedChanges(resource)
363
-
364
- return resource
365
- },
366
-
367
- addPrice: (
368
- context: RepositoryContext,
369
- resource: Writable<Product>,
370
- { variantId, sku, price, staged }: ProductAddPriceAction
371
- ) => {
372
- const addVariantPrice = (data: Writable<ProductData>) => {
373
- const { variant, isMasterVariant, variantIndex } = getVariant(
374
- data,
375
- variantId,
376
- sku
377
- )
378
- if (!variant) {
379
- throw new Error(
380
- `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
381
- )
382
- }
383
-
384
- if (variant.prices === undefined) {
385
- variant.prices = [priceFromDraft(price)]
386
- } else {
387
- variant.prices.push(priceFromDraft(price))
388
- }
389
-
390
- if (isMasterVariant) {
391
- data.masterVariant = variant
392
- } else {
393
- data.variants[variantIndex] = variant
394
- }
395
- }
396
-
397
- // If true, only the staged Attribute is set. If false, both current and
398
- // staged Attribute is set. Default is true
399
- const onlyStaged = staged !== undefined ? staged : true
400
-
401
- // Write the attribute to the staged data
402
- addVariantPrice(resource.masterData.staged)
403
-
404
- // Also write to published data is isStaged = false
405
- // if isStaged is false we set the attribute on both the staged and
406
- // published data.
407
- if (!onlyStaged) {
408
- addVariantPrice(resource.masterData.current)
409
- }
410
- checkForStagedChanges(resource)
411
-
412
- return resource
413
- },
414
- changePrice: (
415
- context: RepositoryContext,
416
- resource: Writable<Product>,
417
- { priceId, price, staged }: ProductChangePriceAction
418
- ) => {
419
- const changeVariantPrice = (data: Writable<ProductData>) => {
420
- const allVariants = [data.masterVariant, ...(data.variants ?? [])]
421
- const priceVariant = allVariants.find((variant) =>
422
- variant.prices?.some((x) => x.id === priceId)
423
- )
424
- if (!priceVariant) {
425
- throw new Error(
426
- `Price with id ${priceId} not found on product ${resource.id}`
427
- )
428
- }
429
-
430
- const { variant, isMasterVariant, variantIndex } = getVariant(
431
- data,
432
- priceVariant.id,
433
- priceVariant.sku
434
- )
435
- if (!variant) {
436
- throw new Error(
437
- `Variant with id ${priceVariant.id} or sku ${priceVariant.sku} not found on product ${resource.id}`
438
- )
439
- }
440
-
441
- variant.prices = variant.prices?.map((x) => {
442
- if (x.id === priceId) {
443
- return { ...x, ...price } as Price
444
- }
445
- return x
446
- })
447
-
448
- if (isMasterVariant) {
449
- data.masterVariant = variant
450
- } else {
451
- data.variants[variantIndex] = variant
452
- }
453
- }
454
-
455
- // If true, only the staged Attribute is set. If false, both current and
456
- // staged Attribute is set. Default is true
457
- const onlyStaged = staged !== undefined ? staged : true
458
-
459
- // Write the attribute to the staged data
460
- changeVariantPrice(resource.masterData.staged)
461
-
462
- // Also write to published data is isStaged = false
463
- // if isStaged is false we set the attribute on both the staged and
464
- // published data.
465
- if (!onlyStaged) {
466
- changeVariantPrice(resource.masterData.current)
467
- }
468
- checkForStagedChanges(resource)
469
-
470
- return resource
471
- },
472
- removePrice: (
473
- context: RepositoryContext,
474
- resource: Writable<Product>,
475
- { priceId, staged }: ProductRemovePriceAction
476
- ) => {
477
- const removeVariantPrice = (data: Writable<ProductData>) => {
478
- const allVariants = [data.masterVariant, ...(data.variants ?? [])]
479
- const priceVariant = allVariants.find((variant) =>
480
- variant.prices?.some((x) => x.id === priceId)
481
- )
482
- if (!priceVariant) {
483
- throw new Error(
484
- `Price with id ${priceId} not found on product ${resource.id}`
485
- )
486
- }
487
-
488
- const { variant, isMasterVariant, variantIndex } = getVariant(
489
- data,
490
- priceVariant.id,
491
- priceVariant.sku
492
- )
493
- if (!variant) {
494
- throw new Error(
495
- `Variant with id ${priceVariant.id} or sku ${priceVariant.sku} not found on product ${resource.id}`
496
- )
497
- }
498
-
499
- variant.prices = variant.prices?.filter((x) => x.id !== priceId)
500
-
501
- if (isMasterVariant) {
502
- data.masterVariant = variant
503
- } else {
504
- data.variants[variantIndex] = variant
505
- }
506
- }
507
-
508
- // If true, only the staged Attribute is set. If false, both current and
509
- // staged Attribute is set. Default is true
510
- const onlyStaged = staged !== undefined ? staged : true
511
-
512
- // Write the attribute to the staged data
513
- removeVariantPrice(resource.masterData.staged)
514
-
515
- // Also write to published data is isStaged = false
516
- // if isStaged is false we set the attribute on both the staged and
517
- // published data.
518
- if (!onlyStaged) {
519
- removeVariantPrice(resource.masterData.current)
520
- }
521
- checkForStagedChanges(resource)
522
-
523
- return resource
524
- },
525
-
526
- // 'changeName': () => {},
527
- // 'changeSlug': () => {},
528
- // 'addVariant': () => {},
529
- // 'removeVariant': () => {},
530
- // 'changeMasterVariant': () => {},
531
- // 'setPrices': () => {},
532
- // 'setProductPriceCustomType': () => {},
533
- // 'setProductPriceCustomField': () => {},
534
- // 'setDiscountedPrice': () => {},
535
- // 'setAttributeInAllVariants': () => {},
536
- // 'addToCategory': () => {},
537
- // 'setCategoryOrderHint': () => {},
538
- // 'removeFromCategory': () => {},
539
- // 'setTaxCategory': () => {},
540
- // 'setSku': () => {},
541
- // 'setProductVariantKey': () => {},
542
- // 'setImageLabel': () => {},
543
- // 'addAsset': () => {},
544
- // 'removeAsset': () => {},
545
- // 'setAssetKey': () => {},
546
- // 'changeAssetOrder': () => {},
547
- // 'changeAssetName': () => {},
548
- // 'setAssetDescription': () => {},
549
- // 'setAssetTags': () => {},
550
- // 'setAssetSources': () => {},
551
- // 'setAssetCustomType': () => {},
552
- // 'setAssetCustomField': () => {},
553
- // 'setSearchKeywords': () => {},
554
- // 'setMetaTitle': () => {},
555
- // 'setMetaDescription': () => {},
556
- // 'setMetaKeywords': () => {},
557
- // 'revertStagedChanges': () => {},
558
- // 'revertStagedVariantChanges': () => {},
559
- // 'transitionState': () => {},
560
- }
33
+ getTypeId() {
34
+ return 'product' as const
35
+ }
36
+
37
+ create(context: RepositoryContext, draft: ProductDraft): Product {
38
+ if (!draft.masterVariant) {
39
+ throw new Error('Missing master variant')
40
+ }
41
+
42
+ let productType: ProductTypeReference | undefined = undefined
43
+ try {
44
+ productType = getReferenceFromResourceIdentifier<ProductTypeReference>(
45
+ draft.productType,
46
+ context.projectKey,
47
+ this._storage
48
+ )
49
+ } catch (err) {
50
+ // For now accept missing product types (but warn)
51
+ console.warn(
52
+ `Error resolving product-type '${draft.productType.id}'. This will be throw an error in later releases.`
53
+ )
54
+ productType = {
55
+ typeId: 'product-type',
56
+ id: draft.productType.id || '',
57
+ }
58
+ }
59
+
60
+ const productData: ProductData = {
61
+ name: draft.name,
62
+ slug: draft.slug,
63
+ categories: [],
64
+ masterVariant: variantFromDraft(1, draft.masterVariant),
65
+ variants:
66
+ draft.variants?.map((variant, index) =>
67
+ variantFromDraft(index + 2, variant)
68
+ ) ?? [],
69
+
70
+ searchKeywords: draft.searchKeywords ?? {},
71
+ }
72
+
73
+ const resource: Product = {
74
+ ...getBaseResourceProperties(),
75
+ key: draft.key,
76
+ productType: productType,
77
+ masterData: {
78
+ current: productData,
79
+ staged: productData,
80
+ hasStagedChanges: false,
81
+ published: draft.publish ?? false,
82
+ },
83
+ }
84
+
85
+ this.saveNew(context, resource)
86
+
87
+ return resource
88
+ }
89
+
90
+ actions: Partial<
91
+ Record<
92
+ ProductUpdateAction['action'],
93
+ (
94
+ context: RepositoryContext,
95
+ resource: Writable<Product>,
96
+ action: any
97
+ ) => void
98
+ >
99
+ > = {
100
+ publish: (
101
+ context: RepositoryContext,
102
+ resource: Writable<Product>,
103
+ { scope }: ProductPublishAction
104
+ ) => {
105
+ resource.masterData.current = resource.masterData.staged
106
+ resource.masterData.published = true
107
+ checkForStagedChanges(resource)
108
+ },
109
+ unpublish: (
110
+ context: RepositoryContext,
111
+ resource: Writable<Product>
112
+ // { action }: ProductUnpublishAction
113
+ ) => {
114
+ resource.masterData.published = false
115
+ checkForStagedChanges(resource)
116
+ },
117
+ setAttribute: (
118
+ context: RepositoryContext,
119
+ resource: Writable<Product>,
120
+ { variantId, sku, name, value, staged }: ProductSetAttributeAction
121
+ ) => {
122
+ const setAttr = (data: Writable<ProductData>) => {
123
+ const { variant, isMasterVariant, variantIndex } = getVariant(
124
+ data,
125
+ variantId,
126
+ sku
127
+ )
128
+ if (!variant) {
129
+ throw new Error(
130
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
131
+ )
132
+ }
133
+
134
+ if (!variant.attributes) {
135
+ variant.attributes = []
136
+ }
137
+
138
+ const existingAttr = variant.attributes.find(
139
+ (attr) => attr.name === name
140
+ )
141
+ if (existingAttr) {
142
+ existingAttr.value = value
143
+ } else {
144
+ variant.attributes.push({
145
+ name,
146
+ value,
147
+ })
148
+ }
149
+ if (isMasterVariant) {
150
+ data.masterVariant = variant
151
+ } else {
152
+ data.variants[variantIndex] = variant
153
+ }
154
+ }
155
+
156
+ // If true, only the staged Attribute is set. If false, both current and
157
+ // staged Attribute is set. Default is true
158
+ const onlyStaged = staged !== undefined ? staged : true
159
+
160
+ // Write the attribute to the staged data
161
+ setAttr(resource.masterData.staged)
162
+
163
+ // Also write to published data is isStaged = false
164
+ // if isStaged is false we set the attribute on both the staged and
165
+ // published data.
166
+ if (!onlyStaged) {
167
+ setAttr(resource.masterData.current)
168
+ }
169
+ checkForStagedChanges(resource)
170
+
171
+ return resource
172
+ },
173
+ setDescription: (
174
+ context: RepositoryContext,
175
+ resource: Writable<Product>,
176
+ { description, staged }: ProductSetDescriptionAction
177
+ ) => {
178
+ const onlyStaged = staged !== undefined ? staged : true
179
+
180
+ resource.masterData.staged.description = description
181
+ if (!onlyStaged) {
182
+ resource.masterData.current.description = description
183
+ }
184
+ checkForStagedChanges(resource)
185
+ return resource
186
+ },
187
+ setKey: (
188
+ context: RepositoryContext,
189
+ resource: Writable<Product>,
190
+ { key }: ProductSetKeyAction
191
+ ) => {
192
+ resource.key = key
193
+ return resource
194
+ },
195
+ addExternalImage: (
196
+ context: RepositoryContext,
197
+ resource: Writable<Product>,
198
+ { variantId, sku, image, staged }: ProductAddExternalImageAction
199
+ ) => {
200
+ const addImg = (data: Writable<ProductData>) => {
201
+ const { variant, isMasterVariant, variantIndex } = getVariant(
202
+ data,
203
+ variantId,
204
+ sku
205
+ )
206
+ if (!variant) {
207
+ throw new Error(
208
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
209
+ )
210
+ }
211
+
212
+ if (!variant.images) {
213
+ variant.images = []
214
+ } else {
215
+ const existingImage = variant.images.find((x) => x.url === image.url)
216
+ if (existingImage) {
217
+ throw new Error(
218
+ `Cannot add image '${image.url}' because product '${resource.id}' already has that image.`
219
+ )
220
+ }
221
+ }
222
+
223
+ // Add image
224
+ variant.images.push(image)
225
+
226
+ if (isMasterVariant) {
227
+ data.masterVariant = variant
228
+ } else {
229
+ data.variants[variantIndex] = variant
230
+ }
231
+ }
232
+
233
+ // If true, only the staged Attribute is set. If false, both current and
234
+ // staged Attribute is set. Default is true
235
+ const onlyStaged = staged !== undefined ? staged : true
236
+
237
+ // Write the attribute to the staged data
238
+ addImg(resource.masterData.staged)
239
+
240
+ // Also write to published data is isStaged = false
241
+ // if isStaged is false we set the attribute on both the staged and
242
+ // published data.
243
+ if (!onlyStaged) {
244
+ addImg(resource.masterData.current)
245
+ }
246
+ checkForStagedChanges(resource)
247
+
248
+ return resource
249
+ },
250
+ removeImage: (
251
+ context: RepositoryContext,
252
+ resource: Writable<Product>,
253
+ { variantId, sku, imageUrl, staged }: ProductRemoveImageAction
254
+ ) => {
255
+ const removeImg = (data: Writable<ProductData>) => {
256
+ const { variant, isMasterVariant, variantIndex } = getVariant(
257
+ data,
258
+ variantId,
259
+ sku
260
+ )
261
+ if (!variant) {
262
+ throw new Error(
263
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
264
+ )
265
+ }
266
+
267
+ const variantImages = variant.images ?? []
268
+ const existingImage = variantImages.find((x) => x.url === imageUrl)
269
+ if (!existingImage) {
270
+ throw new Error(
271
+ `Cannot remove image '${imageUrl}' because product '${resource.id}' does not have that image.`
272
+ )
273
+ }
274
+
275
+ // Remove image
276
+ variant.images = variantImages.filter((image) => image.url !== imageUrl)
277
+
278
+ if (isMasterVariant) {
279
+ data.masterVariant = variant
280
+ } else {
281
+ data.variants[variantIndex] = variant
282
+ }
283
+ }
284
+
285
+ // If true, only the staged Attribute is set. If false, both current and
286
+ // staged Attribute is set. Default is true
287
+ const onlyStaged = staged !== undefined ? staged : true
288
+
289
+ // Write the attribute to the staged data
290
+ removeImg(resource.masterData.staged)
291
+
292
+ // Also write to published data is isStaged = false
293
+ // if isStaged is false we set the attribute on both the staged and
294
+ // published data.
295
+ if (!onlyStaged) {
296
+ removeImg(resource.masterData.current)
297
+ }
298
+ checkForStagedChanges(resource)
299
+
300
+ return resource
301
+ },
302
+ moveImageToPosition: (
303
+ context: RepositoryContext,
304
+ resource: Writable<Product>,
305
+ {
306
+ variantId,
307
+ sku,
308
+ imageUrl,
309
+ position,
310
+ staged,
311
+ }: ProductMoveImageToPositionAction
312
+ ) => {
313
+ const moveImg = (data: Writable<ProductData>) => {
314
+ const { variant, isMasterVariant, variantIndex } = getVariant(
315
+ data,
316
+ variantId,
317
+ sku
318
+ )
319
+ if (!variant) {
320
+ throw new Error(
321
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
322
+ )
323
+ }
324
+
325
+ const variantImages = variant.images ?? []
326
+ const existingImage = variantImages.find((x) => x.url === imageUrl)
327
+ if (!existingImage) {
328
+ throw new Error(
329
+ `Cannot move image '${imageUrl}' because product '${resource.id}' does not have that image.`
330
+ )
331
+ }
332
+
333
+ if (position >= variantImages.length) {
334
+ throw new Error(
335
+ `Invalid position given. Position in images where the image should be moved. Must be between 0 and the total number of images minus 1.`
336
+ )
337
+ }
338
+
339
+ // Remove image
340
+ variant.images = variantImages.filter((image) => image.url !== imageUrl)
341
+
342
+ // Re-add image to the correct position
343
+ variant.images.splice(position, 0, existingImage)
344
+
345
+ if (isMasterVariant) {
346
+ data.masterVariant = variant
347
+ } else {
348
+ data.variants[variantIndex] = variant
349
+ }
350
+ }
351
+
352
+ // If true, only the staged Attribute is set. If false, both current and
353
+ // staged Attribute is set. Default is true
354
+ const onlyStaged = staged !== undefined ? staged : true
355
+
356
+ // Write the attribute to the staged data
357
+ moveImg(resource.masterData.staged)
358
+
359
+ // Also write to published data is isStaged = false
360
+ // if isStaged is false we set the attribute on both the staged and
361
+ // published data.
362
+ if (!onlyStaged) {
363
+ moveImg(resource.masterData.current)
364
+ }
365
+ checkForStagedChanges(resource)
366
+
367
+ return resource
368
+ },
369
+
370
+ addPrice: (
371
+ context: RepositoryContext,
372
+ resource: Writable<Product>,
373
+ { variantId, sku, price, staged }: ProductAddPriceAction
374
+ ) => {
375
+ const addVariantPrice = (data: Writable<ProductData>) => {
376
+ const { variant, isMasterVariant, variantIndex } = getVariant(
377
+ data,
378
+ variantId,
379
+ sku
380
+ )
381
+ if (!variant) {
382
+ throw new Error(
383
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
384
+ )
385
+ }
386
+
387
+ if (variant.prices === undefined) {
388
+ variant.prices = [priceFromDraft(price)]
389
+ } else {
390
+ variant.prices.push(priceFromDraft(price))
391
+ }
392
+
393
+ if (isMasterVariant) {
394
+ data.masterVariant = variant
395
+ } else {
396
+ data.variants[variantIndex] = variant
397
+ }
398
+ }
399
+
400
+ // If true, only the staged Attribute is set. If false, both current and
401
+ // staged Attribute is set. Default is true
402
+ const onlyStaged = staged !== undefined ? staged : true
403
+
404
+ // Write the attribute to the staged data
405
+ addVariantPrice(resource.masterData.staged)
406
+
407
+ // Also write to published data is isStaged = false
408
+ // if isStaged is false we set the attribute on both the staged and
409
+ // published data.
410
+ if (!onlyStaged) {
411
+ addVariantPrice(resource.masterData.current)
412
+ }
413
+ checkForStagedChanges(resource)
414
+
415
+ return resource
416
+ },
417
+ changePrice: (
418
+ context: RepositoryContext,
419
+ resource: Writable<Product>,
420
+ { priceId, price, staged }: ProductChangePriceAction
421
+ ) => {
422
+ const changeVariantPrice = (data: Writable<ProductData>) => {
423
+ const allVariants = [data.masterVariant, ...(data.variants ?? [])]
424
+ const priceVariant = allVariants.find(
425
+ (variant) => variant.prices?.some((x) => x.id === priceId)
426
+ )
427
+ if (!priceVariant) {
428
+ throw new Error(
429
+ `Price with id ${priceId} not found on product ${resource.id}`
430
+ )
431
+ }
432
+
433
+ const { variant, isMasterVariant, variantIndex } = getVariant(
434
+ data,
435
+ priceVariant.id,
436
+ priceVariant.sku
437
+ )
438
+ if (!variant) {
439
+ throw new Error(
440
+ `Variant with id ${priceVariant.id} or sku ${priceVariant.sku} not found on product ${resource.id}`
441
+ )
442
+ }
443
+
444
+ variant.prices = variant.prices?.map((x) => {
445
+ if (x.id === priceId) {
446
+ return { ...x, ...price } as Price
447
+ }
448
+ return x
449
+ })
450
+
451
+ if (isMasterVariant) {
452
+ data.masterVariant = variant
453
+ } else {
454
+ data.variants[variantIndex] = variant
455
+ }
456
+ }
457
+
458
+ // If true, only the staged Attribute is set. If false, both current and
459
+ // staged Attribute is set. Default is true
460
+ const onlyStaged = staged !== undefined ? staged : true
461
+
462
+ // Write the attribute to the staged data
463
+ changeVariantPrice(resource.masterData.staged)
464
+
465
+ // Also write to published data is isStaged = false
466
+ // if isStaged is false we set the attribute on both the staged and
467
+ // published data.
468
+ if (!onlyStaged) {
469
+ changeVariantPrice(resource.masterData.current)
470
+ }
471
+ checkForStagedChanges(resource)
472
+
473
+ return resource
474
+ },
475
+ removePrice: (
476
+ context: RepositoryContext,
477
+ resource: Writable<Product>,
478
+ { priceId, staged }: ProductRemovePriceAction
479
+ ) => {
480
+ const removeVariantPrice = (data: Writable<ProductData>) => {
481
+ const allVariants = [data.masterVariant, ...(data.variants ?? [])]
482
+ const priceVariant = allVariants.find(
483
+ (variant) => variant.prices?.some((x) => x.id === priceId)
484
+ )
485
+ if (!priceVariant) {
486
+ throw new Error(
487
+ `Price with id ${priceId} not found on product ${resource.id}`
488
+ )
489
+ }
490
+
491
+ const { variant, isMasterVariant, variantIndex } = getVariant(
492
+ data,
493
+ priceVariant.id,
494
+ priceVariant.sku
495
+ )
496
+ if (!variant) {
497
+ throw new Error(
498
+ `Variant with id ${priceVariant.id} or sku ${priceVariant.sku} not found on product ${resource.id}`
499
+ )
500
+ }
501
+
502
+ variant.prices = variant.prices?.filter((x) => x.id !== priceId)
503
+
504
+ if (isMasterVariant) {
505
+ data.masterVariant = variant
506
+ } else {
507
+ data.variants[variantIndex] = variant
508
+ }
509
+ }
510
+
511
+ // If true, only the staged Attribute is set. If false, both current and
512
+ // staged Attribute is set. Default is true
513
+ const onlyStaged = staged !== undefined ? staged : true
514
+
515
+ // Write the attribute to the staged data
516
+ removeVariantPrice(resource.masterData.staged)
517
+
518
+ // Also write to published data is isStaged = false
519
+ // if isStaged is false we set the attribute on both the staged and
520
+ // published data.
521
+ if (!onlyStaged) {
522
+ removeVariantPrice(resource.masterData.current)
523
+ }
524
+ checkForStagedChanges(resource)
525
+
526
+ return resource
527
+ },
528
+
529
+ // 'changeName': () => {},
530
+ // 'changeSlug': () => {},
531
+ // 'addVariant': () => {},
532
+ // 'removeVariant': () => {},
533
+ // 'changeMasterVariant': () => {},
534
+ // 'setPrices': () => {},
535
+ // 'setProductPriceCustomType': () => {},
536
+ // 'setProductPriceCustomField': () => {},
537
+ // 'setDiscountedPrice': () => {},
538
+ // 'setAttributeInAllVariants': () => {},
539
+ // 'addToCategory': () => {},
540
+ // 'setCategoryOrderHint': () => {},
541
+ // 'removeFromCategory': () => {},
542
+ // 'setTaxCategory': () => {},
543
+ // 'setSku': () => {},
544
+ // 'setProductVariantKey': () => {},
545
+ // 'setImageLabel': () => {},
546
+ // 'addAsset': () => {},
547
+ // 'removeAsset': () => {},
548
+ // 'setAssetKey': () => {},
549
+ // 'changeAssetOrder': () => {},
550
+ // 'changeAssetName': () => {},
551
+ // 'setAssetDescription': () => {},
552
+ // 'setAssetTags': () => {},
553
+ // 'setAssetSources': () => {},
554
+ // 'setAssetCustomType': () => {},
555
+ // 'setAssetCustomField': () => {},
556
+ // 'setSearchKeywords': () => {},
557
+ // 'setMetaTitle': () => {},
558
+ // 'setMetaDescription': () => {},
559
+ // 'setMetaKeywords': () => {},
560
+ // 'revertStagedChanges': () => {},
561
+ // 'revertStagedVariantChanges': () => {},
562
+ // 'transitionState': () => {},
563
+ }
561
564
  }
562
565
 
563
566
  // Check if the product still has staged data that is different from the
564
567
  // current data.
565
568
  const checkForStagedChanges = (product: Writable<Product>) => {
566
- if (!product.masterData.staged) {
567
- product.masterData.staged = product.masterData.current
568
- }
569
-
570
- if (deepEqual(product.masterData.current, product.masterData.staged)) {
571
- product.masterData.hasStagedChanges = false
572
- } else {
573
- product.masterData.hasStagedChanges = true
574
- }
569
+ if (!product.masterData.staged) {
570
+ product.masterData.staged = product.masterData.current
571
+ }
572
+
573
+ if (deepEqual(product.masterData.current, product.masterData.staged)) {
574
+ product.masterData.hasStagedChanges = false
575
+ } else {
576
+ product.masterData.hasStagedChanges = true
577
+ }
575
578
  }
576
579
 
577
580
  interface VariantResult {
578
- variant: Writable<ProductVariant> | undefined
579
- isMasterVariant: boolean
580
- variantIndex: number
581
+ variant: Writable<ProductVariant> | undefined
582
+ isMasterVariant: boolean
583
+ variantIndex: number
581
584
  }
582
585
 
583
586
  const getVariant = (
584
- productData: ProductData,
585
- variantId?: number,
586
- sku?: string
587
+ productData: ProductData,
588
+ variantId?: number,
589
+ sku?: string
587
590
  ): VariantResult => {
588
- const variants = [productData.masterVariant, ...productData.variants]
589
- const foundVariant = variants.find((variant: ProductVariant) => {
590
- if (variantId) {
591
- return variant.id === variantId
592
- }
593
- if (sku) {
594
- return variant.sku === sku
595
- }
596
- return false
597
- })
598
-
599
- const isMasterVariant = foundVariant === productData.masterVariant
600
- return {
601
- variant: foundVariant,
602
- isMasterVariant,
603
- variantIndex:
604
- !isMasterVariant && foundVariant
605
- ? productData.variants.indexOf(foundVariant)
606
- : -1,
607
- }
591
+ const variants = [productData.masterVariant, ...productData.variants]
592
+ const foundVariant = variants.find((variant: ProductVariant) => {
593
+ if (variantId) {
594
+ return variant.id === variantId
595
+ }
596
+ if (sku) {
597
+ return variant.sku === sku
598
+ }
599
+ return false
600
+ })
601
+
602
+ const isMasterVariant = foundVariant === productData.masterVariant
603
+ return {
604
+ variant: foundVariant,
605
+ isMasterVariant,
606
+ variantIndex:
607
+ !isMasterVariant && foundVariant
608
+ ? productData.variants.indexOf(foundVariant)
609
+ : -1,
610
+ }
608
611
  }
609
612
 
610
613
  const variantFromDraft = (
611
- variantId: number,
612
- variant: ProductVariantDraft
614
+ variantId: number,
615
+ variant: ProductVariantDraft
613
616
  ): ProductVariant => ({
614
- id: variantId,
615
- sku: variant?.sku,
616
- attributes: variant?.attributes ?? [],
617
- prices: variant?.prices?.map(priceFromDraft),
618
- assets: [],
619
- images: [],
617
+ id: variantId,
618
+ sku: variant?.sku,
619
+ attributes: variant?.attributes ?? [],
620
+ prices: variant?.prices?.map(priceFromDraft),
621
+ assets: [],
622
+ images: [],
620
623
  })
621
624
 
622
625
  const priceFromDraft = (draft: PriceDraft): Price => ({
623
- id: uuidv4(),
624
- country: draft.country,
625
- value: createTypedMoney(draft.value),
626
+ id: uuidv4(),
627
+ country: draft.country,
628
+ value: createTypedMoney(draft.value),
626
629
  })