From 2621f00bb035a6b909f9498a2bc98fdba8570ba9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Frane=20Poli=C4=87?=
<16856471+fPolic@users.noreply.github.com>
Date: Thu, 12 Jun 2025 15:07:11 +0200
Subject: [PATCH] feat(promotion, dashboard, core-flows, cart, types, utils,
medusa): tax inclusive promotions (#12412)
* feat: tax inclusive promotions
* feat: add a totals test case
* feat: add integration test
* chore: changeset
* fix: typo
* chore: refactor
* fix: tests
* fix: rest of buyget action tests
* fix: cart spec
* chore: expand integration test with item level totals
* feat: add a few more test cases
---------
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
---
.changeset/metal-kangaroos-push.md | 11 +
.../promotions/admin/promotions.spec.ts | 728 +++++++++++++++++-
.../src/i18n/translations/$schema.json | 18 +
.../dashboard/src/i18n/translations/en.json | 5 +
.../create-promotion-form.tsx | 47 ++
.../create-promotion-form/form-schema.ts | 1 +
.../create-promotion-form/templates.ts | 6 +-
.../promotion-general-section.tsx | 12 +
...pare-adjustments-from-promotion-actions.ts | 1 +
packages/core/types/src/cart/common.ts | 5 +
packages/core/types/src/cart/mutations.ts | 5 +
.../src/http/promotion/admin/payloads.ts | 32 +-
.../core/types/src/http/promotion/common.ts | 11 +-
.../src/promotion/common/compute-actions.ts | 6 +
.../types/src/promotion/common/promotion.ts | 10 +
.../core/utils/src/totals/__tests__/totals.ts | 90 +++
.../core/utils/src/totals/adjustment/index.ts | 12 +-
packages/core/utils/src/totals/cart/index.ts | 2 +-
.../src/api/admin/promotions/query-config.ts | 1 +
.../src/api/admin/promotions/validators.ts | 1 +
.../services/cart-module/index.spec.ts | 2 +
.../src/migrations/.snapshot-medusa-cart.json | 10 +
.../src/migrations/Migration20250508081553.ts | 13 +
.../cart/src/models/line-item-adjustment.ts | 1 +
.../promotion-module/compute-actions.spec.ts | 58 ++
.../.snapshot-medusa-promotion.json | 10 +
.../src/migrations/Migration20250508081510.ts | 15 +
.../modules/promotion/src/models/promotion.ts | 1 +
.../src/utils/compute-actions/line-items.ts | 1 +
29 files changed, 1091 insertions(+), 24 deletions(-)
create mode 100644 .changeset/metal-kangaroos-push.md
create mode 100644 packages/modules/cart/src/migrations/Migration20250508081553.ts
create mode 100644 packages/modules/promotion/src/migrations/Migration20250508081510.ts
diff --git a/.changeset/metal-kangaroos-push.md b/.changeset/metal-kangaroos-push.md
new file mode 100644
index 0000000000..1603b6f7c5
--- /dev/null
+++ b/.changeset/metal-kangaroos-push.md
@@ -0,0 +1,11 @@
+---
+"@medusajs/promotion": patch
+"@medusajs/dashboard": patch
+"@medusajs/core-flows": patch
+"@medusajs/cart": patch
+"@medusajs/types": patch
+"@medusajs/utils": patch
+"@medusajs/medusa": patch
+---
+
+feat(promotion, dashboard, core-flows, cart, types, utils, medusa): tax inclusive promotions
diff --git a/integration-tests/http/__tests__/promotions/admin/promotions.spec.ts b/integration-tests/http/__tests__/promotions/admin/promotions.spec.ts
index 0f7ab1a52b..289076342e 100644
--- a/integration-tests/http/__tests__/promotions/admin/promotions.spec.ts
+++ b/integration-tests/http/__tests__/promotions/admin/promotions.spec.ts
@@ -1,11 +1,12 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
-import { PromotionStatus, PromotionType } from "@medusajs/utils"
+import { Modules, PromotionStatus, PromotionType } from "@medusajs/utils"
import {
createAdminUser,
generatePublishableKey,
generateStoreHeaders,
} from "../../../../helpers/create-admin-user"
import { medusaTshirtProduct } from "../../../__fixtures__/product"
+import { setupTaxStructure } from "../../../../modules/__tests__/fixtures/tax"
jest.setTimeout(50000)
@@ -71,6 +72,8 @@ medusaIntegrationTestRunner({
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, appContainer)
+ await setupTaxStructure(appContainer.resolve(Modules.TAX))
+
promotion = standardPromotion = (
await api.post(
`/admin/promotions`,
@@ -629,6 +632,729 @@ medusaIntegrationTestRunner({
)
})
})
+
+ it("should add tax inclusive promotion to cart successfully in a tax inclusive currency", async () => {
+ const publishableKey = await generatePublishableKey(appContainer)
+ const storeHeaders = generateStoreHeaders({ publishableKey })
+
+ const salesChannel = (
+ await api.post(
+ "/admin/sales-channels",
+ { name: "Webshop", description: "channel" },
+ adminHeaders
+ )
+ ).data.sales_channel
+
+ await api.post(
+ "/admin/price-preferences",
+ {
+ attribute: "currency_code",
+ value: "dkk",
+ is_tax_inclusive: true,
+ },
+ adminHeaders
+ )
+
+ const region = (
+ await api.post(
+ "/admin/regions",
+ {
+ name: "DK",
+ currency_code: "dkk",
+ countries: ["dk"],
+ },
+ adminHeaders
+ )
+ ).data.region
+
+ const product = (
+ await api.post(
+ "/admin/products",
+ {
+ ...medusaTshirtProduct,
+ shipping_profile_id: shippingProfile.id,
+ },
+ adminHeaders
+ )
+ ).data.product
+
+ const response = await api.post(
+ `/admin/promotions`,
+ {
+ code: "FIXED_10",
+ type: PromotionType.STANDARD,
+ status: PromotionStatus.ACTIVE,
+ is_tax_inclusive: true,
+ is_automatic: true,
+ application_method: {
+ target_type: "items",
+ type: "fixed",
+ allocation: "across",
+ currency_code: "DKK",
+ value: 100,
+ },
+ },
+ adminHeaders
+ )
+
+ expect(response.status).toEqual(200)
+ expect(response.data.promotion).toEqual(
+ expect.objectContaining({
+ id: expect.any(String),
+ code: "FIXED_10",
+ type: "standard",
+ is_tax_inclusive: true,
+ is_automatic: true,
+ application_method: expect.objectContaining({
+ value: 100,
+ type: "fixed",
+ target_type: "items",
+ allocation: "across",
+ }),
+ })
+ )
+
+ const cart = (
+ await api.post(
+ `/store/carts?fields=*items,*items.adjustments`,
+ {
+ currency_code: "dkk",
+ sales_channel_id: salesChannel.id,
+ region_id: region.id,
+ items: [
+ {
+ variant_id: product.variants[0].id,
+ quantity: 1,
+ },
+ ],
+ promo_codes: [response.data.promotion.code],
+ },
+ storeHeaders
+ )
+ ).data.cart
+
+ /**
+ * Orignal total -> 1300 DKK (tax incl.)
+ * Tax rate -> 25%
+ * Promotion -> FIXED 100 DKK (tax incl.)
+ *
+ * We want total to be 1300 DKK - 100 DKK = 1200 DKK
+ */
+ expect(cart).toEqual(
+ expect.objectContaining({
+ currency_code: "dkk",
+
+ subtotal: 1040, // taxable base (item subtotal - discount subtotal) = 1040 - 80 = 960
+ total: 1200, // total = taxable base * (1 + tax rate) = 960 * (1 + 0.25) = 1200
+ tax_total: 240,
+
+ original_total: 1300,
+ original_tax_total: 260,
+
+ discount_total: 100,
+ discount_subtotal: 80,
+ discount_tax_total: 20,
+
+ item_total: 1200,
+ item_subtotal: 1040,
+ item_tax_total: 240,
+
+ original_item_total: 1300,
+ original_item_subtotal: 1040,
+ original_item_tax_total: 260,
+
+ shipping_total: 0,
+ shipping_subtotal: 0,
+ shipping_tax_total: 0,
+
+ original_shipping_tax_total: 0,
+ original_shipping_subtotal: 0,
+ original_shipping_total: 0,
+
+ items: expect.arrayContaining([
+ expect.objectContaining({
+ quantity: 1,
+ unit_price: 1300,
+
+ subtotal: 1040,
+ tax_total: 240,
+ total: 1200,
+
+ original_total: 1300,
+ original_tax_total: 260,
+
+ discount_total: 100,
+ discount_subtotal: 80,
+ discount_tax_total: 20,
+
+ adjustments: expect.arrayContaining([
+ expect.objectContaining({
+ amount: 100,
+ is_tax_inclusive: true,
+ }),
+ ]),
+ }),
+ ]),
+ })
+ )
+ })
+
+ it("should add tax inclusive promotion to cart successfully in a tax inclusive currency with 2 items and each allocation", async () => {
+ const publishableKey = await generatePublishableKey(appContainer)
+ const storeHeaders = generateStoreHeaders({ publishableKey })
+
+ const salesChannel = (
+ await api.post(
+ "/admin/sales-channels",
+ { name: "Webshop", description: "channel" },
+ adminHeaders
+ )
+ ).data.sales_channel
+
+ await api.post(
+ "/admin/price-preferences",
+ {
+ attribute: "currency_code",
+ value: "dkk",
+ is_tax_inclusive: true,
+ },
+ adminHeaders
+ )
+
+ const region = (
+ await api.post(
+ "/admin/regions",
+ {
+ name: "DK",
+ currency_code: "dkk",
+ countries: ["dk"],
+ },
+ adminHeaders
+ )
+ ).data.region
+
+ const product = (
+ await api.post(
+ "/admin/products",
+ {
+ title: "Discounted Medusa T-Shirt",
+ handle: "discounted-medusa-t-shirt",
+ options: [
+ {
+ title: "Size",
+ values: ["S", "M"],
+ },
+ ],
+ variants: [
+ {
+ title: "S",
+ sku: "SHIRT-S",
+ options: {
+ Size: "S",
+ },
+ manage_inventory: false,
+ prices: [
+ {
+ amount: 1000,
+ currency_code: "dkk",
+ },
+ ],
+ },
+ {
+ title: "M",
+ sku: "SHIRT-M",
+ options: {
+ Size: "S",
+ },
+ manage_inventory: false,
+ prices: [
+ {
+ amount: 500,
+ currency_code: "dkk",
+ },
+ ],
+ },
+ ],
+ shipping_profile_id: shippingProfile.id,
+ },
+ adminHeaders
+ )
+ ).data.product
+
+ const response = await api.post(
+ `/admin/promotions`,
+ {
+ code: "FIXED_10",
+ type: PromotionType.STANDARD,
+ status: PromotionStatus.ACTIVE,
+ is_tax_inclusive: true,
+ is_automatic: true,
+ application_method: {
+ target_type: "items",
+ type: "fixed",
+ allocation: "each",
+ currency_code: "DKK",
+ value: 100,
+ max_quantity: 2,
+ },
+ },
+ adminHeaders
+ )
+
+ expect(response.status).toEqual(200)
+ expect(response.data.promotion).toEqual(
+ expect.objectContaining({
+ id: expect.any(String),
+ code: "FIXED_10",
+ type: "standard",
+ is_tax_inclusive: true,
+ is_automatic: true,
+ application_method: expect.objectContaining({
+ value: 100,
+ type: "fixed",
+ target_type: "items",
+ allocation: "each",
+ max_quantity: 2,
+ }),
+ })
+ )
+
+ const cart = (
+ await api.post(
+ `/store/carts?fields=*items,*items.adjustments`,
+ {
+ currency_code: "dkk",
+ sales_channel_id: salesChannel.id,
+ region_id: region.id,
+ items: [
+ {
+ variant_id: product.variants[0].id,
+ quantity: 1,
+ },
+ {
+ variant_id: product.variants[1].id,
+ quantity: 1,
+ },
+ ],
+ promo_codes: [response.data.promotion.code],
+ },
+ storeHeaders
+ )
+ ).data.cart
+
+ /**
+ * Orignal total -> 1500 DKK (tax incl.)
+ * Promotion -> FIXED 100 DKK per item (tax incl.)
+ * Tax rate -> 25%
+ *
+ * We want total to be 1500 DKK - 100 DKK - 100 DKK = 1300 DKK
+ */
+ expect(cart).toEqual(
+ expect.objectContaining({
+ currency_code: "dkk",
+
+ total: 1300,
+ subtotal: 1200, // taxable base (item subtotal - discount subtotal) = 1200 - 200 = 1000
+ tax_total: 260,
+
+ discount_total: 200, // 2 * 100 DKK fixed tax inclusive
+ discount_subtotal: 160,
+ discount_tax_total: 40,
+
+ original_total: 1500,
+ original_tax_total: 300,
+
+ item_total: 1300,
+ item_subtotal: 1200,
+ item_tax_total: 260,
+
+ original_item_total: 1500,
+ original_item_subtotal: 1200,
+ original_item_tax_total: 300,
+
+ shipping_total: 0,
+ shipping_subtotal: 0,
+ shipping_tax_total: 0,
+
+ original_shipping_tax_total: 0,
+ original_shipping_subtotal: 0,
+ original_shipping_total: 0,
+
+ items: expect.arrayContaining([
+ expect.objectContaining({
+ quantity: 1,
+ unit_price: 500,
+
+ subtotal: 400,
+ total: 400, // 400 - 80 = 320 -> 320 * 1.25 = 400
+ tax_total: 80,
+
+ original_total: 500,
+ original_tax_total: 100,
+
+ discount_total: 100,
+ discount_subtotal: 80,
+ discount_tax_total: 20,
+
+ adjustments: expect.arrayContaining([
+ expect.objectContaining({
+ amount: 100,
+ is_tax_inclusive: true,
+ }),
+ ]),
+ }),
+ expect.objectContaining({
+ quantity: 1,
+ unit_price: 1000,
+
+ subtotal: 800, // 800 - 80 = 720 -> 720 * 1.25 = 900
+ total: 900,
+ tax_total: 180,
+
+ original_total: 1000,
+ original_tax_total: 200,
+
+ discount_total: 100,
+ discount_subtotal: 80,
+ discount_tax_total: 20,
+
+ adjustments: expect.arrayContaining([
+ expect.objectContaining({
+ amount: 100,
+ is_tax_inclusive: true,
+ }),
+ ]),
+ }),
+ ]),
+ })
+ )
+ })
+
+ it("should add tax exclusive promotion to cart successfully for tax inclusive currency", async () => {
+ const publishableKey = await generatePublishableKey(appContainer)
+ const storeHeaders = generateStoreHeaders({ publishableKey })
+
+ const salesChannel = (
+ await api.post(
+ "/admin/sales-channels",
+ { name: "Webshop", description: "channel" },
+ adminHeaders
+ )
+ ).data.sales_channel
+
+ await api.post(
+ "/admin/price-preferences",
+ {
+ attribute: "currency_code",
+ value: "dkk",
+ is_tax_inclusive: true,
+ },
+ adminHeaders
+ )
+
+ const region = (
+ await api.post(
+ "/admin/regions",
+ {
+ name: "DK",
+ currency_code: "dkk",
+ countries: ["dk"],
+ },
+ adminHeaders
+ )
+ ).data.region
+
+ const product = (
+ await api.post(
+ "/admin/products",
+ {
+ ...medusaTshirtProduct,
+ shipping_profile_id: shippingProfile.id,
+ },
+ adminHeaders
+ )
+ ).data.product
+
+ const response = await api.post(
+ `/admin/promotions`,
+ {
+ code: "FIXED_10",
+ type: PromotionType.STANDARD,
+ status: PromotionStatus.ACTIVE,
+ is_automatic: true,
+ application_method: {
+ target_type: "items",
+ type: "fixed",
+ allocation: "across",
+ currency_code: "DKK",
+ value: 100,
+ },
+ },
+ adminHeaders
+ )
+
+ expect(response.status).toEqual(200)
+ expect(response.data.promotion).toEqual(
+ expect.objectContaining({
+ id: expect.any(String),
+ code: "FIXED_10",
+ type: "standard",
+ is_tax_inclusive: false, // tax exclusive by default
+ is_automatic: true,
+ application_method: expect.objectContaining({
+ value: 100,
+ type: "fixed",
+ target_type: "items",
+ allocation: "across",
+ }),
+ })
+ )
+
+ const cart = (
+ await api.post(
+ `/store/carts?fields=*items,*items.adjustments`,
+ {
+ currency_code: "dkk",
+ sales_channel_id: salesChannel.id,
+ region_id: region.id,
+ items: [
+ {
+ variant_id: product.variants[0].id,
+ quantity: 1,
+ },
+ ],
+ promo_codes: [response.data.promotion.code],
+ },
+ storeHeaders
+ )
+ ).data.cart
+
+ /**
+ * Orignal total -> 1300 DKK (tax incl.)
+ * Tax rate -> 25%
+ * Promotion -> FIXED 100 DKK (tax exclusive !)
+ */
+ expect(cart).toEqual(
+ expect.objectContaining({
+ currency_code: "dkk",
+
+ subtotal: 1040, // taxable base (item subtotal - discount subtotal) = 1040 - 100 = 940
+ total: 1175, // total = taxable base * (1 + tax rate) = 940 * (1 + 0.25) = 1175
+ tax_total: 235,
+
+ original_total: 1300,
+ original_tax_total: 260,
+
+ discount_total: 100,
+ discount_subtotal: 100,
+ discount_tax_total: 20,
+
+ item_total: 1175,
+ item_subtotal: 1040,
+ item_tax_total: 235,
+
+ original_item_total: 1300,
+ original_item_subtotal: 1040,
+ original_item_tax_total: 260,
+
+ shipping_total: 0,
+ shipping_subtotal: 0,
+ shipping_tax_total: 0,
+
+ original_shipping_tax_total: 0,
+ original_shipping_subtotal: 0,
+ original_shipping_total: 0,
+
+ items: expect.arrayContaining([
+ expect.objectContaining({
+ quantity: 1,
+ unit_price: 1300,
+
+ subtotal: 1040,
+ tax_total: 235,
+ total: 1175,
+
+ original_total: 1300,
+ original_tax_total: 260,
+
+ discount_total: 100,
+ discount_subtotal: 100,
+ discount_tax_total: 20,
+
+ adjustments: expect.arrayContaining([
+ expect.objectContaining({
+ amount: 100,
+ is_tax_inclusive: false,
+ }),
+ ]),
+ }),
+ ]),
+ })
+ )
+ })
+
+ it("should add tax exclusive promotion to cart successfully for tax exclusive currency", async () => {
+ const publishableKey = await generatePublishableKey(appContainer)
+ const storeHeaders = generateStoreHeaders({ publishableKey })
+
+ const salesChannel = (
+ await api.post(
+ "/admin/sales-channels",
+ { name: "Webshop", description: "channel" },
+ adminHeaders
+ )
+ ).data.sales_channel
+
+ await api.post(
+ "/admin/price-preferences",
+ {
+ attribute: "currency_code",
+ value: "dkk",
+ is_tax_inclusive: false,
+ },
+ adminHeaders
+ )
+
+ const region = (
+ await api.post(
+ "/admin/regions",
+ {
+ name: "DK",
+ currency_code: "dkk",
+ countries: ["dk"],
+ },
+ adminHeaders
+ )
+ ).data.region
+
+ const product = (
+ await api.post(
+ "/admin/products",
+ {
+ ...medusaTshirtProduct,
+ shipping_profile_id: shippingProfile.id,
+ },
+ adminHeaders
+ )
+ ).data.product
+
+ const response = await api.post(
+ `/admin/promotions`,
+ {
+ code: "FIXED_10",
+ type: PromotionType.STANDARD,
+ status: PromotionStatus.ACTIVE,
+ is_automatic: true,
+ application_method: {
+ target_type: "items",
+ type: "fixed",
+ allocation: "across",
+ currency_code: "DKK",
+ value: 100,
+ },
+ },
+ adminHeaders
+ )
+
+ expect(response.status).toEqual(200)
+ expect(response.data.promotion).toEqual(
+ expect.objectContaining({
+ id: expect.any(String),
+ code: "FIXED_10",
+ type: "standard",
+ is_tax_inclusive: false, // tax exclusive by default
+ is_automatic: true,
+ application_method: expect.objectContaining({
+ value: 100,
+ type: "fixed",
+ target_type: "items",
+ allocation: "across",
+ }),
+ })
+ )
+
+ const cart = (
+ await api.post(
+ `/store/carts?fields=*items,*items.adjustments`,
+ {
+ currency_code: "dkk",
+ sales_channel_id: salesChannel.id,
+ region_id: region.id,
+ items: [
+ {
+ variant_id: product.variants[0].id,
+ quantity: 1,
+ },
+ ],
+ promo_codes: [response.data.promotion.code],
+ },
+ storeHeaders
+ )
+ ).data.cart
+
+ /**
+ * Orignal total -> 1300 DKK (tax excl.)
+ * Tax rate -> 25%
+ * Promotion -> FIXED 100 DKK (tax exclusive !)
+ */
+ expect(cart).toEqual(
+ expect.objectContaining({
+ currency_code: "dkk",
+
+ subtotal: 1300, // taxable base (item subtotal - discount subtotal) = 1300 - 100 = 1200
+ total: 1500, // total = taxable base * (1 + tax rate) = 1200 * (1 + 0.25) = 1500
+ tax_total: 300,
+
+ original_total: 1625,
+ original_tax_total: 325,
+
+ discount_total: 125,
+ discount_subtotal: 100,
+ discount_tax_total: 25,
+
+ item_total: 1500,
+ item_subtotal: 1300,
+ item_tax_total: 300,
+
+ original_item_total: 1625,
+ original_item_subtotal: 1300,
+ original_item_tax_total: 325,
+
+ shipping_total: 0,
+ shipping_subtotal: 0,
+ shipping_tax_total: 0,
+
+ original_shipping_tax_total: 0,
+ original_shipping_subtotal: 0,
+ original_shipping_total: 0,
+
+ items: expect.arrayContaining([
+ expect.objectContaining({
+ quantity: 1,
+ unit_price: 1300,
+
+ subtotal: 1300,
+ total: 1500,
+ tax_total: 300,
+
+ discount_total: 125,
+ discount_subtotal: 100,
+ discount_tax_total: 25,
+
+ original_total: 1625,
+ original_tax_total: 325,
+
+ adjustments: expect.arrayContaining([
+ expect.objectContaining({
+ amount: 100,
+ is_tax_inclusive: false,
+ }),
+ ]),
+ }),
+ ]),
+ })
+ )
+ })
})
describe("DELETE /admin/promotions/:id", () => {
diff --git a/packages/admin/dashboard/src/i18n/translations/$schema.json b/packages/admin/dashboard/src/i18n/translations/$schema.json
index a65eb0ee97..a035252d62 100644
--- a/packages/admin/dashboard/src/i18n/translations/$schema.json
+++ b/packages/admin/dashboard/src/i18n/translations/$schema.json
@@ -7362,6 +7362,9 @@
"clearAll": {
"type": "string"
},
+ "taxInclusive": {
+ "type": "string"
+ },
"amount": {
"type": "object",
"properties": {
@@ -7428,6 +7431,7 @@
"allocation",
"addCondition",
"clearAll",
+ "taxInclusive",
"amount",
"conditions"
],
@@ -7626,6 +7630,19 @@
"required": ["existing", "new", "none"],
"additionalProperties": false
},
+ "taxInclusive": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "required": ["title", "description"],
+ "additionalProperties": false
+ },
"status": {
"type": "object",
"properties": {
@@ -7852,6 +7869,7 @@
"and",
"selectAttribute",
"campaign",
+ "taxInclusive",
"status",
"method",
"max_quantity",
diff --git a/packages/admin/dashboard/src/i18n/translations/en.json b/packages/admin/dashboard/src/i18n/translations/en.json
index ff86747a88..cd8993825c 100644
--- a/packages/admin/dashboard/src/i18n/translations/en.json
+++ b/packages/admin/dashboard/src/i18n/translations/en.json
@@ -1969,6 +1969,7 @@
"allocation": "Allocation",
"addCondition": "Add condition",
"clearAll": "Clear all",
+ "taxInclusive": "Tax Inclusive",
"amount": {
"tooltip": "Select the currency code to enable setting the amount"
},
@@ -2045,6 +2046,10 @@
"description": "Proceed without associating promotion with campaign"
}
},
+ "taxInclusive": {
+ "title": "Does promotion include taxes?",
+ "description": "Whether the promotion will be applied before or after taxes"
+ },
"status": {
"label": "Status",
"draft": {
diff --git a/packages/admin/dashboard/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx b/packages/admin/dashboard/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx
index 9401703e90..26722e5c2a 100644
--- a/packages/admin/dashboard/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx
+++ b/packages/admin/dashboard/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx
@@ -19,6 +19,7 @@ import {
ProgressStatus,
ProgressTabs,
RadioGroup,
+ Switch,
Text,
toast,
} from "@medusajs/ui"
@@ -52,6 +53,7 @@ const defaultValues = {
type: "standard" as PromotionTypeValues,
status: "draft" as PromotionStatusValues,
rules: [],
+ is_tax_inclusive: false,
application_method: {
allocation: "each" as ApplicationMethodAllocationValues,
type: "fixed" as ApplicationMethodTypeValues,
@@ -89,6 +91,7 @@ export const CreatePromotionForm = () => {
const {
campaign_choice: _campaignChoice,
is_automatic,
+ is_tax_inclusive,
template_id: _templateId,
application_method,
rules,
@@ -142,6 +145,7 @@ export const CreatePromotionForm = () => {
target_rules: buildRulesData(targetRulesData),
buy_rules: buildRulesData(buyRulesData),
},
+ is_tax_inclusive,
is_automatic: is_automatic === "true",
},
{
@@ -583,6 +587,49 @@ export const CreatePromotionForm = () => {
/>
+ {!currentTemplate?.hiddenFields?.includes(
+ "is_tax_inclusive"
+ ) && (
+ <>
+