@labdigital/commercetools-mock 3.0.0-beta.2 → 3.0.0-beta.4

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.
@@ -720,6 +720,82 @@ describe("createShippingInfo", () => {
720
720
  expect(result.taxedPrice!.totalNet.centAmount).toBe(995);
721
721
  });
722
722
 
723
+ test("should use external tax rate when cart taxMode is External", async () => {
724
+ await storage.add("dummy", "shipping-method", {
725
+ ...getBaseResourceProperties(),
726
+ id: "external-tax-shipping-id",
727
+ name: "Standard Shipping",
728
+ taxCategory: {
729
+ typeId: "tax-category",
730
+ id: "shipping-tax-category-id",
731
+ },
732
+ zoneRates: [
733
+ {
734
+ zone: {
735
+ typeId: "zone",
736
+ id: "test-zone-id",
737
+ obj: {
738
+ ...getBaseResourceProperties(),
739
+ id: "test-zone-id",
740
+ name: "Test Zone",
741
+ locations: [{ country: "NL" }],
742
+ },
743
+ },
744
+ shippingRates: [
745
+ {
746
+ price: {
747
+ currencyCode: "EUR",
748
+ centAmount: 1000,
749
+ type: "centPrecision",
750
+ fractionDigits: 2,
751
+ },
752
+ tiers: [],
753
+ },
754
+ ],
755
+ },
756
+ ],
757
+ active: true,
758
+ isDefault: false,
759
+ });
760
+
761
+ const cart: any = {
762
+ ...getBaseResourceProperties(),
763
+ id: "external-tax-cart-id",
764
+ version: 1,
765
+ cartState: "Active",
766
+ taxMode: "External",
767
+ totalPrice: {
768
+ currencyCode: "EUR",
769
+ centAmount: 3000,
770
+ type: "centPrecision",
771
+ fractionDigits: 2,
772
+ },
773
+ shippingAddress: { country: "NL" },
774
+ taxRoundingMode: "HalfEven",
775
+ };
776
+
777
+ const context = { projectKey: "dummy", storeKey: "testStore" };
778
+
779
+ const result = await repository.createShippingInfo(
780
+ context,
781
+ cart,
782
+ { typeId: "shipping-method", id: "external-tax-shipping-id" },
783
+ {
784
+ name: "External VAT",
785
+ amount: 0.1,
786
+ includedInPrice: false,
787
+ country: "NL",
788
+ },
789
+ );
790
+
791
+ expect(result.price.centAmount).toBe(1000);
792
+ expect(result.taxRate?.name).toBe("External VAT");
793
+ expect(result.taxRate?.amount).toBe(0.1);
794
+ expect(result.taxRate?.country).toBe("NL");
795
+ expect(result.taxedPrice!.totalNet.centAmount).toBe(1000);
796
+ expect(result.taxedPrice!.totalGross.centAmount).toBe(1100);
797
+ });
798
+
723
799
  test("create cart with discount code", async () => {
724
800
  const code = await storage.add("dummy", "discount-code", {
725
801
  ...getBaseResourceProperties(),
@@ -3,6 +3,7 @@ import type {
3
3
  Cart,
4
4
  CartDraft,
5
5
  DiscountCodeInfo,
6
+ ExternalTaxRateDraft,
6
7
  GeneralError,
7
8
  InvalidOperationError,
8
9
  LineItem,
@@ -15,7 +16,11 @@ import { v4 as uuidv4 } from "uuid";
15
16
  import type { Config } from "#src/config.ts";
16
17
  import { CommercetoolsError } from "#src/exceptions.ts";
17
18
  import { getBaseResourceProperties } from "#src/helpers.ts";
18
- import { calculateTaxTotals } from "#src/lib/tax.ts";
19
+ import {
20
+ calculateTaxedPriceFromRate,
21
+ calculateTaxTotals,
22
+ taxRateFromExternalDraft,
23
+ } from "#src/lib/tax.ts";
19
24
  import { CartDraftSchema } from "#src/schemas/generated/cart.ts";
20
25
  import {
21
26
  createShippingInfoFromMethod,
@@ -76,6 +81,9 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
76
81
  );
77
82
  }
78
83
 
84
+ const taxMode = draft.taxMode ?? "Platform";
85
+ const taxRoundingMode = draft.taxRoundingMode ?? "HalfEven";
86
+
79
87
  const lineItems = draft.lineItems
80
88
  ? await Promise.all(
81
89
  draft.lineItems.map((draftLineItem) =>
@@ -84,6 +92,8 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
84
92
  draftLineItem,
85
93
  draft.currency,
86
94
  draft.country,
95
+ taxMode,
96
+ taxRoundingMode,
87
97
  ),
88
98
  ),
89
99
  )
@@ -97,6 +107,8 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
97
107
  draftCustomLineItem,
98
108
  this._storage,
99
109
  draft.shippingAddress?.country ?? draft.country,
110
+ taxMode,
111
+ taxRoundingMode,
100
112
  ),
101
113
  ),
102
114
  )
