chore(): start moving some packages to the core directory (#7215)
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { BigNumber as BN } from "bignumber.js"
|
||||
import { BigNumber } from "../big-number"
|
||||
|
||||
describe("BigNumber", function () {
|
||||
describe("constructor", function () {
|
||||
it("should set and return number", function () {
|
||||
const number = new BigNumber(42)
|
||||
expect(JSON.stringify(number)).toEqual(JSON.stringify(42))
|
||||
})
|
||||
|
||||
it("should set BigNumber and return number", function () {
|
||||
const number = new BigNumber({
|
||||
value: "42",
|
||||
})
|
||||
expect(JSON.stringify(number)).toEqual(JSON.stringify(42))
|
||||
})
|
||||
|
||||
it("should set string and return number", function () {
|
||||
const number = new BigNumber("42")
|
||||
expect(JSON.stringify(number)).toEqual(JSON.stringify(42))
|
||||
})
|
||||
|
||||
it("should set bignumber.js and return number", function () {
|
||||
const bn = new BN("42")
|
||||
const number = new BigNumber(bn)
|
||||
expect(JSON.stringify(number)).toEqual(JSON.stringify(42))
|
||||
})
|
||||
|
||||
it("should throw if not correct type", function () {
|
||||
// @ts-ignore
|
||||
expect(() => new BigNumber([])).toThrow(
|
||||
"Invalid BigNumber value: . Should be one of: string, number, BigNumber (bignumber.js), BigNumberRawValue"
|
||||
)
|
||||
|
||||
expect(() => new BigNumber(null as any)).toThrow(
|
||||
"Invalid BigNumber value: null. Should be one of: string, number, BigNumber (bignumber.js), BigNumberRawValue"
|
||||
)
|
||||
|
||||
expect(() => new BigNumber(undefined as any)).toThrow(
|
||||
"Invalid BigNumber value: undefined. Should be one of: string, number, BigNumber (bignumber.js), BigNumberRawValue"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
import { BigNumber } from "../big-number"
|
||||
import { createRawPropertiesFromBigNumber } from "../create-raw-properties-from-bignumber"
|
||||
|
||||
describe("Create Raw properties from BigNumber", function () {
|
||||
it("should create raw properties from BigNumber properties", function () {
|
||||
const obj = {
|
||||
price: new BigNumber({
|
||||
value: "42",
|
||||
precision: 10,
|
||||
}),
|
||||
field: 111,
|
||||
metadata: {
|
||||
numeric_field: new BigNumber({
|
||||
value: "100",
|
||||
}),
|
||||
random_field: 134,
|
||||
},
|
||||
|
||||
abc: null,
|
||||
raw_abc: {
|
||||
value: "9.00000010000103991234",
|
||||
precision: 20,
|
||||
},
|
||||
}
|
||||
|
||||
createRawPropertiesFromBigNumber(obj)
|
||||
|
||||
expect(obj).toEqual(
|
||||
expect.objectContaining({
|
||||
raw_price: {
|
||||
value: "42",
|
||||
precision: 10,
|
||||
},
|
||||
field: 111,
|
||||
metadata: expect.objectContaining({
|
||||
raw_numeric_field: {
|
||||
value: "100",
|
||||
precision: 20,
|
||||
},
|
||||
random_field: 134,
|
||||
}),
|
||||
abc: null,
|
||||
raw_abc: {
|
||||
value: "9.00000010000103991234",
|
||||
precision: 20,
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should create all properties containing BigNumber properties excluding selected ones", function () {
|
||||
const obj = {
|
||||
price: new BigNumber({
|
||||
value: "42",
|
||||
precision: 10,
|
||||
}),
|
||||
field: 111,
|
||||
metadata: {
|
||||
numeric_field: new BigNumber({
|
||||
value: "100",
|
||||
}),
|
||||
random_field: 134,
|
||||
},
|
||||
}
|
||||
|
||||
createRawPropertiesFromBigNumber(obj, {
|
||||
exclude: ["metadata.numeric_field"],
|
||||
})
|
||||
|
||||
expect(obj).toEqual({
|
||||
price: new BigNumber({
|
||||
value: "42",
|
||||
precision: 10,
|
||||
}),
|
||||
raw_price: {
|
||||
value: "42",
|
||||
precision: 10,
|
||||
},
|
||||
field: 111,
|
||||
metadata: {
|
||||
numeric_field: new BigNumber({
|
||||
value: "100",
|
||||
}),
|
||||
random_field: 134,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,637 @@
|
||||
import { decorateCartTotals } from "../../totals"
|
||||
|
||||
describe("Total calculation", function () {
|
||||
it("should calculate carts with items + taxes", function () {
|
||||
const cart = {
|
||||
items: [
|
||||
{
|
||||
unit_price: 30,
|
||||
quantity: 2,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
unit_price: 5,
|
||||
quantity: 1,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(decorateCartTotals(cart)))
|
||||
expect(serialized).toEqual({
|
||||
items: [
|
||||
{
|
||||
unit_price: 30,
|
||||
quantity: 2,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 6,
|
||||
subtotal: 6,
|
||||
},
|
||||
],
|
||||
subtotal: 60,
|
||||
total: 66,
|
||||
original_total: 66,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
tax_total: 6,
|
||||
original_tax_total: 6,
|
||||
},
|
||||
{
|
||||
unit_price: 5,
|
||||
quantity: 1,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 50,
|
||||
total: 2.5,
|
||||
subtotal: 2.5,
|
||||
},
|
||||
],
|
||||
subtotal: 5,
|
||||
total: 7.5,
|
||||
original_total: 7.5,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
tax_total: 2.5,
|
||||
original_tax_total: 2.5,
|
||||
},
|
||||
],
|
||||
total: 73.5,
|
||||
subtotal: 65,
|
||||
tax_total: 8.5,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
item_total: 73.5,
|
||||
item_subtotal: 65,
|
||||
item_tax_total: 8.5,
|
||||
original_total: 73.5,
|
||||
original_tax_total: 8.5,
|
||||
original_item_subtotal: 65,
|
||||
original_item_total: 73.5,
|
||||
original_item_tax_total: 8.5,
|
||||
})
|
||||
})
|
||||
|
||||
it("should calculate carts with items + taxes + adjustments", function () {
|
||||
const cart = {
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(decorateCartTotals(cart)))
|
||||
|
||||
expect(serialized).toEqual({
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 9,
|
||||
subtotal: 10,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 10,
|
||||
total: 11,
|
||||
subtotal: 10,
|
||||
},
|
||||
],
|
||||
subtotal: 100,
|
||||
total: 99,
|
||||
original_total: 110,
|
||||
discount_total: 10,
|
||||
discount_tax_total: 1,
|
||||
tax_total: 9,
|
||||
original_tax_total: 10,
|
||||
},
|
||||
],
|
||||
total: 99,
|
||||
subtotal: 100,
|
||||
tax_total: 9,
|
||||
discount_total: 10,
|
||||
discount_tax_total: 1,
|
||||
original_total: 100,
|
||||
original_tax_total: 10,
|
||||
item_total: 99,
|
||||
item_subtotal: 100,
|
||||
item_tax_total: 9,
|
||||
original_item_total: 110,
|
||||
original_item_subtotal: 100,
|
||||
original_item_tax_total: 10,
|
||||
})
|
||||
})
|
||||
|
||||
it("should calculate carts with shipping_methods + items + taxes + discounts with is_tax_inclusive", function () {
|
||||
const cartMixed = {
|
||||
items: [
|
||||
{
|
||||
unit_price: 100,
|
||||
quantity: 1,
|
||||
is_tax_inclusive: true,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
unit_price: 10,
|
||||
quantity: 1,
|
||||
is_tax_inclusive: false,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
amount: 10,
|
||||
is_tax_inclusive: true,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 5,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
amount: 5,
|
||||
is_tax_inclusive: false,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const serializedMixed = JSON.parse(
|
||||
JSON.stringify(decorateCartTotals(cartMixed))
|
||||
)
|
||||
|
||||
expect(serializedMixed).toEqual({
|
||||
items: [
|
||||
{
|
||||
unit_price: 100,
|
||||
quantity: 1,
|
||||
is_tax_inclusive: true,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 8.181818181818182,
|
||||
subtotal: 9.090909090909092,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 10,
|
||||
subtotal: 9.090909090909092,
|
||||
total: 10,
|
||||
},
|
||||
],
|
||||
subtotal: 90.9090909090909,
|
||||
total: 90,
|
||||
original_total: 100,
|
||||
discount_total: 10,
|
||||
discount_tax_total: 1,
|
||||
tax_total: 8.181818181818182,
|
||||
original_tax_total: 9.090909090909092,
|
||||
},
|
||||
{
|
||||
unit_price: 10,
|
||||
quantity: 1,
|
||||
is_tax_inclusive: false,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 0.7,
|
||||
subtotal: 1,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 3,
|
||||
subtotal: 3,
|
||||
total: 3.3,
|
||||
},
|
||||
],
|
||||
subtotal: 10,
|
||||
total: 7.7,
|
||||
original_total: 11,
|
||||
discount_total: 3,
|
||||
discount_tax_total: 0.3,
|
||||
tax_total: 0.7,
|
||||
original_tax_total: 1,
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
amount: 10,
|
||||
is_tax_inclusive: true,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 5,
|
||||
total: 0.38095238095238093,
|
||||
subtotal: 0.47619047619047616,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 2,
|
||||
subtotal: 1.9047619047619047,
|
||||
total: 2,
|
||||
},
|
||||
],
|
||||
subtotal: 10.380952380952381,
|
||||
total: 8,
|
||||
original_total: 10,
|
||||
discount_total: 2,
|
||||
discount_tax_total: 0.1,
|
||||
tax_total: 0.38095238095238093,
|
||||
original_tax_total: 0.47619047619047616,
|
||||
},
|
||||
{
|
||||
amount: 5,
|
||||
is_tax_inclusive: false,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 0.3,
|
||||
subtotal: 0.5,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 2,
|
||||
subtotal: 2,
|
||||
total: 2.2,
|
||||
},
|
||||
],
|
||||
subtotal: 5,
|
||||
total: 3.3,
|
||||
original_total: 5.5,
|
||||
discount_total: 2,
|
||||
discount_tax_total: 0.2,
|
||||
tax_total: 0.3,
|
||||
original_tax_total: 0.5,
|
||||
},
|
||||
],
|
||||
total: 104.77186147186147,
|
||||
subtotal: 100.9090909090909,
|
||||
tax_total: 9.562770562770563,
|
||||
discount_total: 17,
|
||||
discount_tax_total: 1.6,
|
||||
original_total: 110.47619047619048,
|
||||
original_tax_total: 11.067099567099566,
|
||||
item_total: 97.7,
|
||||
item_subtotal: 100.9090909090909,
|
||||
item_tax_total: 8.881818181818181,
|
||||
original_item_total: 111,
|
||||
original_item_subtotal: 100.9090909090909,
|
||||
original_item_tax_total: 10.090909090909092,
|
||||
shipping_total: 11.3,
|
||||
shipping_subtotal: 15.380952380952381,
|
||||
shipping_tax_total: 0.680952380952381,
|
||||
original_shipping_tax_total: 0.9761904761904762,
|
||||
original_shipping_tax_subtotal: 15.380952380952381,
|
||||
original_shipping_total: 15.5,
|
||||
})
|
||||
})
|
||||
|
||||
it("should calculate carts with items + taxes with is_tax_inclusive", function () {
|
||||
const cartWithTax = {
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
is_tax_inclusive: true,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const cartWithoutTax = {
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
is_tax_inclusive: false,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const cartMixed = {
|
||||
items: [...cartWithTax.items, ...cartWithoutTax.items],
|
||||
}
|
||||
|
||||
const serializedWith = JSON.parse(
|
||||
JSON.stringify(decorateCartTotals(cartWithTax))
|
||||
)
|
||||
const serializedWithout = JSON.parse(
|
||||
JSON.stringify(decorateCartTotals(cartWithoutTax))
|
||||
)
|
||||
const serializedMixed = JSON.parse(
|
||||
JSON.stringify(decorateCartTotals(cartMixed))
|
||||
)
|
||||
|
||||
expect(serializedWith).toEqual({
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
is_tax_inclusive: true,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 9.090909090909092,
|
||||
subtotal: 9.090909090909092,
|
||||
},
|
||||
],
|
||||
subtotal: 90.9090909090909,
|
||||
total: 100,
|
||||
original_total: 100,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
tax_total: 9.090909090909092,
|
||||
original_tax_total: 9.090909090909092,
|
||||
},
|
||||
],
|
||||
total: 100,
|
||||
subtotal: 90.9090909090909,
|
||||
tax_total: 9.090909090909092,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
original_total: 100,
|
||||
original_tax_total: 9.090909090909092,
|
||||
item_total: 100,
|
||||
item_subtotal: 90.9090909090909,
|
||||
item_tax_total: 9.090909090909092,
|
||||
original_item_total: 100,
|
||||
original_item_subtotal: 90.9090909090909,
|
||||
original_item_tax_total: 9.090909090909092,
|
||||
})
|
||||
|
||||
expect(serializedWithout).toEqual({
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
is_tax_inclusive: false,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 10,
|
||||
subtotal: 10,
|
||||
},
|
||||
],
|
||||
subtotal: 100,
|
||||
total: 110,
|
||||
original_total: 110,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
tax_total: 10,
|
||||
original_tax_total: 10,
|
||||
},
|
||||
],
|
||||
total: 110,
|
||||
subtotal: 100,
|
||||
tax_total: 10,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
original_total: 110,
|
||||
original_tax_total: 10,
|
||||
item_total: 110,
|
||||
item_subtotal: 100,
|
||||
item_tax_total: 10,
|
||||
original_item_total: 110,
|
||||
original_item_subtotal: 100,
|
||||
original_item_tax_total: 10,
|
||||
})
|
||||
|
||||
expect(serializedMixed).toEqual({
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
is_tax_inclusive: true,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 9.090909090909092,
|
||||
subtotal: 9.090909090909092,
|
||||
},
|
||||
],
|
||||
subtotal: 90.9090909090909,
|
||||
total: 100,
|
||||
original_total: 100,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
tax_total: 9.090909090909092,
|
||||
original_tax_total: 9.090909090909092,
|
||||
},
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
is_tax_inclusive: false,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 10,
|
||||
subtotal: 10,
|
||||
},
|
||||
],
|
||||
subtotal: 100,
|
||||
total: 110,
|
||||
original_total: 110,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
tax_total: 10,
|
||||
original_tax_total: 10,
|
||||
},
|
||||
],
|
||||
total: 210,
|
||||
subtotal: 190.9090909090909,
|
||||
tax_total: 19.09090909090909,
|
||||
discount_total: 0,
|
||||
discount_tax_total: 0,
|
||||
original_total: 210,
|
||||
original_tax_total: 19.09090909090909,
|
||||
item_total: 210,
|
||||
item_subtotal: 190.9090909090909,
|
||||
item_tax_total: 19.09090909090909,
|
||||
original_item_total: 210,
|
||||
original_item_subtotal: 190.9090909090909,
|
||||
original_item_tax_total: 19.09090909090909,
|
||||
})
|
||||
})
|
||||
|
||||
it("should calculate carts with items + taxes + adjustments + shipping methods", function () {
|
||||
const cart = {
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 20,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
amount: 25,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(decorateCartTotals(cart)))
|
||||
|
||||
expect(serialized).toEqual({
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 8,
|
||||
subtotal: 10,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 20,
|
||||
total: 22,
|
||||
subtotal: 20,
|
||||
},
|
||||
],
|
||||
subtotal: 100,
|
||||
total: 88,
|
||||
original_total: 110,
|
||||
discount_total: 20,
|
||||
discount_tax_total: 2,
|
||||
tax_total: 8,
|
||||
original_tax_total: 10,
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
amount: 25,
|
||||
tax_lines: [
|
||||
{
|
||||
rate: 10,
|
||||
total: 2.3,
|
||||
subtotal: 2.5,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
amount: 2,
|
||||
total: 2.2,
|
||||
subtotal: 2,
|
||||
},
|
||||
],
|
||||
subtotal: 25,
|
||||
total: 25.3,
|
||||
original_total: 27.5,
|
||||
discount_total: 2,
|
||||
discount_tax_total: 0.2,
|
||||
tax_total: 2.3,
|
||||
original_tax_total: 2.5,
|
||||
},
|
||||
],
|
||||
total: 113.6,
|
||||
subtotal: 100,
|
||||
tax_total: 10.3,
|
||||
discount_total: 22,
|
||||
discount_tax_total: 2.2,
|
||||
original_total: 118,
|
||||
original_tax_total: 12.5,
|
||||
item_total: 88,
|
||||
item_subtotal: 100,
|
||||
item_tax_total: 8,
|
||||
original_item_total: 110,
|
||||
original_item_subtotal: 100,
|
||||
original_item_tax_total: 10,
|
||||
shipping_total: 25.3,
|
||||
shipping_subtotal: 25,
|
||||
shipping_tax_total: 2.3,
|
||||
original_shipping_tax_total: 2.5,
|
||||
original_shipping_tax_subtotal: 25,
|
||||
original_shipping_total: 27.5,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,130 @@
|
||||
import { BigNumber } from "../big-number"
|
||||
import { transformPropertiesToBigNumber } from "../transform-properties-to-bignumber"
|
||||
|
||||
describe("Transfor Properties to BigNumber", function () {
|
||||
it("should transform all properties containing matching prefix _raw to BigNumber", function () {
|
||||
const obj = {
|
||||
price: 42,
|
||||
raw_price: {
|
||||
value: "42",
|
||||
precision: 10,
|
||||
},
|
||||
field: 111,
|
||||
metadata: {
|
||||
numeric_field: 100,
|
||||
raw_numeric_field: {
|
||||
value: "100",
|
||||
},
|
||||
random_field: 134,
|
||||
},
|
||||
|
||||
abc: null,
|
||||
raw_abc: {
|
||||
value: "9.00000010000103991234",
|
||||
precision: 20,
|
||||
},
|
||||
}
|
||||
|
||||
transformPropertiesToBigNumber(obj)
|
||||
|
||||
const price = obj.price as unknown as BigNumber
|
||||
expect(price).toBeInstanceOf(BigNumber)
|
||||
expect(price.numeric).toEqual(42)
|
||||
expect(price.raw).toEqual({
|
||||
value: "42",
|
||||
precision: 10,
|
||||
})
|
||||
|
||||
expect(obj.field).toBe(111)
|
||||
|
||||
const metaNum = obj.metadata.numeric_field as unknown as BigNumber
|
||||
expect(metaNum).toBeInstanceOf(BigNumber)
|
||||
expect(metaNum.numeric).toEqual(100)
|
||||
expect(metaNum.raw).toEqual({
|
||||
value: "100",
|
||||
precision: 20,
|
||||
})
|
||||
expect(obj.metadata.random_field).toBe(134)
|
||||
|
||||
const abc = obj.abc as unknown as BigNumber
|
||||
expect(abc).toBeInstanceOf(BigNumber)
|
||||
expect(abc.numeric).toEqual(9.00000010000104)
|
||||
expect(abc.raw).toEqual({
|
||||
value: "9.00000010000103991234",
|
||||
precision: 20,
|
||||
})
|
||||
})
|
||||
|
||||
it("should transform all properties on the option 'include' to BigNumber", function () {
|
||||
const obj = {
|
||||
price: 42,
|
||||
raw_price: {
|
||||
value: "42",
|
||||
precision: 10,
|
||||
},
|
||||
field: 111,
|
||||
metadata: {
|
||||
random_field: 134,
|
||||
},
|
||||
}
|
||||
|
||||
transformPropertiesToBigNumber(obj, {
|
||||
include: ["metadata.random_field"],
|
||||
})
|
||||
|
||||
expect(obj.price).toBeInstanceOf(BigNumber)
|
||||
|
||||
const price = obj.price as unknown as BigNumber
|
||||
expect(price.numeric).toEqual(42)
|
||||
expect(price.raw).toEqual({
|
||||
value: "42",
|
||||
precision: 10,
|
||||
})
|
||||
|
||||
expect(obj.field).toBe(111)
|
||||
|
||||
const metaNum = obj.metadata.random_field as unknown as BigNumber
|
||||
expect(metaNum).toBeInstanceOf(BigNumber)
|
||||
expect(metaNum.numeric).toEqual(134)
|
||||
expect(metaNum.raw).toEqual({
|
||||
value: "134.00000000000000000",
|
||||
precision: 20,
|
||||
})
|
||||
})
|
||||
|
||||
it("should transform all properties containing matching prefix _raw to BigNumber excluding selected ones", function () {
|
||||
const obj = {
|
||||
price: 42,
|
||||
raw_price: {
|
||||
value: "42",
|
||||
precision: 10,
|
||||
},
|
||||
metadata: {
|
||||
numeric_field: 100,
|
||||
raw_numeric_field: {
|
||||
value: "100",
|
||||
},
|
||||
},
|
||||
|
||||
abc: null,
|
||||
raw_abc: {
|
||||
value: "9.00000010000103991234",
|
||||
precision: 20,
|
||||
},
|
||||
}
|
||||
|
||||
transformPropertiesToBigNumber(obj, {
|
||||
exclude: ["abc", "metadata.numeric_field"],
|
||||
})
|
||||
|
||||
const price = obj.price as unknown as BigNumber
|
||||
expect(obj.price).toBeInstanceOf(BigNumber)
|
||||
expect(price.numeric).toEqual(42)
|
||||
expect(price.raw).toEqual({
|
||||
value: "42",
|
||||
precision: 10,
|
||||
})
|
||||
|
||||
expect(obj.abc).toEqual(null)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
import { AdjustmentLineDTO, BigNumberInput } from "@medusajs/types"
|
||||
import { isDefined } from "../../common"
|
||||
import { BigNumber } from "../big-number"
|
||||
import { MathBN } from "../math"
|
||||
|
||||
export function calculateAdjustmentTotal({
|
||||
adjustments,
|
||||
includesTax,
|
||||
taxRate,
|
||||
}: {
|
||||
adjustments: Pick<AdjustmentLineDTO, "amount">[]
|
||||
includesTax?: boolean
|
||||
taxRate?: BigNumberInput
|
||||
}) {
|
||||
let total = MathBN.convert(0)
|
||||
for (const adj of adjustments) {
|
||||
if (!isDefined(adj.amount)) {
|
||||
continue
|
||||
}
|
||||
|
||||
total = MathBN.add(total, adj.amount)
|
||||
|
||||
if (isDefined(taxRate)) {
|
||||
const rate = MathBN.div(taxRate, 100)
|
||||
let taxAmount = MathBN.mult(adj.amount, rate)
|
||||
|
||||
if (includesTax) {
|
||||
taxAmount = MathBN.div(taxAmount, MathBN.add(1, rate))
|
||||
|
||||
adj["subtotal"] = new BigNumber(MathBN.sub(adj.amount, taxAmount))
|
||||
adj["total"] = new BigNumber(adj.amount)
|
||||
} else {
|
||||
adj["subtotal"] = new BigNumber(adj.amount)
|
||||
adj["total"] = new BigNumber(MathBN.add(adj.amount, taxAmount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { BigNumberInput, BigNumberRawValue } from "@medusajs/types"
|
||||
import { BigNumber as BigNumberJS } from "bignumber.js"
|
||||
import { isBigNumber, isString } from "../common"
|
||||
|
||||
export class BigNumber {
|
||||
static DEFAULT_PRECISION = 20
|
||||
|
||||
private numeric_: number
|
||||
private raw_?: BigNumberRawValue
|
||||
private bignumber_?: BigNumberJS
|
||||
|
||||
constructor(
|
||||
rawValue: BigNumberInput | BigNumber,
|
||||
options?: { precision?: number }
|
||||
) {
|
||||
this.setRawValueOrThrow(rawValue, options)
|
||||
}
|
||||
|
||||
setRawValueOrThrow(
|
||||
rawValue: BigNumberInput | BigNumber,
|
||||
{ precision }: { precision?: number } = {}
|
||||
) {
|
||||
precision ??= BigNumber.DEFAULT_PRECISION
|
||||
|
||||
if (rawValue instanceof BigNumber) {
|
||||
Object.assign(this, rawValue)
|
||||
} else if (BigNumberJS.isBigNumber(rawValue)) {
|
||||
/**
|
||||
* Example:
|
||||
* const bnUnitValue = new BigNumberJS("10.99")
|
||||
* const unitValue = new BigNumber(bnUnitValue)
|
||||
*/
|
||||
this.numeric_ = rawValue.toNumber()
|
||||
this.raw_ = {
|
||||
value: rawValue.toPrecision(precision),
|
||||
precision,
|
||||
}
|
||||
this.bignumber_ = rawValue
|
||||
} else if (isString(rawValue)) {
|
||||
/**
|
||||
* Example: const unitValue = "1234.1234"
|
||||
*/
|
||||
const bigNum = new BigNumberJS(rawValue)
|
||||
|
||||
this.numeric_ = bigNum.toNumber()
|
||||
this.raw_ = this.raw_ = {
|
||||
value: bigNum.toPrecision(precision),
|
||||
precision,
|
||||
}
|
||||
this.bignumber_ = bigNum
|
||||
} else if (isBigNumber(rawValue)) {
|
||||
/**
|
||||
* Example: const unitValue = { value: "1234.1234" }
|
||||
*/
|
||||
const definedPrecision = rawValue.precision ?? precision
|
||||
const bigNum = new BigNumberJS(rawValue.value)
|
||||
this.numeric_ = bigNum.toNumber()
|
||||
this.raw_ = {
|
||||
...rawValue,
|
||||
precision: definedPrecision,
|
||||
}
|
||||
this.bignumber_ = bigNum
|
||||
} else if (typeof rawValue === `number` && !Number.isNaN(rawValue)) {
|
||||
/**
|
||||
* Example: const unitValue = 1234
|
||||
*/
|
||||
this.numeric_ = rawValue as number
|
||||
|
||||
const bigNum = new BigNumberJS(rawValue as number)
|
||||
this.raw_ = {
|
||||
value: bigNum.toPrecision(precision),
|
||||
precision,
|
||||
}
|
||||
this.bignumber_ = bigNum
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid BigNumber value: ${rawValue}. Should be one of: string, number, BigNumber (bignumber.js), BigNumberRawValue`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get numeric(): number {
|
||||
let raw = this.raw_ as BigNumberRawValue
|
||||
if (raw) {
|
||||
return new BigNumberJS(raw.value).toNumber()
|
||||
} else {
|
||||
return this.numeric_
|
||||
}
|
||||
}
|
||||
|
||||
set numeric(value: BigNumberInput) {
|
||||
const newValue = new BigNumber(value)
|
||||
this.numeric_ = newValue.numeric_
|
||||
this.raw_ = newValue.raw_
|
||||
this.bignumber_ = newValue.bignumber_
|
||||
}
|
||||
|
||||
get raw(): BigNumberRawValue | undefined {
|
||||
return this.raw_
|
||||
}
|
||||
|
||||
get bigNumber(): BigNumberJS | undefined {
|
||||
return this.bignumber_
|
||||
}
|
||||
|
||||
set raw(rawValue: BigNumberInput) {
|
||||
const newValue = new BigNumber(rawValue)
|
||||
this.numeric_ = newValue.numeric_
|
||||
this.raw_ = newValue.raw_
|
||||
this.bignumber_ = newValue.bignumber_
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this.bignumber_
|
||||
? this.bignumber_?.toNumber()
|
||||
: this.raw_
|
||||
? new BigNumberJS(this.raw_.value).toNumber()
|
||||
: this.numeric_
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return this.numeric_
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import { BigNumberInput, CartLikeWithTotals } from "@medusajs/types"
|
||||
import { BigNumber } from "../big-number"
|
||||
import { GetItemTotalInput, getLineItemsTotals } from "../line-item"
|
||||
import { MathBN } from "../math"
|
||||
import {
|
||||
GetShippingMethodTotalInput,
|
||||
getShippingMethodsTotals,
|
||||
} from "../shipping-method"
|
||||
import { transformPropertiesToBigNumber } from "../transform-properties-to-bignumber"
|
||||
|
||||
interface TotalsConfig {
|
||||
includeTaxes?: boolean
|
||||
}
|
||||
|
||||
export interface DecorateCartLikeInputDTO {
|
||||
items?: {
|
||||
id?: string
|
||||
unit_price: BigNumberInput
|
||||
quantity: BigNumberInput
|
||||
adjustments?: { amount: BigNumberInput }[]
|
||||
tax_lines?: {
|
||||
rate: BigNumberInput
|
||||
is_tax_inclusive?: boolean
|
||||
}[]
|
||||
}[]
|
||||
shipping_methods?: {
|
||||
id?: string
|
||||
amount: BigNumberInput
|
||||
adjustments?: { amount: BigNumberInput }[]
|
||||
tax_lines?: {
|
||||
rate: BigNumberInput
|
||||
is_tax_inclusive?: boolean
|
||||
}[]
|
||||
}[]
|
||||
region?: {
|
||||
automatic_taxes?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export function decorateCartTotals(
|
||||
cartLike: DecorateCartLikeInputDTO,
|
||||
config: TotalsConfig = {}
|
||||
): CartLikeWithTotals {
|
||||
transformPropertiesToBigNumber(cartLike)
|
||||
|
||||
const items = (cartLike.items ?? []) as unknown as GetItemTotalInput[]
|
||||
const shippingMethods = (cartLike.shipping_methods ??
|
||||
[]) as unknown as GetShippingMethodTotalInput[]
|
||||
|
||||
const includeTax = config?.includeTaxes || cartLike.region?.automatic_taxes
|
||||
|
||||
const itemsTotals = getLineItemsTotals(items, {
|
||||
includeTax,
|
||||
})
|
||||
|
||||
const shippingMethodsTotals = getShippingMethodsTotals(shippingMethods, {
|
||||
includeTax,
|
||||
})
|
||||
|
||||
let subtotal = MathBN.convert(0)
|
||||
|
||||
let discountTotal = MathBN.convert(0)
|
||||
let discountTaxTotal = MathBN.convert(0)
|
||||
|
||||
let itemsSubtotal = MathBN.convert(0)
|
||||
let itemsTotal = MathBN.convert(0)
|
||||
|
||||
let itemsOriginalTotal = MathBN.convert(0)
|
||||
let itemsOriginalSubtotal = MathBN.convert(0)
|
||||
|
||||
let itemsTaxTotal = MathBN.convert(0)
|
||||
|
||||
let itemsOriginalTaxTotal = MathBN.convert(0)
|
||||
|
||||
let shippingSubtotal = MathBN.convert(0)
|
||||
let shippingTotal = MathBN.convert(0)
|
||||
|
||||
let shippingOriginalTotal = MathBN.convert(0)
|
||||
let shippingOriginalSubtotal = MathBN.convert(0)
|
||||
|
||||
let shippingTaxTotal = MathBN.convert(0)
|
||||
let shippingTaxSubTotal = MathBN.convert(0)
|
||||
|
||||
let shippingOriginalTaxTotal = MathBN.convert(0)
|
||||
let shippingOriginalTaxSubtotal = MathBN.convert(0)
|
||||
|
||||
const cartItems = items.map((item, index) => {
|
||||
const itemTotals = Object.assign(item, itemsTotals[item.id ?? index] ?? {})
|
||||
|
||||
const itemSubtotal = itemTotals.subtotal
|
||||
|
||||
const itemTotal = MathBN.convert(itemTotals.total)
|
||||
const itemOriginalTotal = MathBN.convert(itemTotals.original_total)
|
||||
|
||||
const itemTaxTotal = MathBN.convert(itemTotals.tax_total)
|
||||
const itemOriginalTaxTotal = MathBN.convert(itemTotals.original_tax_total)
|
||||
|
||||
const itemDiscountTotal = MathBN.convert(itemTotals.discount_total)
|
||||
|
||||
const itemDiscountTaxTotal = MathBN.convert(itemTotals.discount_tax_total)
|
||||
|
||||
subtotal = MathBN.add(subtotal, itemSubtotal)
|
||||
|
||||
discountTotal = MathBN.add(discountTotal, itemDiscountTotal)
|
||||
discountTaxTotal = MathBN.add(discountTaxTotal, itemDiscountTaxTotal)
|
||||
|
||||
itemsTotal = MathBN.add(itemsTotal, itemTotal)
|
||||
itemsOriginalTotal = MathBN.add(itemsOriginalTotal, itemOriginalTotal)
|
||||
itemsOriginalSubtotal = MathBN.add(itemsOriginalSubtotal, itemSubtotal)
|
||||
|
||||
itemsSubtotal = MathBN.add(itemsSubtotal, itemSubtotal)
|
||||
|
||||
itemsTaxTotal = MathBN.add(itemsTaxTotal, itemTaxTotal)
|
||||
|
||||
itemsOriginalTaxTotal = MathBN.add(
|
||||
itemsOriginalTaxTotal,
|
||||
itemOriginalTaxTotal
|
||||
)
|
||||
|
||||
return itemTotals
|
||||
})
|
||||
|
||||
const cartShippingMethods = shippingMethods.map((shippingMethod, index) => {
|
||||
const methodTotals = Object.assign(
|
||||
shippingMethod,
|
||||
shippingMethodsTotals[shippingMethod.id ?? index] ?? {}
|
||||
)
|
||||
|
||||
const methodSubtotal = MathBN.convert(methodTotals.subtotal)
|
||||
|
||||
const methodTotal = MathBN.convert(methodTotals.total)
|
||||
const methodOriginalTotal = MathBN.convert(methodTotals.original_total)
|
||||
const methodTaxTotal = MathBN.convert(methodTotals.tax_total)
|
||||
const methodOriginalTaxTotal = MathBN.convert(
|
||||
methodTotals.original_tax_total
|
||||
)
|
||||
|
||||
const methodDiscountTotal = MathBN.convert(methodTotals.discount_total)
|
||||
const methodDiscountTaxTotal = MathBN.convert(
|
||||
methodTotals.discount_tax_total
|
||||
)
|
||||
|
||||
shippingSubtotal = MathBN.add(shippingSubtotal, methodSubtotal)
|
||||
shippingTotal = MathBN.add(shippingTotal, methodTotal)
|
||||
shippingOriginalTotal = MathBN.add(
|
||||
shippingOriginalTotal,
|
||||
methodOriginalTotal
|
||||
)
|
||||
shippingOriginalSubtotal = MathBN.add(
|
||||
shippingOriginalSubtotal,
|
||||
methodSubtotal
|
||||
)
|
||||
|
||||
shippingTaxTotal = MathBN.add(shippingTaxTotal, methodTaxTotal)
|
||||
shippingOriginalTaxTotal = MathBN.add(
|
||||
shippingOriginalTaxTotal,
|
||||
methodOriginalTaxTotal
|
||||
)
|
||||
shippingOriginalTaxSubtotal = MathBN.add(
|
||||
shippingOriginalTaxSubtotal,
|
||||
methodSubtotal
|
||||
)
|
||||
|
||||
discountTotal = MathBN.add(discountTotal, methodDiscountTotal)
|
||||
discountTaxTotal = MathBN.add(discountTaxTotal, methodDiscountTaxTotal)
|
||||
|
||||
return methodTotals
|
||||
})
|
||||
|
||||
const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal)
|
||||
|
||||
const originalTaxTotal = MathBN.add(
|
||||
itemsOriginalTaxTotal,
|
||||
shippingOriginalTaxTotal
|
||||
)
|
||||
|
||||
// TODO: Gift Card calculations
|
||||
|
||||
const originalTempTotal = MathBN.add(
|
||||
subtotal,
|
||||
shippingOriginalTotal,
|
||||
originalTaxTotal
|
||||
)
|
||||
const originalTotal = MathBN.sub(originalTempTotal, discountTotal)
|
||||
// TODO: subtract (cart.gift_card_total + cart.gift_card_tax_total)
|
||||
const tempTotal = MathBN.add(subtotal, shippingTotal, taxTotal)
|
||||
const total = MathBN.sub(tempTotal, discountTotal)
|
||||
|
||||
const cart = cartLike as any
|
||||
|
||||
cart.total = new BigNumber(total)
|
||||
cart.subtotal = new BigNumber(subtotal)
|
||||
cart.tax_total = new BigNumber(taxTotal)
|
||||
|
||||
cart.discount_total = new BigNumber(discountTotal)
|
||||
cart.discount_tax_total = new BigNumber(discountTaxTotal)
|
||||
|
||||
// cart.gift_card_total = giftCardTotal.total || 0
|
||||
// cart.gift_card_tax_total = giftCardTotal.tax_total || 0
|
||||
|
||||
cart.original_total = new BigNumber(originalTotal)
|
||||
cart.original_tax_total = new BigNumber(originalTaxTotal)
|
||||
|
||||
// cart.original_gift_card_total =
|
||||
// cart.original_gift_card_tax_total =
|
||||
|
||||
if (cartLike.items) {
|
||||
cart.items = cartItems
|
||||
cart.item_total = new BigNumber(itemsTotal)
|
||||
cart.item_subtotal = new BigNumber(itemsSubtotal)
|
||||
cart.item_tax_total = new BigNumber(itemsTaxTotal)
|
||||
|
||||
cart.original_item_total = new BigNumber(itemsOriginalTotal)
|
||||
cart.original_item_subtotal = new BigNumber(itemsOriginalSubtotal)
|
||||
cart.original_item_tax_total = new BigNumber(itemsOriginalTaxTotal)
|
||||
}
|
||||
|
||||
if (cart.shipping_methods) {
|
||||
cart.shipping_methods = cartShippingMethods
|
||||
cart.shipping_total = new BigNumber(shippingTotal)
|
||||
cart.shipping_subtotal = new BigNumber(shippingSubtotal)
|
||||
cart.shipping_tax_total = new BigNumber(shippingTaxTotal)
|
||||
|
||||
cart.original_shipping_tax_total = new BigNumber(shippingOriginalTaxTotal)
|
||||
cart.original_shipping_tax_subtotal = new BigNumber(
|
||||
shippingOriginalTaxSubtotal
|
||||
)
|
||||
cart.original_shipping_total = new BigNumber(shippingOriginalTotal)
|
||||
}
|
||||
|
||||
return cart
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { isDefined, trimZeros } from "../common"
|
||||
import { BigNumber } from "./big-number"
|
||||
|
||||
export function createRawPropertiesFromBigNumber(
|
||||
obj,
|
||||
{
|
||||
prefix = "raw_",
|
||||
exclude = [],
|
||||
}: {
|
||||
prefix?: string
|
||||
exclude?: string[]
|
||||
} = {}
|
||||
) {
|
||||
const stack = [{ current: obj, path: "" }]
|
||||
|
||||
while (stack.length > 0) {
|
||||
const { current, path } = stack.pop()!
|
||||
|
||||
if (
|
||||
current == null ||
|
||||
typeof current !== "object" ||
|
||||
current instanceof BigNumber
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (Array.isArray(current)) {
|
||||
current.forEach((element, index) =>
|
||||
stack.push({ current: element, path })
|
||||
)
|
||||
} else {
|
||||
for (const key of Object.keys(current)) {
|
||||
const value = current[key]
|
||||
const currentPath = path ? `${path}.${key}` : key
|
||||
|
||||
if (value != null && !exclude.includes(currentPath)) {
|
||||
const isBigNumber =
|
||||
typeof value === "object" &&
|
||||
isDefined(value.raw_) &&
|
||||
isDefined(value.numeric_)
|
||||
|
||||
if (isBigNumber) {
|
||||
const newKey = prefix + key
|
||||
const newPath = path ? `${path}.${newKey}` : newKey
|
||||
if (!exclude.includes(newPath)) {
|
||||
current[newKey] = {
|
||||
...value.raw_,
|
||||
value: trimZeros(value.raw_.value),
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stack.push({ current: value, path: currentPath })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from "./cart"
|
||||
export * from "./create-raw-properties-from-bignumber"
|
||||
export * from "./line-item"
|
||||
export * from "./math"
|
||||
export * from "./promotion"
|
||||
export * from "./shipping-method"
|
||||
export * from "./transform-properties-to-bignumber"
|
||||
@@ -0,0 +1,126 @@
|
||||
import { AdjustmentLineDTO, TaxLineDTO } from "@medusajs/types"
|
||||
import { calculateAdjustmentTotal } from "../adjustment"
|
||||
import { BigNumber } from "../big-number"
|
||||
import { MathBN } from "../math"
|
||||
import { calculateTaxTotal } from "../tax"
|
||||
|
||||
interface GetLineItemsTotalsContext {
|
||||
includeTax?: boolean
|
||||
}
|
||||
|
||||
export interface GetItemTotalInput {
|
||||
id: string
|
||||
unit_price: BigNumber
|
||||
quantity: BigNumber
|
||||
is_tax_inclusive?: boolean
|
||||
tax_lines?: Pick<TaxLineDTO, "rate">[]
|
||||
adjustments?: Pick<AdjustmentLineDTO, "amount">[]
|
||||
}
|
||||
|
||||
export interface GetItemTotalOutput {
|
||||
quantity: BigNumber
|
||||
unit_price: BigNumber
|
||||
|
||||
subtotal: BigNumber
|
||||
|
||||
total: BigNumber
|
||||
original_total: BigNumber
|
||||
|
||||
discount_total: BigNumber
|
||||
discount_tax_total: BigNumber
|
||||
|
||||
tax_total: BigNumber
|
||||
original_tax_total: BigNumber
|
||||
}
|
||||
|
||||
export function getLineItemsTotals(
|
||||
items: GetItemTotalInput[],
|
||||
context: GetLineItemsTotalsContext
|
||||
) {
|
||||
const itemsTotals = {}
|
||||
|
||||
let index = 0
|
||||
for (const item of items) {
|
||||
itemsTotals[item.id ?? index] = getLineItemTotals(item, {
|
||||
includeTax: context.includeTax || item.is_tax_inclusive,
|
||||
})
|
||||
index++
|
||||
}
|
||||
|
||||
return itemsTotals
|
||||
}
|
||||
|
||||
function getLineItemTotals(
|
||||
item: GetItemTotalInput,
|
||||
context: GetLineItemsTotalsContext
|
||||
) {
|
||||
const subtotal = MathBN.mult(item.unit_price, item.quantity)
|
||||
|
||||
const sumTaxRate = MathBN.sum(
|
||||
...((item.tax_lines ?? []).map((taxLine) => taxLine.rate) ?? [])
|
||||
)
|
||||
const discountTotal = calculateAdjustmentTotal({
|
||||
adjustments: item.adjustments || [],
|
||||
includesTax: context.includeTax,
|
||||
taxRate: sumTaxRate,
|
||||
})
|
||||
const discountTaxTotal = MathBN.mult(
|
||||
discountTotal,
|
||||
MathBN.div(sumTaxRate, 100)
|
||||
)
|
||||
|
||||
const total = MathBN.sub(subtotal, discountTotal)
|
||||
|
||||
const totals: GetItemTotalOutput = {
|
||||
quantity: item.quantity,
|
||||
unit_price: item.unit_price,
|
||||
|
||||
subtotal: new BigNumber(subtotal),
|
||||
|
||||
total: new BigNumber(total),
|
||||
original_total: new BigNumber(subtotal),
|
||||
|
||||
discount_total: new BigNumber(discountTotal),
|
||||
discount_tax_total: new BigNumber(discountTaxTotal),
|
||||
|
||||
tax_total: new BigNumber(0),
|
||||
original_tax_total: new BigNumber(0),
|
||||
}
|
||||
|
||||
const taxableAmountWithDiscount = MathBN.sub(subtotal, discountTotal)
|
||||
const taxableAmount = subtotal
|
||||
|
||||
const taxTotal = calculateTaxTotal({
|
||||
taxLines: item.tax_lines || [],
|
||||
includesTax: context.includeTax,
|
||||
taxableAmount: taxableAmountWithDiscount,
|
||||
setTotalField: "total",
|
||||
})
|
||||
totals.tax_total = new BigNumber(taxTotal)
|
||||
|
||||
const originalTaxTotal = calculateTaxTotal({
|
||||
taxLines: item.tax_lines || [],
|
||||
includesTax: context.includeTax,
|
||||
taxableAmount,
|
||||
setTotalField: "subtotal",
|
||||
})
|
||||
totals.original_tax_total = new BigNumber(originalTaxTotal)
|
||||
|
||||
const isTaxInclusive = context.includeTax ?? item.is_tax_inclusive
|
||||
|
||||
if (isTaxInclusive) {
|
||||
totals.subtotal = new BigNumber(
|
||||
MathBN.sub(
|
||||
MathBN.mult(item.unit_price, totals.quantity),
|
||||
originalTaxTotal
|
||||
)
|
||||
)
|
||||
} else {
|
||||
const newTotal = MathBN.add(total, totals.tax_total)
|
||||
const originalTotal = MathBN.add(subtotal, totals.original_tax_total)
|
||||
totals.total = new BigNumber(newTotal)
|
||||
totals.original_total = new BigNumber(originalTotal)
|
||||
}
|
||||
|
||||
return totals
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { BigNumberInput, BigNumberRawValue } from "@medusajs/types"
|
||||
import { BigNumber as BigNumberJS } from "bignumber.js"
|
||||
import { isDefined } from "../common"
|
||||
import { BigNumber } from "./big-number"
|
||||
|
||||
type BNInput = BigNumberInput | BigNumber
|
||||
export class MathBN {
|
||||
static convert(num: BNInput): BigNumberJS {
|
||||
if (num == null) {
|
||||
return new BigNumberJS(0)
|
||||
}
|
||||
|
||||
if (num instanceof BigNumber) {
|
||||
return num.bigNumber!
|
||||
} else if (num instanceof BigNumberJS) {
|
||||
return num
|
||||
} else if (isDefined((num as BigNumberRawValue)?.value)) {
|
||||
return new BigNumberJS((num as BigNumberRawValue).value)
|
||||
}
|
||||
|
||||
return new BigNumberJS(num as BigNumberJS | number)
|
||||
}
|
||||
|
||||
static add(...nums: BNInput[]): BigNumberJS {
|
||||
let sum = new BigNumberJS(0)
|
||||
for (const num of nums) {
|
||||
const n = MathBN.convert(num)
|
||||
sum = sum.plus(n)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
static sum(...nums: BNInput[]): BigNumberJS {
|
||||
return MathBN.add(0, ...(nums ?? [0]))
|
||||
}
|
||||
|
||||
static sub(...nums: BNInput[]): BigNumberJS {
|
||||
let agg = MathBN.convert(nums[0])
|
||||
for (let i = 1; i < nums.length; i++) {
|
||||
const n = MathBN.convert(nums[i])
|
||||
agg = agg.minus(n)
|
||||
}
|
||||
return agg
|
||||
}
|
||||
|
||||
static mult(n1: BNInput, n2: BNInput): BigNumberJS {
|
||||
const num1 = MathBN.convert(n1)
|
||||
const num2 = MathBN.convert(n2)
|
||||
return num1.times(num2)
|
||||
}
|
||||
|
||||
static div(n1: BNInput, n2: BNInput): BigNumberJS {
|
||||
const num1 = MathBN.convert(n1)
|
||||
const num2 = MathBN.convert(n2)
|
||||
return num1.dividedBy(num2)
|
||||
}
|
||||
|
||||
static abs(n: BNInput): BigNumberJS {
|
||||
const num = MathBN.convert(n)
|
||||
return num.absoluteValue()
|
||||
}
|
||||
|
||||
static mod(n1: BNInput, n2: BNInput): BigNumberJS {
|
||||
const num1 = MathBN.convert(n1)
|
||||
const num2 = MathBN.convert(n2)
|
||||
return num1.modulo(num2)
|
||||
}
|
||||
|
||||
static exp(n: BNInput, exp = 2): BigNumberJS {
|
||||
const num = MathBN.convert(n)
|
||||
const expBy = MathBN.convert(exp)
|
||||
return num.exponentiatedBy(expBy)
|
||||
}
|
||||
|
||||
static min(...nums: BNInput[]): BigNumberJS {
|
||||
return BigNumberJS.minimum(...nums.map((num) => MathBN.convert(num)))
|
||||
}
|
||||
|
||||
static max(...nums: BNInput[]): BigNumberJS {
|
||||
return BigNumberJS.maximum(...nums.map((num) => MathBN.convert(num)))
|
||||
}
|
||||
|
||||
static gt(n1: BNInput, n2: BNInput): boolean {
|
||||
const num1 = MathBN.convert(n1)
|
||||
const num2 = MathBN.convert(n2)
|
||||
return num1.isGreaterThan(num2)
|
||||
}
|
||||
|
||||
static gte(n1: BNInput, n2: BNInput): boolean {
|
||||
const num1 = MathBN.convert(n1)
|
||||
const num2 = MathBN.convert(n2)
|
||||
return num1.isGreaterThanOrEqualTo(num2)
|
||||
}
|
||||
|
||||
static lt(n1: BNInput, n2: BNInput): boolean {
|
||||
const num1 = MathBN.convert(n1)
|
||||
const num2 = MathBN.convert(n2)
|
||||
return num1.isLessThan(num2)
|
||||
}
|
||||
|
||||
static lte(n1: BNInput, n2: BNInput): boolean {
|
||||
const num1 = MathBN.convert(n1)
|
||||
const num2 = MathBN.convert(n2)
|
||||
return num1.isLessThanOrEqualTo(num2)
|
||||
}
|
||||
|
||||
static eq(n1: BNInput, n2: BNInput): boolean {
|
||||
const num1 = MathBN.convert(n1)
|
||||
const num2 = MathBN.convert(n2)
|
||||
return num1.isEqualTo(num2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
ApplicationMethodAllocation,
|
||||
ApplicationMethodType,
|
||||
} from "../../promotion"
|
||||
|
||||
function getPromotionValueForPercentage(promotion, lineItemTotal) {
|
||||
return (promotion.value / 100) * lineItemTotal
|
||||
}
|
||||
|
||||
function getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal) {
|
||||
if (promotion.allocation === ApplicationMethodAllocation.ACROSS) {
|
||||
return (lineItemTotal / lineItemsTotal) * promotion.value
|
||||
}
|
||||
|
||||
return promotion.value
|
||||
}
|
||||
|
||||
export function getPromotionValue(promotion, lineItemTotal, lineItemsTotal) {
|
||||
if (promotion.type === ApplicationMethodType.PERCENTAGE) {
|
||||
return getPromotionValueForPercentage(promotion, lineItemTotal)
|
||||
}
|
||||
|
||||
return getPromotionValueForFixed(promotion, lineItemTotal, lineItemsTotal)
|
||||
}
|
||||
|
||||
export function getApplicableQuantity(lineItem, maxQuantity) {
|
||||
if (maxQuantity && lineItem.quantity) {
|
||||
return Math.min(lineItem.quantity, maxQuantity)
|
||||
}
|
||||
|
||||
return lineItem.quantity
|
||||
}
|
||||
|
||||
function getLineItemUnitPrice(lineItem) {
|
||||
return lineItem.subtotal / lineItem.quantity
|
||||
}
|
||||
|
||||
export function calculateAdjustmentAmountFromPromotion(
|
||||
lineItem,
|
||||
promotion,
|
||||
lineItemsTotal = 0
|
||||
) {
|
||||
const quantity = getApplicableQuantity(lineItem, promotion.max_quantity)
|
||||
const lineItemTotal = getLineItemUnitPrice(lineItem) * quantity
|
||||
const applicableTotal = lineItemTotal - promotion.applied_value
|
||||
|
||||
if (applicableTotal <= 0) {
|
||||
return applicableTotal
|
||||
}
|
||||
|
||||
const promotionValue = getPromotionValue(
|
||||
promotion,
|
||||
applicableTotal,
|
||||
lineItemsTotal
|
||||
)
|
||||
|
||||
return Math.min(promotionValue, applicableTotal)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { AdjustmentLineDTO, TaxLineDTO } from "@medusajs/types"
|
||||
import { calculateAdjustmentTotal } from "../adjustment"
|
||||
import { BigNumber } from "../big-number"
|
||||
import { MathBN } from "../math"
|
||||
import { calculateTaxTotal } from "../tax"
|
||||
|
||||
interface GetShippingMethodsTotalsContext {
|
||||
includeTax?: boolean
|
||||
}
|
||||
|
||||
export interface GetShippingMethodTotalInput {
|
||||
id: string
|
||||
amount: BigNumber
|
||||
is_tax_inclusive?: boolean
|
||||
tax_lines?: TaxLineDTO[]
|
||||
adjustments?: Pick<AdjustmentLineDTO, "amount">[]
|
||||
}
|
||||
|
||||
export interface GetShippingMethodTotalOutput {
|
||||
amount: BigNumber
|
||||
|
||||
subtotal: BigNumber
|
||||
|
||||
total: BigNumber
|
||||
original_total: BigNumber
|
||||
|
||||
discount_total: BigNumber
|
||||
discount_tax_total: BigNumber
|
||||
|
||||
tax_total: BigNumber
|
||||
original_tax_total: BigNumber
|
||||
}
|
||||
|
||||
export function getShippingMethodsTotals(
|
||||
shippingMethods: GetShippingMethodTotalInput[],
|
||||
context: GetShippingMethodsTotalsContext
|
||||
) {
|
||||
const { includeTax } = context
|
||||
|
||||
const shippingMethodsTotals = {}
|
||||
|
||||
let index = 0
|
||||
for (const shippingMethod of shippingMethods) {
|
||||
shippingMethodsTotals[shippingMethod.id ?? index] = getShippingMethodTotals(
|
||||
shippingMethod,
|
||||
{
|
||||
includeTax: includeTax || shippingMethod.is_tax_inclusive,
|
||||
}
|
||||
)
|
||||
index++
|
||||
}
|
||||
|
||||
return shippingMethodsTotals
|
||||
}
|
||||
|
||||
export function getShippingMethodTotals(
|
||||
shippingMethod: GetShippingMethodTotalInput,
|
||||
context: GetShippingMethodsTotalsContext
|
||||
) {
|
||||
const amount = MathBN.convert(shippingMethod.amount)
|
||||
const subtotal = MathBN.convert(shippingMethod.amount)
|
||||
|
||||
const sumTaxRate = MathBN.sum(
|
||||
...(shippingMethod.tax_lines?.map((taxLine) => taxLine.rate) ?? [])
|
||||
)
|
||||
|
||||
const discountTotal = calculateAdjustmentTotal({
|
||||
adjustments: shippingMethod.adjustments || [],
|
||||
includesTax: context.includeTax,
|
||||
taxRate: sumTaxRate,
|
||||
})
|
||||
const discountTaxTotal = MathBN.mult(
|
||||
discountTotal,
|
||||
MathBN.div(sumTaxRate, 100)
|
||||
)
|
||||
|
||||
const total = MathBN.sub(amount, discountTotal)
|
||||
|
||||
const totals: GetShippingMethodTotalOutput = {
|
||||
amount: new BigNumber(amount),
|
||||
|
||||
subtotal: new BigNumber(subtotal),
|
||||
|
||||
total: new BigNumber(total),
|
||||
original_total: new BigNumber(amount),
|
||||
|
||||
discount_total: new BigNumber(discountTotal),
|
||||
discount_tax_total: new BigNumber(discountTaxTotal),
|
||||
|
||||
tax_total: new BigNumber(0),
|
||||
original_tax_total: new BigNumber(0),
|
||||
}
|
||||
|
||||
const taxLines = shippingMethod.tax_lines || []
|
||||
|
||||
const taxableAmountWithDiscount = MathBN.sub(subtotal, discountTotal)
|
||||
const taxableAmount = subtotal
|
||||
|
||||
const taxTotal = calculateTaxTotal({
|
||||
taxLines,
|
||||
includesTax: context.includeTax,
|
||||
taxableAmount: taxableAmountWithDiscount,
|
||||
setTotalField: "total",
|
||||
})
|
||||
totals.tax_total = new BigNumber(taxTotal)
|
||||
|
||||
const originalTaxTotal = calculateTaxTotal({
|
||||
taxLines,
|
||||
includesTax: context.includeTax,
|
||||
taxableAmount,
|
||||
setTotalField: "subtotal",
|
||||
})
|
||||
totals.original_tax_total = new BigNumber(originalTaxTotal)
|
||||
|
||||
const isTaxInclusive = context.includeTax ?? shippingMethod.is_tax_inclusive
|
||||
|
||||
if (isTaxInclusive) {
|
||||
const subtotal = MathBN.add(shippingMethod.amount, taxTotal)
|
||||
totals.subtotal = new BigNumber(subtotal)
|
||||
} else {
|
||||
const originalTotal = MathBN.add(
|
||||
shippingMethod.amount,
|
||||
totals.original_tax_total
|
||||
)
|
||||
const total = MathBN.add(totals.total, totals.tax_total)
|
||||
|
||||
totals.total = new BigNumber(total)
|
||||
totals.original_total = new BigNumber(originalTotal)
|
||||
}
|
||||
|
||||
return totals
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { BigNumberInput, TaxLineDTO } from "@medusajs/types"
|
||||
import { BigNumber } from "../big-number"
|
||||
import { MathBN } from "../math"
|
||||
|
||||
export function calculateTaxTotal({
|
||||
taxLines,
|
||||
includesTax,
|
||||
taxableAmount,
|
||||
setTotalField,
|
||||
}: {
|
||||
taxLines: Pick<TaxLineDTO, "rate">[]
|
||||
includesTax?: boolean
|
||||
taxableAmount: BigNumberInput
|
||||
setTotalField?: string
|
||||
}) {
|
||||
let taxTotal = MathBN.convert(0)
|
||||
for (const taxLine of taxLines) {
|
||||
const rate = MathBN.div(taxLine.rate, 100)
|
||||
let taxAmount = MathBN.mult(taxableAmount, rate)
|
||||
|
||||
if (includesTax) {
|
||||
taxAmount = MathBN.div(taxAmount, MathBN.add(1, rate))
|
||||
}
|
||||
|
||||
if (setTotalField) {
|
||||
;(taxLine as any)[setTotalField] = new BigNumber(taxAmount)
|
||||
}
|
||||
|
||||
taxTotal = MathBN.add(taxTotal, taxAmount)
|
||||
}
|
||||
|
||||
return taxTotal
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { BigNumber } from "./big-number"
|
||||
|
||||
export function transformPropertiesToBigNumber(
|
||||
obj,
|
||||
{
|
||||
prefix = "raw_",
|
||||
include = [],
|
||||
exclude = [],
|
||||
}: {
|
||||
prefix?: string
|
||||
include?: string[]
|
||||
exclude?: string[]
|
||||
} = {}
|
||||
) {
|
||||
const stack = [{ current: obj, path: "" }]
|
||||
|
||||
while (stack.length > 0) {
|
||||
const { current, path } = stack.pop()!
|
||||
|
||||
if (
|
||||
current == null ||
|
||||
typeof current !== "object" ||
|
||||
current instanceof BigNumber
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (Array.isArray(current)) {
|
||||
current.forEach((element, index) =>
|
||||
stack.push({ current: element, path })
|
||||
)
|
||||
} else {
|
||||
for (const key of Object.keys(current)) {
|
||||
const value = current[key]
|
||||
const currentPath = path ? `${path}.${key}` : key
|
||||
|
||||
if (value != null && !exclude.includes(currentPath)) {
|
||||
if (key.startsWith(prefix)) {
|
||||
const newKey = key.replace(prefix, "")
|
||||
|
||||
const newPath = path ? `${path}.${newKey}` : newKey
|
||||
if (!exclude.includes(newPath)) {
|
||||
current[newKey] = new BigNumber(value)
|
||||
continue
|
||||
}
|
||||
} else if (include.includes(currentPath)) {
|
||||
current[key] = new BigNumber(value)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
stack.push({ current: value, path: currentPath })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user