feat(core-flows,types,order,cart): assign tax lines only to regular products (#11994)
what: - assign tax lines only to regular products
This commit is contained in:
8
.changeset/shaggy-cooks-wave.md
Normal file
8
.changeset/shaggy-cooks-wave.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@medusajs/core-flows": patch
|
||||
"@medusajs/order": patch
|
||||
"@medusajs/cart": patch
|
||||
"@medusajs/types": patch
|
||||
---
|
||||
|
||||
feat(core-flows,types,order,cart): assign tax lines only to regular products
|
||||
@@ -2001,6 +2001,72 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
})
|
||||
|
||||
it("should not generate tax lines for gift card products", async () => {
|
||||
const giftCardProduct = (
|
||||
await api.post(
|
||||
`/admin/products`,
|
||||
{
|
||||
title: "Gift Card",
|
||||
description: "test",
|
||||
is_giftcard: true,
|
||||
options: [
|
||||
{
|
||||
title: "Denomination",
|
||||
values: ["10", "20", "50", "100"],
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "10",
|
||||
sku: "special-shirt",
|
||||
options: {
|
||||
Denomination: "10",
|
||||
},
|
||||
manage_inventory: false,
|
||||
prices: [
|
||||
{
|
||||
amount: 1000,
|
||||
currency_code: "usd",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
|
||||
let updated = await api.post(
|
||||
`/store/carts/${cart.id}/line-items?fields=+items.is_giftcard`,
|
||||
{ variant_id: giftCardProduct.variants[0].id, quantity: 1 },
|
||||
storeHeaders
|
||||
)
|
||||
|
||||
expect(updated.status).toEqual(200)
|
||||
expect(updated.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
is_giftcard: false,
|
||||
tax_lines: [
|
||||
expect.objectContaining({
|
||||
description: "CA Default Rate",
|
||||
code: "CADEFAULT",
|
||||
rate: 5,
|
||||
provider_id: "system",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
is_giftcard: true,
|
||||
tax_lines: [],
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should update a cart's region, sales channel, customer data and tax lines", async () => {
|
||||
const newSalesChannel = (
|
||||
await api.post(
|
||||
|
||||
@@ -820,6 +820,7 @@ medusaIntegrationTestRunner({
|
||||
deleted_at: null,
|
||||
id: expect.any(String),
|
||||
is_discountable: false,
|
||||
is_giftcard: false,
|
||||
is_tax_inclusive: false,
|
||||
is_custom_price: true,
|
||||
metadata: {
|
||||
@@ -1179,6 +1180,7 @@ medusaIntegrationTestRunner({
|
||||
// Custom line item
|
||||
id: expect.any(String),
|
||||
is_discountable: false,
|
||||
is_giftcard: false,
|
||||
is_tax_inclusive: false,
|
||||
is_custom_price: true,
|
||||
quantity: 1,
|
||||
@@ -1415,6 +1417,7 @@ medusaIntegrationTestRunner({
|
||||
deleted_at: null,
|
||||
id: expect.any(String),
|
||||
is_discountable: false,
|
||||
is_giftcard: false,
|
||||
is_tax_inclusive: false,
|
||||
is_custom_price: true,
|
||||
metadata: {
|
||||
@@ -1876,6 +1879,7 @@ medusaIntegrationTestRunner({
|
||||
deleted_at: null,
|
||||
id: expect.any(String),
|
||||
is_discountable: false,
|
||||
is_giftcard: false,
|
||||
is_tax_inclusive: false,
|
||||
is_custom_price: true,
|
||||
metadata: {
|
||||
@@ -1937,6 +1941,7 @@ medusaIntegrationTestRunner({
|
||||
deleted_at: null,
|
||||
id: expect.any(String),
|
||||
is_discountable: false,
|
||||
is_giftcard: false,
|
||||
is_tax_inclusive: false,
|
||||
is_custom_price: true,
|
||||
metadata: {
|
||||
|
||||
@@ -172,6 +172,7 @@ medusaIntegrationTestRunner({
|
||||
variant_option_values: null,
|
||||
requires_shipping: true,
|
||||
is_discountable: true,
|
||||
is_giftcard: false,
|
||||
is_tax_inclusive: false,
|
||||
raw_compare_at_unit_price: null,
|
||||
raw_unit_price: {
|
||||
|
||||
@@ -16,6 +16,7 @@ export const cartFieldsForRefreshSteps = [
|
||||
"region.*",
|
||||
"items.*",
|
||||
"items.product.id",
|
||||
"items.product.is_giftcard",
|
||||
"items.product.collection_id",
|
||||
"items.product.categories.id",
|
||||
"items.product.tags.id",
|
||||
@@ -105,6 +106,7 @@ export const completeCartFields = [
|
||||
"payment_collection.payment_sessions.*",
|
||||
"items.variant.id",
|
||||
"items.variant.product.id",
|
||||
"items.variant.product.is_giftcard",
|
||||
"items.variant.product.shipping_profile.id",
|
||||
"items.variant.manage_inventory",
|
||||
"items.variant.allow_backorder",
|
||||
@@ -157,6 +159,7 @@ export const productVariantsFields = [
|
||||
"product.collection.title",
|
||||
"product.handle",
|
||||
"product.discountable",
|
||||
"product.is_giftcard",
|
||||
"product.shipping_profile.id",
|
||||
"calculated_price.*",
|
||||
"inventory_items.inventory_item_id",
|
||||
|
||||
@@ -150,6 +150,7 @@ export function prepareLineItemData(data: PrepareLineItemDataInput) {
|
||||
variant_option_values: item?.variant_option_values,
|
||||
|
||||
is_discountable: variant?.product?.discountable ?? item?.is_discountable,
|
||||
is_giftcard: variant?.product?.is_giftcard ?? false,
|
||||
requires_shipping: requiresShipping,
|
||||
|
||||
unit_price: unitPrice,
|
||||
|
||||
@@ -21,6 +21,7 @@ const cartFields = [
|
||||
"items.id",
|
||||
"items.variant_id",
|
||||
"items.product_id",
|
||||
"items.product.is_giftcard",
|
||||
"items.product_title",
|
||||
"items.product_description",
|
||||
"items.product_subtitle",
|
||||
|
||||
@@ -86,13 +86,13 @@ export const createOrdersWorkflowId = "create-orders"
|
||||
/**
|
||||
* This workflow creates an order. It's used by the [Create Draft Order Admin API Route](https://docs.medusajs.com/api/admin#draft-orders_postdraftorders), but
|
||||
* you can also use it to create any order.
|
||||
*
|
||||
* This workflow has a hook that allows you to perform custom actions on the created order. For example, you can pass under `additional_data` custom data that
|
||||
*
|
||||
* This workflow has a hook that allows you to perform custom actions on the created order. For example, you can pass under `additional_data` custom data that
|
||||
* allows you to create custom data models linked to the order.
|
||||
*
|
||||
*
|
||||
* You can also use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around creating an order. For example,
|
||||
* you can create a workflow that imports orders from an external system, then uses this workflow to create the orders in Medusa.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await createOrderWorkflow(container)
|
||||
* .run({
|
||||
@@ -121,11 +121,11 @@ export const createOrdersWorkflowId = "create-orders"
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Create an order.
|
||||
*
|
||||
*
|
||||
* @property hooks.orderCreated - This hook is executed after the order is created. You can consume this hook to perform custom actions on the created order.
|
||||
*/
|
||||
export const createOrderWorkflow = createWorkflow(
|
||||
|
||||
@@ -17,6 +17,7 @@ const completeOrderFields = [
|
||||
"region.automatic_taxes",
|
||||
"items.id",
|
||||
"items.is_tax_inclusive",
|
||||
"items.is_giftcard",
|
||||
"items.variant_id",
|
||||
"items.product_id",
|
||||
"items.product_title",
|
||||
@@ -101,6 +102,7 @@ const lineItemFields = [
|
||||
"variant_id",
|
||||
"product_id",
|
||||
"is_tax_inclusive",
|
||||
"is_giftcard",
|
||||
"product_title",
|
||||
"product_description",
|
||||
"product_subtitle",
|
||||
@@ -137,7 +139,7 @@ export type UpdateOrderTaxLinesWorkflowInput = {
|
||||
shipping_method_ids?: string[]
|
||||
/**
|
||||
* Whether to force the tax calculation. If enabled, the tax provider
|
||||
* may send request to a third-party service to retrieve the calculated
|
||||
* may send request to a third-party service to retrieve the calculated
|
||||
* tax rates. This depends on the chosen tax provider in the order's tax region.
|
||||
*/
|
||||
force_tax_calculation?: boolean
|
||||
@@ -156,10 +158,10 @@ export const updateOrderTaxLinesWorkflowId = "update-order-tax-lines"
|
||||
* This workflow updates the tax lines of items and shipping methods in an order. It's used by
|
||||
* other order-related workflows, such as the {@link createOrderWorkflow} to set the order's
|
||||
* tax lines.
|
||||
*
|
||||
*
|
||||
* You can use this workflow within your customizations or your own custom workflows, allowing you to update an
|
||||
* order's tax lines in your custom flows.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await updateOrderTaxLinesWorkflow(container)
|
||||
* .run({
|
||||
@@ -168,9 +170,9 @@ export const updateOrderTaxLinesWorkflowId = "update-order-tax-lines"
|
||||
* item_ids: ["orli_123", "orli_456"],
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Update the tax lines of items and shipping methods in an order.
|
||||
*/
|
||||
export const updateOrderTaxLinesWorkflow = createWorkflow(
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
TaxableShippingDTO,
|
||||
TaxCalculationContext,
|
||||
} from "@medusajs/framework/types"
|
||||
import { MedusaError, Modules } from "@medusajs/framework/utils"
|
||||
import { isDefined, MedusaError, Modules } from "@medusajs/framework/utils"
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
/**
|
||||
@@ -168,6 +168,11 @@ export const getItemTaxLinesStep = createStep(
|
||||
is_return: isReturn = false,
|
||||
shipping_address: shippingAddress,
|
||||
} = data
|
||||
|
||||
const filteredItems = items.filter(
|
||||
(item) => !item.is_giftcard || !isDefined(item.is_giftcard)
|
||||
) as OrderLineItemDTO[] | CartLineItemDTO[]
|
||||
|
||||
const taxService = container.resolve<ITaxModuleService>(Modules.TAX)
|
||||
|
||||
const taxContext = normalizeTaxModuleContext(
|
||||
@@ -188,7 +193,7 @@ export const getItemTaxLinesStep = createStep(
|
||||
|
||||
if (items.length) {
|
||||
stepResponseData.lineItemTaxLines = (await taxService.getTaxLines(
|
||||
normalizeLineItemsForTax(orderOrCart, items),
|
||||
normalizeLineItemsForTax(orderOrCart, filteredItems),
|
||||
taxContext
|
||||
)) as ItemTaxLineDTO[]
|
||||
}
|
||||
|
||||
@@ -658,6 +658,11 @@ export interface CartLineItemDTO extends CartLineItemTotalsDTO {
|
||||
*/
|
||||
is_discountable: boolean
|
||||
|
||||
/**
|
||||
* Whether the line item is a gift card.
|
||||
*/
|
||||
is_giftcard: boolean
|
||||
|
||||
/**
|
||||
* Whether the line item price is tax inclusive.
|
||||
*/
|
||||
|
||||
@@ -789,6 +789,11 @@ export interface OrderLineItemDTO extends OrderLineItemTotalsDTO {
|
||||
*/
|
||||
is_discountable: boolean
|
||||
|
||||
/**
|
||||
* Indicates whether the line item is a gift card.
|
||||
*/
|
||||
is_giftcard: boolean
|
||||
|
||||
/**
|
||||
* Indicates whether the line item price is tax inclusive.
|
||||
*/
|
||||
|
||||
@@ -2886,6 +2886,7 @@ moduleIntegrationTestRunner<ICartModuleService>({
|
||||
variant_option_values: null,
|
||||
requires_shipping: true,
|
||||
is_discountable: true,
|
||||
is_giftcard: false,
|
||||
is_tax_inclusive: false,
|
||||
is_custom_price: false,
|
||||
raw_compare_at_unit_price: null,
|
||||
@@ -2992,6 +2993,7 @@ moduleIntegrationTestRunner<ICartModuleService>({
|
||||
variant_option_values: null,
|
||||
requires_shipping: true,
|
||||
is_discountable: true,
|
||||
is_giftcard: false,
|
||||
is_tax_inclusive: false,
|
||||
is_custom_price: false,
|
||||
raw_compare_at_unit_price: null,
|
||||
|
||||
@@ -757,6 +757,16 @@
|
||||
"default": "true",
|
||||
"mappedType": "boolean"
|
||||
},
|
||||
"is_giftcard": {
|
||||
"name": "is_giftcard",
|
||||
"type": "boolean",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "false",
|
||||
"mappedType": "boolean"
|
||||
},
|
||||
"is_tax_inclusive": {
|
||||
"name": "is_tax_inclusive",
|
||||
"type": "boolean",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20250326151602 extends Migration {
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(
|
||||
`alter table if exists "cart_line_item" add column if not exists "is_giftcard" boolean not null default false;`
|
||||
)
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(
|
||||
`alter table if exists "cart_line_item" drop column if exists "is_giftcard";`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ const LineItem = model
|
||||
variant_option_values: model.json().nullable(),
|
||||
requires_shipping: model.boolean().default(true),
|
||||
is_discountable: model.boolean().default(true),
|
||||
is_giftcard: model.boolean().default(false),
|
||||
is_tax_inclusive: model.boolean().default(false),
|
||||
is_custom_price: model.boolean().default(false),
|
||||
compare_at_unit_price: model.bigNumber().nullable(),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20250326151554 extends Migration {
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(
|
||||
`alter table if exists "order_line_item" add column if not exists "is_giftcard" boolean not null default false;`
|
||||
)
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(
|
||||
`alter table if exists "order_line_item" drop column if exists "is_giftcard";`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ const _OrderLineItem = model
|
||||
variant_title: model.text().nullable(),
|
||||
variant_option_values: model.json().nullable(),
|
||||
requires_shipping: model.boolean().default(true),
|
||||
is_giftcard: model.boolean().default(false),
|
||||
is_discountable: model.boolean().default(true),
|
||||
is_tax_inclusive: model.boolean().default(false),
|
||||
compare_at_unit_price: model.bigNumber().nullable(),
|
||||
|
||||
Reference in New Issue
Block a user