@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.
- package/dist/{config-BcNSzPZz.d.mts → config-CN3DgmWu.d.mts} +7 -5
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +287 -53
- package/dist/index.mjs.map +1 -1
- package/dist/storage/sqlite.d.mts +1 -1
- package/package.json +1 -1
- package/src/lib/tax.test.ts +53 -0
- package/src/lib/tax.ts +90 -13
- package/src/repositories/cart/actions.ts +399 -10
- package/src/repositories/cart/helpers.ts +61 -18
- package/src/repositories/cart/index.test.ts +76 -0
- package/src/repositories/cart/index.ts +38 -5
- package/src/repositories/order/index.test.ts +6 -2
- package/src/repositories/order/index.ts +82 -25
- package/src/services/cart.test.ts +633 -0
- package/src/shipping.ts +21 -12
|
@@ -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 {
|
|
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
|
|
149
|
-
taxRoundingMode
|
|
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:
|
|
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
|
-
|
|
957
|
-
expect(result.shippingInfo?.deliveries).
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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,
|