@@ -145,8 +157,8 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
145
157
  locale: draft.locale,
146
158
  priceRoundingMode: draft.priceRoundingMode ?? "HalfEven",
147
159
  taxCalculationMode: draft.taxCalculationMode ?? "LineItemLevel",
148
- taxMode: draft.taxMode ?? "Platform",
149
- taxRoundingMode: draft.taxRoundingMode ?? "HalfEven",
160
+ taxMode,
161
+ taxRoundingMode,
150
162
  totalPrice: {
151
163
  type: "centPrecision",
152
164
  centAmount: 0,
@@ -184,6 +196,7 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
184
196
  context,
185
197
  resource,
186
198
  draft.shippingMethod,
199
+ draft.externalTaxRateForShippingMethod ?? undefined,
187
200
  );
188
201
  }
189
202
 
@@ -211,6 +224,8 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
211
224
  draftLineItem: LineItemDraft,
212
225
  currency: string,
213
226
  country: string | undefined,
227
+ taxMode: Cart["taxMode"] = "Platform",
228
+ roundingMode: Cart["taxRoundingMode"] = "HalfEven",
214
229
  ): Promise<LineItem> => {
215
230
  const { productId, quantity, variantId, sku } = draftLineItem;
216
231
 
@@ -277,6 +292,19 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
277
292
  centAmount: calculateMoneyTotalCentAmount(price.value, quant),
278
293
  });
279
294
 
295
+ const taxRate =
296
+ taxMode === "External" && draftLineItem.externalTaxRate
297
+ ? taxRateFromExternalDraft(draftLineItem.externalTaxRate)
298
+ : undefined;
299
+ const taxedPrice = taxRate
300
+ ? calculateTaxedPriceFromRate(
301
+ totalPrice.centAmount,
302
+ totalPrice.currencyCode,
303
+ taxRate,
304
+ roundingMode,
305
+ )
306
+ : undefined;
307
+
280
308
  return {
281
309
  id: uuidv4(),
282
310
  productId: product.id,
@@ -287,6 +315,8 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
287
315
  variant,
288
316
  price: price,
289
317
  totalPrice,
318
+ taxRate,
319
+ taxedPrice,
290
320
  taxedPricePortions: [],
291
321
  perMethodTaxRate: [],
292
322
  quantity: quant,
@@ -306,11 +336,13 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
306
336
  context: RepositoryContext,
307
337
  resource: Writable<Cart>,
308
338
  shippingMethodRef: NonNullable<CartDraft["shippingMethod"]>,
339
+ externalTaxRate?: ExternalTaxRateDraft,
309
340
  ): Promise<NonNullable<Cart["shippingInfo"]>> {
310
- if (resource.taxMode === "External") {
341
+ if (resource.taxMode === "External" && !externalTaxRate) {
311
342
  throw new CommercetoolsError<InvalidOperationError>({
312
343
  code: "InvalidOperation",
313
- message: "External tax rate is not supported",
344
+ message:
345
+ "An external tax rate is required for a cart with External tax mode.",
314
346
  });
315
347
  }
316
348
 
@@ -355,6 +387,7 @@ export class CartRepository extends AbstractResourceRepository<"cart"> {
355
387
  this._storage,
356
388
  resource,
357
389
  method,
390
+ resource.taxMode === "External" ? externalTaxRate : undefined,
358
391
  );
359
392
  }
360
393
  }
@@ -953,8 +953,12 @@ describe("Order repository", () => {
953
953
  );
954
954
  expect(result.shippingInfo?.taxCategory?.id).toBe("tax-category-456");
955
955
  expect(result.shippingInfo?.taxRate?.amount).toBe(0.19);
956
- // Note: deliveries from import drafts are not currently supported in native implementation
957
- expect(result.shippingInfo?.deliveries).toEqual([]);
956
+ expect(result.shippingInfo?.deliveries).toHaveLength(1);
957
+ expect(result.shippingInfo?.deliveries?.[0].key).toBe("delivery-1");
958
+ expect(result.shippingInfo?.deliveries?.[0].parcels).toHaveLength(1);
959
+ expect(result.shippingInfo?.deliveries?.[0].parcels[0].key).toBe(
960
+ "parcel-1",
961
+ );
958
962
  });
959
963
 
