feat(core-flows,types,cart): add credit lines to cart (#11419)

* feat(core-flows,types,cart): add credit lines to cart

* chore: fix specs

* chore: credit lines hook

* chore: update types

* chore: added credit line totals

* chore: add totals fields to query config

* chore: add complete cart hook

* chore: add credit lines creation to order

* chore: pr ready for review

* chore: fix tests

* Apply suggestions from code review

* chore: fix types

* chore: adjust summary calculations with new totals
This commit is contained in:
Riqwan Thamir
2025-02-24 14:34:36 +01:00
committed by GitHub
parent be566ca6fb
commit fb2e86484a
27 changed files with 802 additions and 58 deletions
@@ -81,6 +81,9 @@ describe("Total calculation", function () {
original_item_subtotal: 65,
original_item_total: 73.5,
original_item_tax_total: 8.5,
credit_lines_subtotal: 0,
credit_lines_tax_total: 0,
credit_lines_total: 0,
})
})
@@ -149,6 +152,9 @@ describe("Total calculation", function () {
original_item_total: 110,
original_item_subtotal: 100,
original_item_tax_total: 10,
credit_lines_subtotal: 0,
credit_lines_tax_total: 0,
credit_lines_total: 0,
})
})
@@ -353,6 +359,9 @@ describe("Total calculation", function () {
original_shipping_tax_total: 9.9,
original_shipping_subtotal: 99,
original_shipping_total: 108.9,
credit_lines_subtotal: 0,
credit_lines_tax_total: 0,
credit_lines_total: 0,
})
})
@@ -438,6 +447,9 @@ describe("Total calculation", function () {
original_item_total: 100,
original_item_subtotal: 90.9090909090909,
original_item_tax_total: 9.090909090909092,
credit_lines_subtotal: 0,
credit_lines_tax_total: 0,
credit_lines_total: 0,
})
expect(serializedWithout).toEqual({
@@ -477,6 +489,9 @@ describe("Total calculation", function () {
original_item_total: 110,
original_item_subtotal: 100,
original_item_tax_total: 10,
credit_lines_subtotal: 0,
credit_lines_tax_total: 0,
credit_lines_total: 0,
})
expect(serializedMixed).toEqual({
@@ -536,6 +551,9 @@ describe("Total calculation", function () {
original_item_total: 210,
original_item_subtotal: 190.9090909090909,
original_item_tax_total: 19.09090909090909,
credit_lines_subtotal: 0,
credit_lines_tax_total: 0,
credit_lines_total: 0,
})
})
@@ -652,11 +670,21 @@ describe("Total calculation", function () {
original_shipping_tax_total: 2.5,
original_shipping_subtotal: 25,
original_shipping_total: 27.5,
credit_lines_subtotal: 0,
credit_lines_tax_total: 0,
credit_lines_total: 0,
})
})
it("should calculate order with items + taxes + adjustments", function () {
it("should calculate order with items + taxes + adjustments + credit lines", function () {
const cart = {
credit_lines: [
{
amount: 40,
reference: "order",
reference_id: "order_123",
},
],
items: [
{
unit_price: 50,
@@ -686,6 +714,15 @@ describe("Total calculation", function () {
const serialized = JSON.parse(JSON.stringify(decorateCartTotals(cart)))
expect(serialized).toEqual({
credit_lines: [
{
amount: 40,
reference: "order",
reference_id: "order_123",
subtotal: 40,
total: 40,
},
],
items: [
{
unit_price: 50,
@@ -730,8 +767,8 @@ describe("Total calculation", function () {
write_off_total: 44,
},
],
total: 88,
subtotal: 100,
total: 48,
subtotal: 60,
tax_total: 8,
discount_total: 22,
discount_subtotal: 20,
@@ -750,6 +787,9 @@ describe("Total calculation", function () {
return_received_total: 44,
return_dismissed_total: 44,
write_off_total: 44,
credit_lines_subtotal: 40,
credit_lines_tax_total: 0,
credit_lines_total: 40,
})
})
})
@@ -1,5 +1,6 @@
import { BigNumberInput, CartLikeWithTotals } from "@medusajs/types"
import { BigNumber } from "../big-number"
import { calculateCreditLinesTotal } from "../credit-lines"
import { GetItemTotalInput, getLineItemsTotals } from "../line-item"
import { MathBN } from "../math"
import {
@@ -13,6 +14,9 @@ interface TotalsConfig {
}
export interface DecorateCartLikeInputDTO {
credit_lines?: {
amount: BigNumberInput
}[]
items?: {
id?: string
unit_price: BigNumberInput
@@ -52,6 +56,7 @@ export function decorateCartTotals(
"detail.written_off_quantity": "write_off_total",
}
const creditLines = cartLike.credit_lines ?? []
const items = (cartLike.items ?? []) as unknown as GetItemTotalInput[]
const shippingMethods = (cartLike.shipping_methods ??
[]) as unknown as GetShippingMethodTotalInput[]
@@ -69,6 +74,10 @@ export function decorateCartTotals(
const extraTotals = {}
// TODO: Remove this once we have a way to calculate the tax rate for credit lines
const creditLinesSumTax = MathBN.convert(0)
const creditLinesSumTaxRate = MathBN.div(creditLinesSumTax, 100)
let subtotal = MathBN.convert(0)
let discountTotal = MathBN.convert(0)
@@ -192,6 +201,15 @@ export function decorateCartTotals(
return shippingMethodTotals
})
const { creditLinesTotal, creditLinesSubtotal, creditLinesTaxTotal } =
calculateCreditLinesTotal({
creditLines,
includesTax: false,
taxRate: creditLinesSumTaxRate,
})
subtotal = MathBN.sub(subtotal, creditLinesSubtotal)
const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal)
const originalTaxTotal = MathBN.add(
@@ -205,6 +223,7 @@ export function decorateCartTotals(
// TODO: subtract (cart.gift_card_total + cart.gift_card_tax_total)
const tempTotal = MathBN.add(subtotal, taxTotal)
const total = MathBN.sub(tempTotal, discountSubtotal)
const cart = cartLike as any
cart.total = new BigNumber(total)
@@ -215,6 +234,10 @@ export function decorateCartTotals(
cart.discount_subtotal = new BigNumber(discountSubtotal)
cart.discount_tax_total = new BigNumber(discountTaxTotal)
cart.credit_lines_total = new BigNumber(creditLinesTotal)
cart.credit_lines_subtotal = new BigNumber(creditLinesSubtotal)
cart.credit_lines_tax_total = new BigNumber(creditLinesTaxTotal)
// cart.gift_card_total = giftCardTotal.total || 0
// cart.gift_card_tax_total = giftCardTotal.tax_total || 0
@@ -0,0 +1,54 @@
import { BigNumberInput } from "@medusajs/types"
import { isDefined } from "../../common"
import { BigNumber } from "../big-number"
import { MathBN } from "../math"
export function calculateCreditLinesTotal({
creditLines,
includesTax,
taxRate,
}: {
creditLines: { amount: BigNumberInput }[]
includesTax?: boolean
taxRate?: BigNumberInput
}) {
// the sum of all creditLine amounts excluding tax
let creditLinesSubtotal = MathBN.convert(0)
// the sum of all creditLine amounts including tax
let creditLinesTotal = MathBN.convert(0)
// the sum of all taxes on subtotals
let creditLinesTaxTotal = MathBN.convert(0)
for (const cl of creditLines) {
if (!isDefined(cl.amount)) {
continue
}
const creditLineAmount = MathBN.convert(cl.amount)
creditLinesSubtotal = MathBN.add(creditLinesSubtotal, creditLineAmount)
if (isDefined(taxRate)) {
const creditLineSubtotal = includesTax
? MathBN.div(creditLineAmount, MathBN.add(1, taxRate))
: creditLineAmount
const creditLineTaxTotal = MathBN.mult(creditLineSubtotal, taxRate)
const creditLineTotal = MathBN.add(creditLineSubtotal, creditLineTaxTotal)
cl["subtotal"] = new BigNumber(creditLineSubtotal)
cl["total"] = new BigNumber(creditLineTotal)
creditLinesTotal = MathBN.add(creditLinesTotal, creditLineTotal)
creditLinesTaxTotal = MathBN.add(creditLinesTaxTotal, creditLineTaxTotal)
} else {
cl["subtotal"] = new BigNumber(creditLineAmount)
creditLinesTotal = MathBN.add(creditLinesTotal, creditLineAmount)
}
}
return {
creditLinesTotal,
creditLinesSubtotal,
creditLinesTaxTotal,
}
}