@labdigital/commercetools-mock 2.59.1 → 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.js CHANGED
@@ -8,8 +8,8 @@ import auth from "basic-auth";
8
8
  import bodyParser from "body-parser";
9
9
  import { randomBytes } from "node:crypto";
10
10
  import { isDeepStrictEqual } from "node:util";
11
- import { Decimal } from "decimal.js";
12
- import { Decimal as Decimal$1 } from "decimal.js/decimal";
11
+ import { Decimal } from "decimal.js/decimal";
12
+ import { Decimal as Decimal$1 } from "decimal.js";
13
13
  import assert from "node:assert";
14
14
  import { z } from "zod";
15
15
  import { fromZodError } from "zod-validation-error";
@@ -555,9 +555,9 @@ const createPrice = (draft) => ({
555
555
  */
556
556
  const roundDecimal = (decimal, roundingMode) => {
557
557
  switch (roundingMode) {
558
- case "HalfEven": return decimal.toDecimalPlaces(0, Decimal$1.ROUND_HALF_EVEN);
559
- case "HalfUp": return decimal.toDecimalPlaces(0, Decimal$1.ROUND_HALF_UP);
560
- case "HalfDown": return decimal.toDecimalPlaces(0, Decimal$1.ROUND_HALF_DOWN);
558
+ case "HalfEven": return decimal.toDecimalPlaces(0, Decimal.ROUND_HALF_EVEN);
559
+ case "HalfUp": return decimal.toDecimalPlaces(0, Decimal.ROUND_HALF_UP);
560
+ case "HalfDown": return decimal.toDecimalPlaces(0, Decimal.ROUND_HALF_DOWN);
561
561
  default: throw new Error(`Unknown rounding mode: ${roundingMode}`);
562
562
  }
563
563
  };
@@ -686,6 +686,109 @@ const getBusinessUnitKeyReference = (id, projectKey, storage) => {
686
686
  };
687
687
  };
688
688
 
689
+ //#endregion
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);
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
+ };
747
+ };
748
+ const buildTaxedPriceFromRate = (amount, currencyCode, taxRate) => {
749
+ if (!taxRate) return void 0;
750
+ const toMoney = (centAmount) => createCentPrecisionMoney({
751
+ type: "centPrecision",
752
+ currencyCode,
753
+ centAmount
754
+ });
755
+ let netAmount;
756
+ let grossAmount;
757
+ let taxAmount;
758
+ if (taxRate.includedInPrice) {
759
+ grossAmount = amount;
760
+ taxAmount = Math.round(grossAmount * taxRate.amount / (1 + taxRate.amount));
761
+ netAmount = grossAmount - taxAmount;
762
+ } else {
763
+ netAmount = amount;
764
+ taxAmount = Math.round(netAmount * taxRate.amount);
765
+ grossAmount = netAmount + taxAmount;
766
+ }
767
+ return {
768
+ totalNet: toMoney(netAmount),
769
+ totalGross: toMoney(grossAmount),
770
+ totalTax: taxAmount > 0 ? toMoney(taxAmount) : void 0,
771
+ taxPortions: taxAmount > 0 ? [{
772
+ rate: taxRate.amount,
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
+
689
792
  //#endregion
690
793
  //#region src/shipping.ts
691
794
  const markMatchingShippingRate = (cart, shippingRate) => {
@@ -774,11 +877,11 @@ const createShippingInfoFromMethod = (context, storage, resource, method) => {
774
877
  };
775
878
  const totalGross = taxRate.includedInPrice ? shippingPrice : {
776
879
  ...shippingPrice,
777
- centAmount: roundDecimal(new Decimal(shippingPrice.centAmount).mul(1 + taxRate.amount), resource.taxRoundingMode || "HalfEven").toNumber()
880
+ centAmount: roundDecimal(new Decimal$1(shippingPrice.centAmount).mul(1 + taxRate.amount), resource.taxRoundingMode || "HalfEven").toNumber()
778
881
  };
779
882
  const totalNet = taxRate.includedInPrice ? {
780
883
  ...shippingPrice,
781
- centAmount: roundDecimal(new Decimal(shippingPrice.centAmount).div(1 + taxRate.amount), resource.taxRoundingMode || "HalfEven").toNumber()
884
+ centAmount: roundDecimal(new Decimal$1(shippingPrice.centAmount).div(1 + taxRate.amount), resource.taxRoundingMode || "HalfEven").toNumber()
782
885
  } : shippingPrice;
783
886
  const taxPortions = [{
784
887
  name: taxRate.name,
@@ -829,53 +932,6 @@ const calculateCartTotalPrice = (cart) => {
829
932
  const customLineItemsTotal = cart.customLineItems.reduce((cur, item) => cur + item.totalPrice.centAmount, 0);
830
933
  return lineItemsTotal + customLineItemsTotal;
831
934
  };
832
- const calculateTaxedPrice = (amount, taxCategory, currency, country) => {
833
- if (!taxCategory || !taxCategory.rates.length) return void 0;
834
- const taxRate = taxCategory.rates.find((rate) => !rate.country || rate.country === country) || taxCategory.rates[0];
835
- if (!taxRate) return void 0;
836
- let netAmount;
837
- let grossAmount;
838
- let taxAmount;
839
- if (taxRate.includedInPrice) {
840
- grossAmount = amount;
841
- taxAmount = Math.round(grossAmount * taxRate.amount / (1 + taxRate.amount));
842
- netAmount = grossAmount - taxAmount;
843
- } else {
844
- netAmount = amount;
845
- taxAmount = Math.round(netAmount * taxRate.amount);
846
- grossAmount = netAmount + taxAmount;
847
- }
848
- return {
849
- totalNet: {
850
- type: "centPrecision",
851
- currencyCode: currency,
852
- centAmount: netAmount,
853
- fractionDigits: 2
854
- },
855
- totalGross: {
856
- type: "centPrecision",
857
- currencyCode: currency,
858
- centAmount: grossAmount,
859
- fractionDigits: 2
860
- },
861
- taxPortions: taxAmount > 0 ? [{
862
- rate: taxRate.amount,
863
- amount: {
864
- type: "centPrecision",
865
- currencyCode: currency,
866
- centAmount: taxAmount,
867
- fractionDigits: 2
868
- },
869
- name: taxRate.name
870
- }] : [],
871
- totalTax: taxAmount > 0 ? {
872
- type: "centPrecision",
873
- currencyCode: currency,
874
- centAmount: taxAmount,
875
- fractionDigits: 2
876
- } : void 0
877
- };
878
- };
879
935
  const createCustomLineItemFromDraft = (projectKey, draft, storage, country) => {
880
936
  const quantity = draft.quantity ?? 1;
881
937
  const taxCategoryRef = draft.taxCategory ? getReferenceFromResourceIdentifier(draft.taxCategory, projectKey, storage) : void 0;
@@ -1228,6 +1284,39 @@ var CartUpdateHandler = class extends AbstractUpdateHandler {
1228
1284
  };
1229
1285
  }
1230
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
+ }
1231
1320
  setLineItemShippingDetails(context, resource, { action, shippingDetails, lineItemId, lineItemKey }) {
1232
1321
  const lineItem = resource.lineItems.find((x) => lineItemId && x.id === lineItemId || lineItemKey && x.key === lineItemKey);
1233
1322
  if (!lineItem) throw new CommercetoolsError({
@@ -1360,6 +1449,9 @@ var CartRepository = class extends AbstractResourceRepository {
1360
1449
  key: draft.store.key
1361
1450
  } : void 0;
1362
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;
1363
1455
  return this.saveNew(context, resource);
1364
1456
  }
1365
1457
  getActiveCart(projectKey) {
@@ -1647,6 +1739,14 @@ var OrderRepository = class extends AbstractResourceRepository {
1647
1739
  totalPrice: cart.totalPrice,
1648
1740
  store: cart.store
1649
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;
1650
1750
  return this.saveNew(context, resource);
1651
1751
  }
1652
1752
  import(context, draft) {
@@ -1692,6 +1792,14 @@ var OrderRepository = class extends AbstractResourceRepository {
1692
1792
  id: shippingMethodRef.id
1693
1793
  });
1694
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;
1695
1803
  return this.saveNew(context, resource);
1696
1804
  }
1697
1805
  lineItemFromImportDraft(context, draft) {
@@ -1712,6 +1820,11 @@ var OrderRepository = class extends AbstractResourceRepository {
1712
1820
  else variant = product.masterData.current.variants.find((v) => v.sku === draft.variant.sku);
1713
1821
  if (!variant) throw new Error("Internal state error");
1714
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
+ });
1715
1828
  const lineItem = {
1716
1829
  ...getBaseResourceProperties(),
1717
1830
  custom: createCustomFields(draft.custom, context.projectKey, this._storage),
@@ -1722,12 +1835,13 @@ var OrderRepository = class extends AbstractResourceRepository {
1722
1835
  priceMode: "Platform",
1723
1836
  productId: product.id,
1724
1837
  productType: product.productType,
1725
- quantity: draft.quantity,
1838
+ quantity,
1726
1839
  state: draft.state || [],
1727
1840
  taxRate: draft.taxRate,
1841
+ taxedPrice: calculateTaxedPriceFromRate(totalPrice.centAmount, totalPrice.currencyCode, draft.taxRate),
1728
1842
  taxedPricePortions: [],
1729
1843
  perMethodTaxRate: [],
1730
- totalPrice: createCentPrecisionMoney(draft.price.value),
1844
+ totalPrice,
1731
1845
  variant: {
1732
1846
  id: variant.id,
1733
1847
  sku: variant.sku,
@@ -1738,18 +1852,24 @@ var OrderRepository = class extends AbstractResourceRepository {
1738
1852
  return lineItem;
1739
1853
  }
1740
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
+ });
1741
1860
  const lineItem = {
1742
1861
  ...getBaseResourceProperties(),
1743
1862
  custom: createCustomFields(draft.custom, context.projectKey, this._storage),
1744
1863
  discountedPricePerQuantity: [],
1745
1864
  money: createTypedMoney(draft.money),
1746
1865
  name: draft.name,
1747
- quantity: draft.quantity ?? 0,
1866
+ quantity,
1748
1867
  perMethodTaxRate: [],
1749
1868
  priceMode: draft.priceMode ?? "Standard",
1750
1869
  slug: draft.slug,
1751
1870
  state: [],
1752
- totalPrice: createCentPrecisionMoney(draft.money),
1871
+ totalPrice,
1872
+ taxedPrice: calculateTaxedPriceFromRate(totalPrice.centAmount, totalPrice.currencyCode, draft.taxRate),
1753
1873
  taxedPricePortions: []
1754
1874
  };
1755
1875
  return lineItem;