This fixes the discount_ calculation logic and promotion tax inclusiveness calculation (#12960)

* This fixes the discount_ calculation logic

* This fixes the adjustment to be handled as a subtotal value in every calculation and applies the tax inclusive logic on the promotion value itself

* Added some testcases and revoked some changes to improve testing output

* Fixed a test case based on feedback

* Corrected promotion/admin test cases

* Corrected cart/store test case

* Improved cart/store test cases for more robust promotion testing considering tax inclusion flags

* Remove unnessary changes as adjustments now automatically are subtotals and therefore the tax inclusive flag does not need to be applied again

* Remove adjustments->is_tax_inclusive usage everywhere

* Migration script to remove is_tax_inclusive in cart line item adjustment

* Forgot to adjust one more testcase

* Corrections based on fPolic feedback

* Refactored PR to consider feedback from oliver

* Added more testcases for promotion in cart

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
scherddel
2025-07-31 13:27:43 +02:00
committed by GitHub
parent 75320e744f
commit 1bdf602f1c
11 changed files with 1076 additions and 119 deletions

View File

@@ -419,22 +419,22 @@ medusaIntegrationTestRunner({
compare_at_unit_price: null,
is_tax_inclusive: true,
quantity: 2,
tax_lines: [
tax_lines: expect.arrayContaining([
expect.objectContaining({
description: "CA Default Rate",
code: "CADEFAULT",
rate: 5,
provider_id: "system",
}),
],
adjustments: [
{
]),
adjustments: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
code: "PROMOTION_APPLIED",
promotion_id: promotion.id,
amount: 100,
},
],
}),
]),
}),
]),
})
@@ -456,14 +456,14 @@ medusaIntegrationTestRunner({
id: cart.id,
items: expect.arrayContaining([
expect.objectContaining({
adjustments: [
{
adjustments: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
code: "PROMOTION_APPLIED",
promotion_id: promotion.id,
amount: 100,
},
],
}),
]),
}),
]),
})
@@ -823,22 +823,22 @@ medusaIntegrationTestRunner({
compare_at_unit_price: 1500,
is_tax_inclusive: true,
quantity: 2,
tax_lines: [
tax_lines: expect.arrayContaining([
expect.objectContaining({
description: "CA Default Rate",
code: "CADEFAULT",
rate: 5,
provider_id: "system",
}),
],
adjustments: [
{
]),
adjustments: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
code: "PROMOTION_APPLIED",
promotion_id: promotion.id,
amount: 100,
},
],
}),
]),
}),
]),
})
@@ -1235,7 +1235,7 @@ medusaIntegrationTestRunner({
id: expect.any(String),
currency_code: "usd",
credit_line_total: 2395,
discount_total: 100,
discount_total: 105,
credit_lines: [
expect.objectContaining({
amount: 2395,
@@ -2794,7 +2794,7 @@ medusaIntegrationTestRunner({
is_discountable: true,
unit_price: 1500,
total: 1395,
discount_total: 100,
discount_total: 105,
adjustments: [
expect.objectContaining({
promotion_id: promotion.id,
@@ -2825,14 +2825,14 @@ medusaIntegrationTestRunner({
id: cart.id,
items: expect.arrayContaining([
expect.objectContaining({
adjustments: [
{
adjustments: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
code: "PROMOTION_APPLIED",
promotion_id: promotion.id,
amount: 100,
},
],
}),
]),
}),
]),
})
@@ -2860,6 +2860,609 @@ medusaIntegrationTestRunner({
})
)
})
it("should add a 100 USD tax exclusive promotion for a 105 USD tax inclusive item and logically result in a 0 total with tax 5%", async () => {
const taxExclPromotion = (
await api.post(
`/admin/promotions`,
{
code: "PROMOTION_TAX_EXCLUSIVE",
type: PromotionType.STANDARD,
status: PromotionStatus.ACTIVE,
is_tax_inclusive: false, //Here we apply a tax exclusive promotion to a tax inclusive item in a way that the total SHOULD be 0
application_method: {
type: "fixed",
target_type: "items",
allocation: "across",
currency_code: "usd",
value: 100,
apply_to_quantity: 1,
},
},
adminHeaders
)
).data.promotion
const product = (
await api.post(
`/admin/products`,
{
title: "Product for free",
description: "test",
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
},
],
variants: [
{
title: "S / Black",
sku: "special-shirt",
options: {
Size: "S",
},
manage_inventory: false,
prices: [
{
amount: 105,
currency_code: "usd",
},
],
},
],
},
adminHeaders
)
).data.product
cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
},
storeHeadersWithCustomer
)
).data.cart
cart = (
await api.post(
`/store/carts/${cart.id}/line-items`,
{
variant_id: product.variants[0].id,
quantity: 1,
},
storeHeaders
)
).data.cart
let updated = await api.post(
`/store/carts/${cart.id}`,
{ promo_codes: [taxExclPromotion.code] },
storeHeaders
)
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
discount_total: 105,
discount_subtotal: 100,
discount_tax_total: 5,
original_total: 105,
total: 0, // 105 - 100 tax excl promotion + 5 promotion tax
items: expect.arrayContaining([
expect.objectContaining({
is_tax_inclusive: true,
adjustments: expect.arrayContaining([
expect.objectContaining({
code: taxExclPromotion.code,
amount: 100,
}),
]),
}),
]),
promotions: expect.arrayContaining([
expect.objectContaining({
code: "PROMOTION_TAX_EXCLUSIVE",
application_method: expect.objectContaining({
value: 100,
}),
}),
]),
})
)
})
it("should add a 105 USD tax inclusive promotion (fixed, across, apply_to_quantity=1) for a 105 USD tax inclusive item and logically result in a 0 total with tax 5%", async () => {
const taxInclPromotion = (
await api.post(
`/admin/promotions`,
{
code: "PROMOTION_TAX_INCLUSIVE",
type: PromotionType.STANDARD,
status: PromotionStatus.ACTIVE,
is_tax_inclusive: true, //Here we apply a tax inclusive promotion to a tax inclusive item in a way that the total SHOULD be 0
application_method: {
type: "fixed",
target_type: "items",
allocation: "across",
currency_code: "usd",
value: 105,
apply_to_quantity: 1,
},
},
adminHeaders
)
).data.promotion
const product = (
await api.post(
`/admin/products`,
{
title: "Product for free",
description: "test",
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
},
],
variants: [
{
title: "S / Black",
sku: "special-shirt",
options: {
Size: "S",
},
manage_inventory: false,
prices: [
{
amount: 105,
currency_code: "usd",
},
],
},
],
},
adminHeaders
)
).data.product
cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
},
storeHeadersWithCustomer
)
).data.cart
cart = (
await api.post(
`/store/carts/${cart.id}/line-items`,
{
variant_id: product.variants[0].id,
quantity: 1,
},
storeHeaders
)
).data.cart
let updated = await api.post(
`/store/carts/${cart.id}`,
{ promo_codes: [taxInclPromotion.code] },
storeHeaders
)
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
discount_total: 105,
discount_subtotal: 100,
discount_tax_total: 5,
original_total: 105,
total: 0, // 105 - 100 tax excl promotion + 5 promotion tax
items: expect.arrayContaining([
expect.objectContaining({
is_tax_inclusive: true,
adjustments: expect.arrayContaining([
expect.objectContaining({
code: taxInclPromotion.code,
amount: 105,
is_tax_inclusive: true,
}),
]),
}),
]),
promotions: expect.arrayContaining([
expect.objectContaining({
code: "PROMOTION_TAX_INCLUSIVE",
is_tax_inclusive: true,
application_method: expect.objectContaining({
value: 105,
}),
}),
]),
})
)
})
it("should add a 105 USD tax inclusive promotion (fixed, across, apply_to_quantity=1) for two 105 USD tax inclusive items and logically result in a 105 total with tax 5%", async () => {
const taxInclPromotion = (
await api.post(
`/admin/promotions`,
{
code: "PROMOTION_TAX_INCLUSIVE",
type: PromotionType.STANDARD,
status: PromotionStatus.ACTIVE,
is_tax_inclusive: true, //Here we apply a tax inclusive promotion to a tax inclusive item in a way that the total SHOULD be 0
application_method: {
type: "fixed",
target_type: "items",
allocation: "across",
currency_code: "usd",
value: 105,
apply_to_quantity: 1,
},
},
adminHeaders
)
).data.promotion
const product = (
await api.post(
`/admin/products`,
{
title: "Product for free",
description: "test",
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
},
],
variants: [
{
title: "S / Black",
sku: "special-shirt",
options: {
Size: "S",
},
manage_inventory: false,
prices: [
{
amount: 105,
currency_code: "usd",
},
],
},
],
},
adminHeaders
)
).data.product
cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
},
storeHeadersWithCustomer
)
).data.cart
cart = (
await api.post(
`/store/carts/${cart.id}/line-items`,
{
variant_id: product.variants[0].id,
quantity: 2,
},
storeHeaders
)
).data.cart
let updated = await api.post(
`/store/carts/${cart.id}`,
{ promo_codes: [taxInclPromotion.code] },
storeHeaders
)
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
discount_total: 105,
discount_subtotal: 100,
discount_tax_total: 5,
original_total: 210,
total: 105, // 210 - 100 tax excl promotion + 5 promotion tax
items: expect.arrayContaining([
expect.objectContaining({
is_tax_inclusive: true,
adjustments: expect.arrayContaining([
expect.objectContaining({
code: taxInclPromotion.code,
amount: 105,
is_tax_inclusive: true,
}),
]),
}),
]),
promotions: expect.arrayContaining([
expect.objectContaining({
code: "PROMOTION_TAX_INCLUSIVE",
is_tax_inclusive: true,
application_method: expect.objectContaining({
value: 105,
}),
}),
]),
})
)
})
it("should add a 105 USD tax inclusive promotion (fixed, each, max_quantity=2) for two 105 USD tax inclusive items and logically result in a 0 total with tax 5%", async () => {
const taxInclPromotion = (
await api.post(
`/admin/promotions`,
{
code: "PROMOTION_TAX_INCLUSIVE",
type: PromotionType.STANDARD,
status: PromotionStatus.ACTIVE,
is_tax_inclusive: true, //Here we apply a tax inclusive promotion to a tax inclusive item in a way that the total SHOULD be 0
application_method: {
type: "fixed",
target_type: "items",
allocation: "each",
currency_code: "usd",
value: 105,
max_quantity: 2,
},
},
adminHeaders
)
).data.promotion
const product = (
await api.post(
`/admin/products`,
{
title: "Product for free",
description: "test",
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
},
],
variants: [
{
title: "S / Black",
sku: "special-shirt",
options: {
Size: "S",
},
manage_inventory: false,
prices: [
{
amount: 105,
currency_code: "usd",
},
],
},
],
},
adminHeaders
)
).data.product
cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
},
storeHeadersWithCustomer
)
).data.cart
cart = (
await api.post(
`/store/carts/${cart.id}/line-items`,
{
variant_id: product.variants[0].id,
quantity: 2,
},
storeHeaders
)
).data.cart
let updated = await api.post(
`/store/carts/${cart.id}`,
{ promo_codes: [taxInclPromotion.code] },
storeHeaders
)
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
discount_total: 105,
discount_subtotal: 100,
discount_tax_total: 5,
original_total: 210,
total: 105, // 210 - 100 tax excl promotion + 5 promotion tax
items: expect.arrayContaining([
expect.objectContaining({
is_tax_inclusive: true,
adjustments: expect.arrayContaining([
expect.objectContaining({
code: taxInclPromotion.code,
amount: 105,
is_tax_inclusive: true,
}),
]),
}),
]),
promotions: expect.arrayContaining([
expect.objectContaining({
code: "PROMOTION_TAX_INCLUSIVE",
is_tax_inclusive: true,
application_method: expect.objectContaining({
value: 105,
}),
}),
]),
})
)
})
it("should add two tax inclusive promotions (50,100) (fixed, across) for two 105 USD tax inclusive items", async () => {
const taxInclPromotion50 = (
await api.post(
`/admin/promotions`,
{
code: "PROMOTION_TAX_INCLUSIVE_50",
type: PromotionType.STANDARD,
status: PromotionStatus.ACTIVE,
is_tax_inclusive: true,
application_method: {
type: "fixed",
target_type: "items",
allocation: "across",
currency_code: "usd",
value: 50,
},
},
adminHeaders
)
).data.promotion
const taxInclPromotion100 = (
await api.post(
`/admin/promotions`,
{
code: "PROMOTION_TAX_INCLUSIVE_100",
type: PromotionType.STANDARD,
status: PromotionStatus.ACTIVE,
is_tax_inclusive: true,
application_method: {
type: "fixed",
target_type: "items",
allocation: "across",
currency_code: "usd",
value: 100,
},
},
adminHeaders
)
).data.promotion
const product = (
await api.post(
`/admin/products`,
{
title: "Product for free",
description: "test",
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
},
],
variants: [
{
title: "S / Black",
sku: "special-shirt",
options: {
Size: "S",
},
manage_inventory: false,
prices: [
{
amount: 105,
currency_code: "usd",
},
],
},
],
},
adminHeaders
)
).data.product
cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
},
storeHeadersWithCustomer
)
).data.cart
cart = (
await api.post(
`/store/carts/${cart.id}/line-items`,
{
variant_id: product.variants[0].id,
quantity: 2,
},
storeHeaders
)
).data.cart
let updated = await api.post(
`/store/carts/${cart.id}`,
{
promo_codes: [taxInclPromotion50.code, taxInclPromotion100.code],
},
storeHeaders
)
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
discount_total: 150,
original_total: 210,
total: 60, // 210 - (100 + 50 tax incl promotion)
items: expect.arrayContaining([
expect.objectContaining({
is_tax_inclusive: true,
adjustments: expect.arrayContaining([
expect.objectContaining({
code: taxInclPromotion50.code,
amount: 50,
is_tax_inclusive: true,
}),
expect.objectContaining({
code: taxInclPromotion100.code,
amount: 100,
is_tax_inclusive: true,
}),
]),
}),
]),
})
)
})
})
describe("POST /store/carts/:id/customer", () => {

View File

@@ -1322,9 +1322,9 @@ medusaIntegrationTestRunner({
original_total: 1300,
original_tax_total: 260,
discount_total: 100,
discount_total: 125,
discount_subtotal: 100,
discount_tax_total: 20,
discount_tax_total: 25,
item_total: 1175,
item_subtotal: 1040,
@@ -1354,14 +1354,13 @@ medusaIntegrationTestRunner({
original_total: 1300,
original_tax_total: 260,
discount_total: 100,
discount_total: 125,
discount_subtotal: 100,
discount_tax_total: 20,
discount_tax_total: 25,
adjustments: expect.arrayContaining([
expect.objectContaining({
amount: 100,
is_tax_inclusive: false,
}),
]),
}),
@@ -1403,9 +1402,9 @@ medusaIntegrationTestRunner({
original_total: 1300,
original_tax_total: 260,
discount_total: 100,
discount_total: 125,
discount_subtotal: 100,
discount_tax_total: 20,
discount_tax_total: 25,
item_total: 1175,
item_subtotal: 1040,
@@ -1435,14 +1434,13 @@ medusaIntegrationTestRunner({
original_total: 1300,
original_tax_total: 260,
discount_total: 100,
discount_total: 125,
discount_subtotal: 100,
discount_tax_total: 20,
discount_tax_total: 25,
adjustments: expect.arrayContaining([
expect.objectContaining({
amount: 100,
is_tax_inclusive: false,
}),
]),
}),
@@ -1598,7 +1596,6 @@ medusaIntegrationTestRunner({
adjustments: expect.arrayContaining([
expect.objectContaining({
amount: 100,
is_tax_inclusive: false,
}),
]),
}),
@@ -1679,7 +1676,6 @@ medusaIntegrationTestRunner({
adjustments: expect.arrayContaining([
expect.objectContaining({
amount: 100,
is_tax_inclusive: false,
}),
]),
}),