960
964
  test("createShippingInfo throws error for non-existent shipping method", async () => {
@@ -4,6 +4,7 @@ import type {
4
4
  CartReference,
5
5
  CustomLineItem,
6
6
  CustomLineItemImportDraft,
7
+ Delivery,
7
8
  DuplicateFieldError,
8
9
  GeneralError,
9
10
  LineItem,
@@ -18,6 +19,7 @@ import type {
18
19
  ReferencedResourceNotFoundError,
19
20
  ResourceNotFoundError,
20
21
  ShippingInfo,
22
+ ShippingInfoImportDraft,
21
23
  ShippingMethodDoesNotMatchCartError,
22
24
  ShippingMethodReference,
23
25
  } from "@commercetools/platform-sdk";
@@ -206,30 +208,12 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
206
208
  totalPrice: createCentPrecisionMoney(draft.totalPrice),
207
209
  };
208
210
 
209
- // Set shipping info after resource is created
210
- if (draft.shippingInfo?.shippingMethod) {
211
- const { ...shippingMethodRef } = draft.shippingInfo.shippingMethod;
212
-
213
- // get id when reference is by key only
214
- if (shippingMethodRef.key && !shippingMethodRef.id) {
215
- const shippingMethod =
216
- await this._storage.getByResourceIdentifier<"shipping-method">(
217
- context.projectKey,
218
- shippingMethodRef,
219
- );
220
- if (!shippingMethod) {
221
- throw new CommercetoolsError<GeneralError>({
222
- code: "General",
223
- message: `A shipping method with key '${shippingMethodRef.key}' does not exist.`,
224
- });
225
- }
226
- shippingMethodRef.id = shippingMethod.id;
227
- }
228
-
229
- resource.shippingInfo = await this.createShippingInfo(context, resource, {
230
- typeId: "shipping-method",
231
- id: shippingMethodRef.id as string,
232
- });
211
+ if (draft.shippingInfo) {
212
+ resource.shippingInfo = await this.shippingInfoFromImportDraft(
213
+ context,
214
+ resource,
215
+ draft.shippingInfo,
216
+ );
233
217
  }
234
218
 
235
219
  const { taxedPrice, taxedShippingPrice } = calculateTaxTotals({
@@ -399,7 +383,7 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
399
383
  context: RepositoryContext,
400
384
  resource: Writable<Order>,
401
385
  shippingMethodRef: ShippingMethodReference,
402
- ): Promise<ShippingInfo> {
386
+ ): Promise<Writable<ShippingInfo>> {
403
387
  const cartLikeForMatching: Writable<Cart> = {
404
388
  ...resource,
405
389
  cartState: "Active" as const,
@@ -447,6 +431,79 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
447
431
  };
448
432
  }
449
433
 
434
+ private async shippingInfoFromImportDraft(
435
+ context: RepositoryContext,
436
+ resource: Writable<Order>,
437
+ draft: ShippingInfoImportDraft,
438
+ ): Promise<ShippingInfo | undefined> {
439
+ if (!draft.shippingMethod) {
440
+ return undefined;
441
+ }
442
+
443
+ const { ...shippingMethodRef } = draft.shippingMethod;
444
+ if (shippingMethodRef.key && !shippingMethodRef.id) {
445
+ const shippingMethod =
446
+ await this._storage.getByResourceIdentifier<"shipping-method">(
447
+ context.projectKey,
448
+ shippingMethodRef,
449
+ );
450
+ if (!shippingMethod) {
451
+ throw new CommercetoolsError<GeneralError>({
452
+ code: "General",
453
+ message: `A shipping method with key '${shippingMethodRef.key}' does not exist.`,
454
+ });
455
+ }
456
+ shippingMethodRef.id = shippingMethod.id;
457
+ }
458
+
459
+ const shippingInfo = await this.createShippingInfo(context, resource, {
460
+ typeId: "shipping-method",
461
+ id: shippingMethodRef.id as string,
462
+ });
463
+
464
+ shippingInfo.deliveries = await this.deliveriesFromImportDraft(
465
+ context,
466
+ draft,
467
+ );
468
+ return shippingInfo;
469
+ }
470
+
471
+ private async deliveriesFromImportDraft(
472
+ context: RepositoryContext,
473
+ draft: ShippingInfoImportDraft,
474
+ ): Promise<Delivery[]> {
475
+ if (!draft.deliveries) return [];
476
+
477
+ return Promise.all(
478
+ draft.deliveries.map(async (deliveryDraft) => ({
479
+ ...getBaseResourceProperties(),
480
+ key: deliveryDraft.key,
481
+ items: deliveryDraft.items ?? [],
482
+ parcels: await Promise.all(
483
+ deliveryDraft.parcels?.map(async (parcel) => ({
484
+ ...getBaseResourceProperties(),
485
+ ...parcel,
486
+ custom: await createCustomFields(
487
+ parcel.custom,
488
+ context.projectKey,
489
+ this._storage,
490
+ ),
491
+ })) ?? [],
492
+ ),
493
+ address: createAddress(
494
+ deliveryDraft.address,
495
+ context.projectKey,
496
+ this._storage,
497
+ ),
498
+ custom: await createCustomFields(
499
+ deliveryDraft.custom,
500
+ context.projectKey,
501
+ this._storage,
502
+ ),
503
+ })),
504
+ );
505
+ }
506
+
450
507
  async search(
451
508
  context: RepositoryContext,
452
509
  searchRequest: OrderSearchRequest,