@labdigital/commercetools-mock 2.59.0 → 2.60.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.
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +338 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/tax.test.ts +119 -0
- package/src/lib/tax.ts +186 -0
- package/src/repositories/cart/actions.ts +67 -0
- package/src/repositories/cart/helpers.ts +1 -80
- package/src/repositories/cart/index.test.ts +48 -0
- package/src/repositories/cart/index.ts +16 -141
- package/src/repositories/order/index.test.ts +490 -0
- package/src/repositories/order/index.ts +145 -7
- package/src/services/cart.test.ts +211 -0
- package/src/shipping.ts +157 -0
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import bodyParser from "body-parser";
|
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
10
|
import { isDeepStrictEqual } from "node:util";
|
|
11
11
|
import { Decimal } from "decimal.js/decimal";
|
|
12
|
+
import { Decimal as Decimal$1 } from "decimal.js";
|
|
12
13
|
import assert from "node:assert";
|
|
13
14
|
import { z } from "zod";
|
|
14
15
|
import { fromZodError } from "zod-validation-error";
|
|
@@ -521,70 +522,6 @@ var ProductTailoringUpdateHandler = class extends AbstractUpdateHandler {
|
|
|
521
522
|
}
|
|
522
523
|
};
|
|
523
524
|
|
|
524
|
-
//#endregion
|
|
525
|
-
//#region src/shipping.ts
|
|
526
|
-
const markMatchingShippingRate = (cart, shippingRate) => {
|
|
527
|
-
const isMatching = shippingRate.price.currencyCode === cart.totalPrice.currencyCode;
|
|
528
|
-
return {
|
|
529
|
-
...shippingRate,
|
|
530
|
-
tiers: markMatchingShippingRatePriceTiers(cart, shippingRate.tiers),
|
|
531
|
-
isMatching
|
|
532
|
-
};
|
|
533
|
-
};
|
|
534
|
-
const markMatchingShippingRatePriceTiers = (cart, tiers) => {
|
|
535
|
-
if (tiers.length === 0) return [];
|
|
536
|
-
if (new Set(tiers.map((tier) => tier.type)).size > 1) throw new Error("Can't handle multiple types of tiers");
|
|
537
|
-
const tierType = tiers[0].type;
|
|
538
|
-
switch (tierType) {
|
|
539
|
-
case "CartValue": return markMatchingCartValueTiers(cart, tiers);
|
|
540
|
-
default: throw new Error(`Unsupported tier type: ${tierType}`);
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
const markMatchingCartValueTiers = (cart, tiers) => {
|
|
544
|
-
const sortedTiers = [...tiers].sort((a, b) => b.minimumCentAmount - a.minimumCentAmount);
|
|
545
|
-
const result = {};
|
|
546
|
-
let hasMatchingTier = false;
|
|
547
|
-
for (const tier of sortedTiers) {
|
|
548
|
-
const isMatching = !hasMatchingTier && cart.totalPrice.currencyCode === tier.price.currencyCode && cart.totalPrice.centAmount >= tier.minimumCentAmount;
|
|
549
|
-
if (isMatching) hasMatchingTier = true;
|
|
550
|
-
result[tier.minimumCentAmount] = {
|
|
551
|
-
...tier,
|
|
552
|
-
isMatching
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
return tiers.map((tier) => result[tier.minimumCentAmount]);
|
|
556
|
-
};
|
|
557
|
-
const getShippingMethodsMatchingCart = (context, storage, cart, params = {}) => {
|
|
558
|
-
if (!cart.shippingAddress?.country) throw new CommercetoolsError({
|
|
559
|
-
code: "InvalidOperation",
|
|
560
|
-
message: `The cart with ID '${cart.id}' does not have a shipping address set.`
|
|
561
|
-
});
|
|
562
|
-
const zones = storage.query(context.projectKey, "zone", {
|
|
563
|
-
where: [`locations(country="${cart.shippingAddress.country}"))`],
|
|
564
|
-
limit: 100
|
|
565
|
-
});
|
|
566
|
-
const zoneIds = zones.results.map((zone) => zone.id);
|
|
567
|
-
const shippingMethods = storage.query(context.projectKey, "shipping-method", {
|
|
568
|
-
where: ["zoneRates(zone(id in (:zoneIds)))", `zoneRates(shippingRates(price(currencyCode="${cart.totalPrice.currencyCode}")))`],
|
|
569
|
-
"var.zoneIds": zoneIds,
|
|
570
|
-
expand: params.expand
|
|
571
|
-
});
|
|
572
|
-
const results = shippingMethods.results.map((shippingMethod) => {
|
|
573
|
-
const rates = shippingMethod.zoneRates.map((zoneRate) => ({
|
|
574
|
-
zone: zoneRate.zone,
|
|
575
|
-
shippingRates: zoneRate.shippingRates.map((rate) => markMatchingShippingRate(cart, rate)).filter((rate) => rate.isMatching)
|
|
576
|
-
})).filter((zoneRate) => zoneRate.shippingRates.length > 0);
|
|
577
|
-
return {
|
|
578
|
-
...shippingMethod,
|
|
579
|
-
zoneRates: rates
|
|
580
|
-
};
|
|
581
|
-
}).filter((shippingMethod) => shippingMethod.zoneRates.length > 0);
|
|
582
|
-
return {
|
|
583
|
-
...shippingMethods,
|
|
584
|
-
results
|
|
585
|
-
};
|
|
586
|
-
};
|
|
587
|
-
|
|
588
525
|
//#endregion
|
|
589
526
|
//#region src/repositories/helpers.ts
|
|
590
527
|
const createAddress = (base, projectKey, storage) => {
|
|
@@ -750,25 +687,71 @@ const getBusinessUnitKeyReference = (id, projectKey, storage) => {
|
|
|
750
687
|
};
|
|
751
688
|
|
|
752
689
|
//#endregion
|
|
753
|
-
//#region src/
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const currencyMatch = price.value.currencyCode === currency;
|
|
759
|
-
return countryMatch && currencyMatch;
|
|
690
|
+
//#region src/lib/tax.ts
|
|
691
|
+
const calculateTaxTotals = (resource) => {
|
|
692
|
+
const taxedItemPrices = [];
|
|
693
|
+
resource.lineItems.forEach((item) => {
|
|
694
|
+
if (item.taxedPrice) taxedItemPrices.push(item.taxedPrice);
|
|
760
695
|
});
|
|
696
|
+
resource.customLineItems.forEach((item) => {
|
|
697
|
+
if (item.taxedPrice) taxedItemPrices.push(item.taxedPrice);
|
|
698
|
+
});
|
|
699
|
+
let taxedShippingPrice;
|
|
700
|
+
if (resource.shippingInfo?.taxedPrice) {
|
|
701
|
+
taxedShippingPrice = resource.shippingInfo.taxedPrice;
|
|
702
|
+
taxedItemPrices.push(resource.shippingInfo.taxedPrice);
|
|
703
|
+
}
|
|
704
|
+
if (!taxedItemPrices.length) return {
|
|
705
|
+
taxedPrice: void 0,
|
|
706
|
+
taxedShippingPrice
|
|
707
|
+
};
|
|
708
|
+
const currencyCode = resource.totalPrice.currencyCode;
|
|
709
|
+
const toMoney = (centAmount) => createCentPrecisionMoney({
|
|
710
|
+
currencyCode,
|
|
711
|
+
centAmount
|
|
712
|
+
});
|
|
713
|
+
let totalNet = 0;
|
|
714
|
+
let totalGross = 0;
|
|
715
|
+
let totalTax = 0;
|
|
716
|
+
const taxPortionsByRate = new Map();
|
|
717
|
+
taxedItemPrices.forEach((price) => {
|
|
718
|
+
totalNet += price.totalNet.centAmount;
|
|
719
|
+
totalGross += price.totalGross.centAmount;
|
|
720
|
+
const priceTax = price.totalTax ? price.totalTax.centAmount : price.totalGross.centAmount - price.totalNet.centAmount;
|
|
721
|
+
totalTax += Math.max(priceTax, 0);
|
|
722
|
+
price.taxPortions?.forEach((portion) => {
|
|
723
|
+
const key = `${portion.rate}-${portion.name ?? ""}`;
|
|
724
|
+
const existing = taxPortionsByRate.get(key) ?? {
|
|
725
|
+
rate: portion.rate,
|
|
726
|
+
name: portion.name,
|
|
727
|
+
centAmount: 0
|
|
728
|
+
};
|
|
729
|
+
existing.centAmount += portion.amount.centAmount;
|
|
730
|
+
taxPortionsByRate.set(key, existing);
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
const taxPortions = Array.from(taxPortionsByRate.values()).map((portion) => ({
|
|
734
|
+
rate: portion.rate,
|
|
735
|
+
name: portion.name,
|
|
736
|
+
amount: toMoney(portion.centAmount)
|
|
737
|
+
}));
|
|
738
|
+
return {
|
|
739
|
+
taxedPrice: {
|
|
740
|
+
totalNet: toMoney(totalNet),
|
|
741
|
+
totalGross: toMoney(totalGross),
|
|
742
|
+
taxPortions,
|
|
743
|
+
totalTax: totalTax > 0 ? toMoney(totalTax) : void 0
|
|
744
|
+
},
|
|
745
|
+
taxedShippingPrice
|
|
746
|
+
};
|
|
761
747
|
};
|
|
762
|
-
const
|
|
763
|
-
const calculateCartTotalPrice = (cart) => {
|
|
764
|
-
const lineItemsTotal = cart.lineItems.reduce((cur, item) => cur + item.totalPrice.centAmount, 0);
|
|
765
|
-
const customLineItemsTotal = cart.customLineItems.reduce((cur, item) => cur + item.totalPrice.centAmount, 0);
|
|
766
|
-
return lineItemsTotal + customLineItemsTotal;
|
|
767
|
-
};
|
|
768
|
-
const calculateTaxedPrice = (amount, taxCategory, currency, country) => {
|
|
769
|
-
if (!taxCategory || !taxCategory.rates.length) return void 0;
|
|
770
|
-
const taxRate = taxCategory.rates.find((rate) => !rate.country || rate.country === country) || taxCategory.rates[0];
|
|
748
|
+
const buildTaxedPriceFromRate = (amount, currencyCode, taxRate) => {
|
|
771
749
|
if (!taxRate) return void 0;
|
|
750
|
+
const toMoney = (centAmount) => createCentPrecisionMoney({
|
|
751
|
+
type: "centPrecision",
|
|
752
|
+
currencyCode,
|
|
753
|
+
centAmount
|
|
754
|
+
});
|
|
772
755
|
let netAmount;
|
|
773
756
|
let grossAmount;
|
|
774
757
|
let taxAmount;
|
|
@@ -782,36 +765,173 @@ const calculateTaxedPrice = (amount, taxCategory, currency, country) => {
|
|
|
782
765
|
grossAmount = netAmount + taxAmount;
|
|
783
766
|
}
|
|
784
767
|
return {
|
|
785
|
-
totalNet:
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
centAmount: netAmount,
|
|
789
|
-
fractionDigits: 2
|
|
790
|
-
},
|
|
791
|
-
totalGross: {
|
|
792
|
-
type: "centPrecision",
|
|
793
|
-
currencyCode: currency,
|
|
794
|
-
centAmount: grossAmount,
|
|
795
|
-
fractionDigits: 2
|
|
796
|
-
},
|
|
768
|
+
totalNet: toMoney(netAmount),
|
|
769
|
+
totalGross: toMoney(grossAmount),
|
|
770
|
+
totalTax: taxAmount > 0 ? toMoney(taxAmount) : void 0,
|
|
797
771
|
taxPortions: taxAmount > 0 ? [{
|
|
798
772
|
rate: taxRate.amount,
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
773
|
+
name: taxRate.name,
|
|
774
|
+
amount: toMoney(taxAmount)
|
|
775
|
+
}] : []
|
|
776
|
+
};
|
|
777
|
+
};
|
|
778
|
+
const calculateTaxedPriceFromRate = (amount, currencyCode, taxRate) => buildTaxedPriceFromRate(amount, currencyCode, taxRate);
|
|
779
|
+
const calculateTaxedPrice = (amount, taxCategory, currency, country) => {
|
|
780
|
+
if (!taxCategory || !taxCategory.rates.length) return void 0;
|
|
781
|
+
const taxRate = taxCategory.rates.find((rate) => !rate.country || rate.country === country) || taxCategory.rates[0];
|
|
782
|
+
const taxedItemPrice = buildTaxedPriceFromRate(amount, currency, taxRate);
|
|
783
|
+
if (!taxedItemPrice) return void 0;
|
|
784
|
+
return {
|
|
785
|
+
totalNet: taxedItemPrice.totalNet,
|
|
786
|
+
totalGross: taxedItemPrice.totalGross,
|
|
787
|
+
taxPortions: taxedItemPrice.taxPortions,
|
|
788
|
+
totalTax: taxedItemPrice.totalTax
|
|
789
|
+
};
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
//#endregion
|
|
793
|
+
//#region src/shipping.ts
|
|
794
|
+
const markMatchingShippingRate = (cart, shippingRate) => {
|
|
795
|
+
const isMatching = shippingRate.price.currencyCode === cart.totalPrice.currencyCode;
|
|
796
|
+
return {
|
|
797
|
+
...shippingRate,
|
|
798
|
+
tiers: markMatchingShippingRatePriceTiers(cart, shippingRate.tiers),
|
|
799
|
+
isMatching
|
|
800
|
+
};
|
|
801
|
+
};
|
|
802
|
+
const markMatchingShippingRatePriceTiers = (cart, tiers) => {
|
|
803
|
+
if (tiers.length === 0) return [];
|
|
804
|
+
if (new Set(tiers.map((tier) => tier.type)).size > 1) throw new Error("Can't handle multiple types of tiers");
|
|
805
|
+
const tierType = tiers[0].type;
|
|
806
|
+
switch (tierType) {
|
|
807
|
+
case "CartValue": return markMatchingCartValueTiers(cart, tiers);
|
|
808
|
+
default: throw new Error(`Unsupported tier type: ${tierType}`);
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
const markMatchingCartValueTiers = (cart, tiers) => {
|
|
812
|
+
const sortedTiers = [...tiers].sort((a, b) => b.minimumCentAmount - a.minimumCentAmount);
|
|
813
|
+
const result = {};
|
|
814
|
+
let hasMatchingTier = false;
|
|
815
|
+
for (const tier of sortedTiers) {
|
|
816
|
+
const isMatching = !hasMatchingTier && cart.totalPrice.currencyCode === tier.price.currencyCode && cart.totalPrice.centAmount >= tier.minimumCentAmount;
|
|
817
|
+
if (isMatching) hasMatchingTier = true;
|
|
818
|
+
result[tier.minimumCentAmount] = {
|
|
819
|
+
...tier,
|
|
820
|
+
isMatching
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
return tiers.map((tier) => result[tier.minimumCentAmount]);
|
|
824
|
+
};
|
|
825
|
+
const getShippingMethodsMatchingCart = (context, storage, cart, params = {}) => {
|
|
826
|
+
if (!cart.shippingAddress?.country) throw new CommercetoolsError({
|
|
827
|
+
code: "InvalidOperation",
|
|
828
|
+
message: `The cart with ID '${cart.id}' does not have a shipping address set.`
|
|
829
|
+
});
|
|
830
|
+
const zones = storage.query(context.projectKey, "zone", {
|
|
831
|
+
where: [`locations(country="${cart.shippingAddress.country}"))`],
|
|
832
|
+
limit: 100
|
|
833
|
+
});
|
|
834
|
+
const zoneIds = zones.results.map((zone) => zone.id);
|
|
835
|
+
const shippingMethods = storage.query(context.projectKey, "shipping-method", {
|
|
836
|
+
where: ["zoneRates(zone(id in (:zoneIds)))", `zoneRates(shippingRates(price(currencyCode="${cart.totalPrice.currencyCode}")))`],
|
|
837
|
+
"var.zoneIds": zoneIds,
|
|
838
|
+
expand: params.expand
|
|
839
|
+
});
|
|
840
|
+
const results = shippingMethods.results.map((shippingMethod) => {
|
|
841
|
+
const rates = shippingMethod.zoneRates.map((zoneRate) => ({
|
|
842
|
+
zone: zoneRate.zone,
|
|
843
|
+
shippingRates: zoneRate.shippingRates.map((rate) => markMatchingShippingRate(cart, rate)).filter((rate) => rate.isMatching)
|
|
844
|
+
})).filter((zoneRate) => zoneRate.shippingRates.length > 0);
|
|
845
|
+
return {
|
|
846
|
+
...shippingMethod,
|
|
847
|
+
zoneRates: rates
|
|
848
|
+
};
|
|
849
|
+
}).filter((shippingMethod) => shippingMethod.zoneRates.length > 0);
|
|
850
|
+
return {
|
|
851
|
+
...shippingMethods,
|
|
852
|
+
results
|
|
853
|
+
};
|
|
854
|
+
};
|
|
855
|
+
/**
|
|
856
|
+
* Creates shipping info from a shipping method, handling all tax calculations and pricing logic.
|
|
857
|
+
*/
|
|
858
|
+
const createShippingInfoFromMethod = (context, storage, resource, method) => {
|
|
859
|
+
const country = resource.shippingAddress.country;
|
|
860
|
+
const zoneRate = method.zoneRates.find((rate) => rate.zone.obj?.locations.some((loc) => loc.country === country));
|
|
861
|
+
if (!zoneRate) throw new Error("Zone rate not found");
|
|
862
|
+
const shippingRate = zoneRate.shippingRates[0];
|
|
863
|
+
if (!shippingRate) throw new Error("Shipping rate not found");
|
|
864
|
+
const taxCategory = storage.getByResourceIdentifier(context.projectKey, method.taxCategory);
|
|
865
|
+
const taxRate = taxCategory.rates.find((rate) => rate.country === country);
|
|
866
|
+
if (!taxRate) throw new CommercetoolsError({
|
|
867
|
+
code: "MissingTaxRateForCountry",
|
|
868
|
+
message: `Tax category '${taxCategory.id}' is missing a tax rate for country '${country}'.`,
|
|
869
|
+
taxCategoryId: taxCategory.id
|
|
870
|
+
});
|
|
871
|
+
const shippingRateTier = shippingRate.tiers.find((tier) => tier.isMatching);
|
|
872
|
+
if (shippingRateTier && shippingRateTier.type !== "CartValue") throw new Error("Non-CartValue shipping rate tier is not supported");
|
|
873
|
+
let shippingPrice = shippingRateTier ? createCentPrecisionMoney(shippingRateTier.price) : shippingRate.price;
|
|
874
|
+
if (shippingRate.freeAbove && shippingRate.freeAbove.currencyCode === resource.totalPrice.currencyCode && resource.totalPrice.centAmount >= shippingRate.freeAbove.centAmount) shippingPrice = {
|
|
875
|
+
...shippingPrice,
|
|
876
|
+
centAmount: 0
|
|
877
|
+
};
|
|
878
|
+
const totalGross = taxRate.includedInPrice ? shippingPrice : {
|
|
879
|
+
...shippingPrice,
|
|
880
|
+
centAmount: roundDecimal(new Decimal$1(shippingPrice.centAmount).mul(1 + taxRate.amount), resource.taxRoundingMode || "HalfEven").toNumber()
|
|
881
|
+
};
|
|
882
|
+
const totalNet = taxRate.includedInPrice ? {
|
|
883
|
+
...shippingPrice,
|
|
884
|
+
centAmount: roundDecimal(new Decimal$1(shippingPrice.centAmount).div(1 + taxRate.amount), resource.taxRoundingMode || "HalfEven").toNumber()
|
|
885
|
+
} : shippingPrice;
|
|
886
|
+
const taxPortions = [{
|
|
887
|
+
name: taxRate.name,
|
|
888
|
+
rate: taxRate.amount,
|
|
889
|
+
amount: {
|
|
890
|
+
...shippingPrice,
|
|
891
|
+
centAmount: totalGross.centAmount - totalNet.centAmount
|
|
892
|
+
}
|
|
893
|
+
}];
|
|
894
|
+
const totalTax = {
|
|
895
|
+
...shippingPrice,
|
|
896
|
+
centAmount: taxPortions.reduce((acc, portion) => acc + portion.amount.centAmount, 0)
|
|
897
|
+
};
|
|
898
|
+
const taxedPrice = {
|
|
899
|
+
totalNet,
|
|
900
|
+
totalGross,
|
|
901
|
+
taxPortions,
|
|
902
|
+
totalTax
|
|
903
|
+
};
|
|
904
|
+
return {
|
|
905
|
+
shippingMethod: {
|
|
906
|
+
typeId: "shipping-method",
|
|
907
|
+
id: method.id
|
|
908
|
+
},
|
|
909
|
+
shippingMethodName: method.name,
|
|
910
|
+
price: shippingPrice,
|
|
911
|
+
shippingRate,
|
|
912
|
+
taxedPrice,
|
|
913
|
+
taxRate,
|
|
914
|
+
taxCategory: method.taxCategory,
|
|
915
|
+
shippingMethodState: "MatchesCart"
|
|
813
916
|
};
|
|
814
917
|
};
|
|
918
|
+
|
|
919
|
+
//#endregion
|
|
920
|
+
//#region src/repositories/cart/helpers.ts
|
|
921
|
+
const selectPrice = ({ prices, currency, country }) => {
|
|
922
|
+
if (!prices) return void 0;
|
|
923
|
+
return prices.find((price) => {
|
|
924
|
+
const countryMatch = !price.country || price.country === country;
|
|
925
|
+
const currencyMatch = price.value.currencyCode === currency;
|
|
926
|
+
return countryMatch && currencyMatch;
|
|
927
|
+
});
|
|
928
|
+
};
|
|
929
|
+
const calculateLineItemTotalPrice = (lineItem) => lineItem.price?.value.centAmount * lineItem.quantity;
|
|
930
|
+
const calculateCartTotalPrice = (cart) => {
|
|
931
|
+
const lineItemsTotal = cart.lineItems.reduce((cur, item) => cur + item.totalPrice.centAmount, 0);
|
|
932
|
+
const customLineItemsTotal = cart.customLineItems.reduce((cur, item) => cur + item.totalPrice.centAmount, 0);
|
|
933
|
+
return lineItemsTotal + customLineItemsTotal;
|
|
934
|
+
};
|
|
815
935
|
const createCustomLineItemFromDraft = (projectKey, draft, storage, country) => {
|
|
816
936
|
const quantity = draft.quantity ?? 1;
|
|
817
937
|
const taxCategoryRef = draft.taxCategory ? getReferenceFromResourceIdentifier(draft.taxCategory, projectKey, storage) : void 0;
|
|
@@ -1164,6 +1284,39 @@ var CartUpdateHandler = class extends AbstractUpdateHandler {
|
|
|
1164
1284
|
};
|
|
1165
1285
|
}
|
|
1166
1286
|
}
|
|
1287
|
+
setLineItemPrice(context, resource, { lineItemId, lineItemKey, externalPrice }) {
|
|
1288
|
+
const lineItem = resource.lineItems.find((x) => lineItemId && x.id === lineItemId || lineItemKey && x.key === lineItemKey);
|
|
1289
|
+
if (!lineItem) throw new CommercetoolsError({
|
|
1290
|
+
code: "General",
|
|
1291
|
+
message: lineItemKey ? `A line item with key '${lineItemKey}' not found.` : `A line item with ID '${lineItemId}' not found.`
|
|
1292
|
+
});
|
|
1293
|
+
if (!externalPrice && lineItem.priceMode !== "ExternalPrice") return;
|
|
1294
|
+
if (externalPrice && externalPrice.currencyCode !== resource.totalPrice.currencyCode) throw new CommercetoolsError({
|
|
1295
|
+
code: "General",
|
|
1296
|
+
message: `Currency mismatch. Expected '${resource.totalPrice.currencyCode}' but got '${externalPrice.currencyCode}'.`
|
|
1297
|
+
});
|
|
1298
|
+
if (externalPrice) {
|
|
1299
|
+
lineItem.priceMode = "ExternalPrice";
|
|
1300
|
+
const priceValue = createTypedMoney(externalPrice);
|
|
1301
|
+
lineItem.price = lineItem.price ?? { id: v4() };
|
|
1302
|
+
lineItem.price.value = priceValue;
|
|
1303
|
+
} else {
|
|
1304
|
+
lineItem.priceMode = "Platform";
|
|
1305
|
+
const price = selectPrice({
|
|
1306
|
+
prices: lineItem.variant.prices,
|
|
1307
|
+
currency: resource.totalPrice.currencyCode,
|
|
1308
|
+
country: resource.country
|
|
1309
|
+
});
|
|
1310
|
+
if (!price) throw new Error(`No valid price found for ${lineItem.productId} for country ${resource.country} and currency ${resource.totalPrice.currencyCode}`);
|
|
1311
|
+
lineItem.price = price;
|
|
1312
|
+
}
|
|
1313
|
+
const lineItemTotal = calculateLineItemTotalPrice(lineItem);
|
|
1314
|
+
lineItem.totalPrice = createCentPrecisionMoney({
|
|
1315
|
+
...lineItem.price.value,
|
|
1316
|
+
centAmount: lineItemTotal
|
|
1317
|
+
});
|
|
1318
|
+
resource.totalPrice.centAmount = calculateCartTotalPrice(resource);
|
|
1319
|
+
}
|
|
1167
1320
|
setLineItemShippingDetails(context, resource, { action, shippingDetails, lineItemId, lineItemKey }) {
|
|
1168
1321
|
const lineItem = resource.lineItems.find((x) => lineItemId && x.id === lineItemId || lineItemKey && x.key === lineItemKey);
|
|
1169
1322
|
if (!lineItem) throw new CommercetoolsError({
|
|
@@ -1296,6 +1449,9 @@ var CartRepository = class extends AbstractResourceRepository {
|
|
|
1296
1449
|
key: draft.store.key
|
|
1297
1450
|
} : void 0;
|
|
1298
1451
|
if (draft.shippingMethod) resource.shippingInfo = this.createShippingInfo(context, resource, draft.shippingMethod);
|
|
1452
|
+
const { taxedPrice, taxedShippingPrice } = calculateTaxTotals(resource);
|
|
1453
|
+
resource.taxedPrice = taxedPrice;
|
|
1454
|
+
resource.taxedShippingPrice = taxedShippingPrice;
|
|
1299
1455
|
return this.saveNew(context, resource);
|
|
1300
1456
|
}
|
|
1301
1457
|
getActiveCart(projectKey) {
|
|
@@ -1355,11 +1511,6 @@ var CartRepository = class extends AbstractResourceRepository {
|
|
|
1355
1511
|
};
|
|
1356
1512
|
createShippingInfo(context, resource, shippingMethodRef) {
|
|
1357
1513
|
if (resource.taxMode === "External") throw new Error("External tax rate is not supported");
|
|
1358
|
-
const country = resource.shippingAddress?.country;
|
|
1359
|
-
if (!country) throw new CommercetoolsError({
|
|
1360
|
-
code: "InvalidOperation",
|
|
1361
|
-
message: `The cart with ID '${resource.id}' does not have a shipping address set.`
|
|
1362
|
-
});
|
|
1363
1514
|
this._storage.getByResourceIdentifier(context.projectKey, shippingMethodRef);
|
|
1364
1515
|
const shippingMethods = getShippingMethodsMatchingCart(context, this._storage, resource, { expand: ["zoneRates[*].zone"] });
|
|
1365
1516
|
const method = shippingMethods.results.find((candidate) => shippingMethodRef.id ? candidate.id === shippingMethodRef.id : candidate.key === shippingMethodRef.key);
|
|
@@ -1367,63 +1518,7 @@ var CartRepository = class extends AbstractResourceRepository {
|
|
|
1367
1518
|
code: "ShippingMethodDoesNotMatchCart",
|
|
1368
1519
|
message: `The shipping method with ${shippingMethodRef.id ? `ID '${shippingMethodRef.id}'` : `key '${shippingMethodRef.key}'`} is not allowed for the cart with ID '${resource.id}'.`
|
|
1369
1520
|
});
|
|
1370
|
-
|
|
1371
|
-
const taxRate = taxCategory.rates.find((rate) => rate.country === country);
|
|
1372
|
-
if (!taxRate) throw new CommercetoolsError({
|
|
1373
|
-
code: "MissingTaxRateForCountry",
|
|
1374
|
-
message: `Tax category '${taxCategory.id}' is missing a tax rate for country '${country}'.`,
|
|
1375
|
-
taxCategoryId: taxCategory.id
|
|
1376
|
-
});
|
|
1377
|
-
const zoneRate = method.zoneRates.find((rate) => rate.zone.obj?.locations.some((loc) => loc.country === country));
|
|
1378
|
-
if (!zoneRate) throw new Error("Zone rate not found");
|
|
1379
|
-
const shippingRate = zoneRate.shippingRates[0];
|
|
1380
|
-
if (!shippingRate) throw new Error("Shipping rate not found");
|
|
1381
|
-
const shippingRateTier = shippingRate.tiers.find((tier) => tier.isMatching);
|
|
1382
|
-
if (shippingRateTier && shippingRateTier.type !== "CartValue") throw new Error("Non-CartValue shipping rate tier is not supported");
|
|
1383
|
-
let shippingPrice = shippingRateTier ? createCentPrecisionMoney(shippingRateTier.price) : shippingRate.price;
|
|
1384
|
-
if (shippingRate.freeAbove && shippingRate.freeAbove.currencyCode === resource.totalPrice.currencyCode && resource.totalPrice.centAmount >= shippingRate.freeAbove.centAmount) shippingPrice = {
|
|
1385
|
-
...shippingPrice,
|
|
1386
|
-
centAmount: 0
|
|
1387
|
-
};
|
|
1388
|
-
const totalGross = taxRate.includedInPrice ? shippingPrice : {
|
|
1389
|
-
...shippingPrice,
|
|
1390
|
-
centAmount: roundDecimal(new Decimal(shippingPrice.centAmount).mul(1 + taxRate.amount), resource.taxRoundingMode).toNumber()
|
|
1391
|
-
};
|
|
1392
|
-
const totalNet = taxRate.includedInPrice ? {
|
|
1393
|
-
...shippingPrice,
|
|
1394
|
-
centAmount: roundDecimal(new Decimal(shippingPrice.centAmount).div(1 + taxRate.amount), resource.taxRoundingMode).toNumber()
|
|
1395
|
-
} : shippingPrice;
|
|
1396
|
-
const taxPortions = [{
|
|
1397
|
-
name: taxRate.name,
|
|
1398
|
-
rate: taxRate.amount,
|
|
1399
|
-
amount: {
|
|
1400
|
-
...shippingPrice,
|
|
1401
|
-
centAmount: totalGross.centAmount - totalNet.centAmount
|
|
1402
|
-
}
|
|
1403
|
-
}];
|
|
1404
|
-
const totalTax = {
|
|
1405
|
-
...shippingPrice,
|
|
1406
|
-
centAmount: taxPortions.reduce((acc, portion) => acc + portion.amount.centAmount, 0)
|
|
1407
|
-
};
|
|
1408
|
-
const taxedPrice = {
|
|
1409
|
-
totalNet,
|
|
1410
|
-
totalGross,
|
|
1411
|
-
taxPortions,
|
|
1412
|
-
totalTax
|
|
1413
|
-
};
|
|
1414
|
-
return {
|
|
1415
|
-
shippingMethod: {
|
|
1416
|
-
typeId: "shipping-method",
|
|
1417
|
-
id: method.id
|
|
1418
|
-
},
|
|
1419
|
-
shippingMethodName: method.name,
|
|
1420
|
-
price: shippingPrice,
|
|
1421
|
-
shippingRate,
|
|
1422
|
-
taxedPrice,
|
|
1423
|
-
taxRate,
|
|
1424
|
-
taxCategory: method.taxCategory,
|
|
1425
|
-
shippingMethodState: "MatchesCart"
|
|
1426
|
-
};
|
|
1521
|
+
return createShippingInfoFromMethod(context, this._storage, resource, method);
|
|
1427
1522
|
}
|
|
1428
1523
|
};
|
|
1429
1524
|
|
|
@@ -1633,6 +1728,7 @@ var OrderRepository = class extends AbstractResourceRepository {
|
|
|
1633
1728
|
refusedGifts: [],
|
|
1634
1729
|
shipping: cart.shipping,
|
|
1635
1730
|
shippingAddress: cart.shippingAddress,
|
|
1731
|
+
shippingInfo: cart.shippingInfo,
|
|
1636
1732
|
shippingMode: cart.shippingMode,
|
|
1637
1733
|
syncInfo: [],
|
|
1638
1734
|
taxCalculationMode: cart.taxCalculationMode,
|
|
@@ -1643,6 +1739,14 @@ var OrderRepository = class extends AbstractResourceRepository {
|
|
|
1643
1739
|
totalPrice: cart.totalPrice,
|
|
1644
1740
|
store: cart.store
|
|
1645
1741
|
};
|
|
1742
|
+
const { taxedPrice, taxedShippingPrice } = calculateTaxTotals({
|
|
1743
|
+
lineItems: cart.lineItems,
|
|
1744
|
+
customLineItems: cart.customLineItems,
|
|
1745
|
+
shippingInfo: cart.shippingInfo,
|
|
1746
|
+
totalPrice: cart.totalPrice
|
|
1747
|
+
});
|
|
1748
|
+
resource.taxedPrice = resource.taxedPrice ?? taxedPrice;
|
|
1749
|
+
resource.taxedShippingPrice = resource.taxedShippingPrice ?? taxedShippingPrice;
|
|
1646
1750
|
return this.saveNew(context, resource);
|
|
1647
1751
|
}
|
|
1648
1752
|
import(context, draft) {
|
|
@@ -1666,12 +1770,36 @@ var OrderRepository = class extends AbstractResourceRepository {
|
|
|
1666
1770
|
refusedGifts: [],
|
|
1667
1771
|
shippingMode: "Single",
|
|
1668
1772
|
shipping: [],
|
|
1773
|
+
shippingInfo: void 0,
|
|
1669
1774
|
store: resolveStoreReference(draft.store, context.projectKey, this._storage),
|
|
1670
1775
|
syncInfo: [],
|
|
1671
1776
|
lineItems: draft.lineItems?.map((item) => this.lineItemFromImportDraft.bind(this)(context, item)) || [],
|
|
1672
1777
|
customLineItems: draft.customLineItems?.map((item) => this.customLineItemFromImportDraft.bind(this)(context, item)) || [],
|
|
1673
1778
|
totalPrice: createCentPrecisionMoney(draft.totalPrice)
|
|
1674
1779
|
};
|
|
1780
|
+
if (draft.shippingInfo?.shippingMethod) {
|
|
1781
|
+
const { ...shippingMethodRef } = draft.shippingInfo.shippingMethod;
|
|
1782
|
+
if (shippingMethodRef.key && !shippingMethodRef.id) {
|
|
1783
|
+
const shippingMethod = this._storage.getByResourceIdentifier(context.projectKey, shippingMethodRef);
|
|
1784
|
+
if (!shippingMethod) throw new CommercetoolsError({
|
|
1785
|
+
code: "General",
|
|
1786
|
+
message: `A shipping method with key '${shippingMethodRef.key}' does not exist.`
|
|
1787
|
+
});
|
|
1788
|
+
shippingMethodRef.id = shippingMethod.id;
|
|
1789
|
+
}
|
|
1790
|
+
resource.shippingInfo = this.createShippingInfo(context, resource, {
|
|
1791
|
+
typeId: "shipping-method",
|
|
1792
|
+
id: shippingMethodRef.id
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
const { taxedPrice, taxedShippingPrice } = calculateTaxTotals({
|
|
1796
|
+
lineItems: resource.lineItems,
|
|
1797
|
+
customLineItems: resource.customLineItems,
|
|
1798
|
+
shippingInfo: resource.shippingInfo,
|
|
1799
|
+
totalPrice: resource.totalPrice
|
|
1800
|
+
});
|
|
1801
|
+
resource.taxedPrice = resource.taxedPrice ?? taxedPrice;
|
|
1802
|
+
resource.taxedShippingPrice = resource.taxedShippingPrice ?? taxedShippingPrice;
|
|
1675
1803
|
return this.saveNew(context, resource);
|
|
1676
1804
|
}
|
|
1677
1805
|
lineItemFromImportDraft(context, draft) {
|
|
@@ -1692,6 +1820,11 @@ var OrderRepository = class extends AbstractResourceRepository {
|
|
|
1692
1820
|
else variant = product.masterData.current.variants.find((v) => v.sku === draft.variant.sku);
|
|
1693
1821
|
if (!variant) throw new Error("Internal state error");
|
|
1694
1822
|
} else throw new Error("No product found");
|
|
1823
|
+
const quantity = draft.quantity ?? 1;
|
|
1824
|
+
const totalPrice = createCentPrecisionMoney({
|
|
1825
|
+
...draft.price.value,
|
|
1826
|
+
centAmount: (draft.price.value.centAmount ?? 0) * quantity
|
|
1827
|
+
});
|
|
1695
1828
|
const lineItem = {
|
|
1696
1829
|
...getBaseResourceProperties(),
|
|
1697
1830
|
custom: createCustomFields(draft.custom, context.projectKey, this._storage),
|
|
@@ -1702,12 +1835,13 @@ var OrderRepository = class extends AbstractResourceRepository {
|
|
|
1702
1835
|
priceMode: "Platform",
|
|
1703
1836
|
productId: product.id,
|
|
1704
1837
|
productType: product.productType,
|
|
1705
|
-
quantity
|
|
1838
|
+
quantity,
|
|
1706
1839
|
state: draft.state || [],
|
|
1707
1840
|
taxRate: draft.taxRate,
|
|
1841
|
+
taxedPrice: calculateTaxedPriceFromRate(totalPrice.centAmount, totalPrice.currencyCode, draft.taxRate),
|
|
1708
1842
|
taxedPricePortions: [],
|
|
1709
1843
|
perMethodTaxRate: [],
|
|
1710
|
-
totalPrice
|
|
1844
|
+
totalPrice,
|
|
1711
1845
|
variant: {
|
|
1712
1846
|
id: variant.id,
|
|
1713
1847
|
sku: variant.sku,
|
|
@@ -1718,18 +1852,24 @@ var OrderRepository = class extends AbstractResourceRepository {
|
|
|
1718
1852
|
return lineItem;
|
|
1719
1853
|
}
|
|
1720
1854
|
customLineItemFromImportDraft(context, draft) {
|
|
1855
|
+
const quantity = draft.quantity ?? 1;
|
|
1856
|
+
const totalPrice = createCentPrecisionMoney({
|
|
1857
|
+
...draft.money,
|
|
1858
|
+
centAmount: (draft.money.centAmount ?? 0) * quantity
|
|
1859
|
+
});
|
|
1721
1860
|
const lineItem = {
|
|
1722
1861
|
...getBaseResourceProperties(),
|
|
1723
1862
|
custom: createCustomFields(draft.custom, context.projectKey, this._storage),
|
|
1724
1863
|
discountedPricePerQuantity: [],
|
|
1725
1864
|
money: createTypedMoney(draft.money),
|
|
1726
1865
|
name: draft.name,
|
|
1727
|
-
quantity
|
|
1866
|
+
quantity,
|
|
1728
1867
|
perMethodTaxRate: [],
|
|
1729
1868
|
priceMode: draft.priceMode ?? "Standard",
|
|
1730
1869
|
slug: draft.slug,
|
|
1731
1870
|
state: [],
|
|
1732
|
-
totalPrice
|
|
1871
|
+
totalPrice,
|
|
1872
|
+
taxedPrice: calculateTaxedPriceFromRate(totalPrice.centAmount, totalPrice.currencyCode, draft.taxRate),
|
|
1733
1873
|
taxedPricePortions: []
|
|
1734
1874
|
};
|
|
1735
1875
|
return lineItem;
|
|
@@ -1743,6 +1883,32 @@ var OrderRepository = class extends AbstractResourceRepository {
|
|
|
1743
1883
|
if (result.count > 1) throw new Error("Duplicate order numbers");
|
|
1744
1884
|
return;
|
|
1745
1885
|
}
|
|
1886
|
+
createShippingInfo(context, resource, shippingMethodRef) {
|
|
1887
|
+
const cartLikeForMatching = {
|
|
1888
|
+
...resource,
|
|
1889
|
+
cartState: "Active",
|
|
1890
|
+
inventoryMode: "None",
|
|
1891
|
+
itemShippingAddresses: [],
|
|
1892
|
+
priceRoundingMode: resource.taxRoundingMode || "HalfEven",
|
|
1893
|
+
taxMode: resource.taxMode || "Platform",
|
|
1894
|
+
taxCalculationMode: resource.taxCalculationMode || "LineItemLevel",
|
|
1895
|
+
taxRoundingMode: resource.taxRoundingMode || "HalfEven",
|
|
1896
|
+
discountCodes: resource.discountCodes || [],
|
|
1897
|
+
directDiscounts: resource.directDiscounts || [],
|
|
1898
|
+
shippingInfo: void 0
|
|
1899
|
+
};
|
|
1900
|
+
const shippingMethods = getShippingMethodsMatchingCart(context, this._storage, cartLikeForMatching, { expand: ["zoneRates[*].zone"] });
|
|
1901
|
+
const method = shippingMethods.results.find((candidate) => candidate.id === shippingMethodRef.id);
|
|
1902
|
+
if (!method) throw new CommercetoolsError({
|
|
1903
|
+
code: "ShippingMethodDoesNotMatchCart",
|
|
1904
|
+
message: `The shipping method with ID '${shippingMethodRef.id}' is not allowed for the order with ID '${resource.id}'.`
|
|
1905
|
+
});
|
|
1906
|
+
const baseShippingInfo = createShippingInfoFromMethod(context, this._storage, resource, method);
|
|
1907
|
+
return {
|
|
1908
|
+
...baseShippingInfo,
|
|
1909
|
+
deliveries: []
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1746
1912
|
};
|
|
1747
1913
|
|
|
1748
1914
|
//#endregion
|