feat(core-flows,types,utils,medusa): Translate tax lines (#14359)
* Include locale field for traslations on tax line workflows * Translate tax lines in getItemTaxLinesStep with new util * Update tax calculation context, so that we pass locale to third party tax providers if they want to return translated tax rates * Apply translations to tax lines on product and variant tax middlewares * Cart management translations tests * Update tax lines when order locale gets updated * Add changeset * Get tranlsated tax lines step * Fix wording * Mutate ref directly * Update order tax lines translations upon order locale change * Claims translations tests * Update tax lines upon draft order locale update * Exchange tests for tax lines translations * Order edits test for tax line translation * Add tests for shipping methods tax line translations on various order flows * Returns shipping method translations tests * Execute update in parallel * Use TranslationFeatureFlag.key * Fix feature flag import * Add @medusajs/medusa dependency for feature flag usage * Revert "Add @medusajs/medusa dependency for feature flag usage" This reverts commit e8897aed0a88f83c1034ac73e817e4222250a2c9. * Use feature flag string directly * Fix test * Parallelize tax line translations application
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
CartLineItemDTO,
|
||||
CartShippingMethodDTO,
|
||||
ItemTaxLineDTO,
|
||||
ShippingTaxLineDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
WorkflowData,
|
||||
@@ -12,11 +14,13 @@ import { useQueryGraphStep } from "../../common"
|
||||
import { acquireLockStep, releaseLockStep } from "../../locking"
|
||||
import { getItemTaxLinesStep } from "../../tax/steps/get-item-tax-lines"
|
||||
import { setTaxLinesForItemsStep, validateCartStep } from "../steps"
|
||||
import { getTranslatedTaxLinesStep } from "../../common/steps/get-translated-tax-lines"
|
||||
|
||||
const cartFields = [
|
||||
"id",
|
||||
"currency_code",
|
||||
"email",
|
||||
"locale",
|
||||
"region.id",
|
||||
"region.automatic_taxes",
|
||||
"items.id",
|
||||
@@ -161,10 +165,17 @@ export const updateTaxLinesWorkflow = createWorkflow(
|
||||
}))
|
||||
)
|
||||
|
||||
const translatedTaxLines = getTranslatedTaxLinesStep({
|
||||
itemTaxLines: taxLineItems.lineItemTaxLines,
|
||||
shippingTaxLines: taxLineItems.shippingMethodsTaxLines,
|
||||
locale: cart.locale,
|
||||
})
|
||||
|
||||
setTaxLinesForItemsStep({
|
||||
cart,
|
||||
item_tax_lines: taxLineItems.lineItemTaxLines,
|
||||
shipping_tax_lines: taxLineItems.shippingMethodsTaxLines,
|
||||
item_tax_lines: translatedTaxLines.itemTaxLines as ItemTaxLineDTO[],
|
||||
shipping_tax_lines:
|
||||
translatedTaxLines.shippingTaxLines as ShippingTaxLineDTO[],
|
||||
})
|
||||
|
||||
releaseLockStep({
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
CartLineItemDTO,
|
||||
CartShippingMethodDTO,
|
||||
ItemTaxLineDTO,
|
||||
ShippingTaxLineDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
WorkflowData,
|
||||
@@ -12,9 +14,11 @@ import { useQueryGraphStep } from "../../common"
|
||||
import { getItemTaxLinesStep } from "../../tax/steps/get-item-tax-lines"
|
||||
import { validateCartStep } from "../steps"
|
||||
import { upsertTaxLinesForItemsStep } from "../steps/upsert-tax-lines-for-items"
|
||||
import { getTranslatedTaxLinesStep } from "../../common/steps/get-translated-tax-lines"
|
||||
|
||||
const cartFields = [
|
||||
"id",
|
||||
"locale",
|
||||
"currency_code",
|
||||
"email",
|
||||
"region.id",
|
||||
@@ -153,10 +157,17 @@ export const upsertTaxLinesWorkflow = createWorkflow(
|
||||
}))
|
||||
)
|
||||
|
||||
const translatedTaxLines = getTranslatedTaxLinesStep({
|
||||
itemTaxLines: taxLineItems.lineItemTaxLines,
|
||||
shippingTaxLines: taxLineItems.shippingMethodsTaxLines,
|
||||
locale: cart.locale,
|
||||
})
|
||||
|
||||
upsertTaxLinesForItemsStep({
|
||||
cart,
|
||||
item_tax_lines: taxLineItems.lineItemTaxLines,
|
||||
shipping_tax_lines: taxLineItems.shippingMethodsTaxLines,
|
||||
item_tax_lines: translatedTaxLines.itemTaxLines as ItemTaxLineDTO[],
|
||||
shipping_tax_lines:
|
||||
translatedTaxLines.shippingTaxLines as ShippingTaxLineDTO[],
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ItemTaxLineDTO, ShippingTaxLineDTO } from "@medusajs/framework/types"
|
||||
import {
|
||||
applyTranslationsToTaxLines,
|
||||
FeatureFlag,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
export const getTranslatedTaxLinesStepId = "get-translated-tax-lines-step"
|
||||
|
||||
export interface GetTranslatedTaxLinesStepInput {
|
||||
itemTaxLines: ItemTaxLineDTO[]
|
||||
shippingTaxLines: ShippingTaxLineDTO[]
|
||||
locale: string
|
||||
}
|
||||
|
||||
export const getTranslatedTaxLinesStep = createStep(
|
||||
getTranslatedTaxLinesStepId,
|
||||
async (
|
||||
{ itemTaxLines, shippingTaxLines, locale }: GetTranslatedTaxLinesStepInput,
|
||||
{ container }
|
||||
) => {
|
||||
const isTranslationEnabled = FeatureFlag.isFeatureEnabled("translation")
|
||||
|
||||
if (!isTranslationEnabled) {
|
||||
return new StepResponse({
|
||||
itemTaxLines,
|
||||
shippingTaxLines,
|
||||
})
|
||||
}
|
||||
|
||||
const [translatedItemTaxLines, translatedShippingTaxLines] =
|
||||
await Promise.all([
|
||||
applyTranslationsToTaxLines(itemTaxLines, locale, container),
|
||||
applyTranslationsToTaxLines(shippingTaxLines, locale, container),
|
||||
])
|
||||
|
||||
return new StepResponse({
|
||||
itemTaxLines: translatedItemTaxLines,
|
||||
shippingTaxLines: translatedShippingTaxLines,
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
updateOrderShippingMethodsTranslationsStep,
|
||||
} from "../../order"
|
||||
import { validateDraftOrderStep } from "../steps/validate-draft-order"
|
||||
import { updateOrderTaxLinesTranslationsStep } from "../../order/steps/update-order-tax-lines-translations"
|
||||
|
||||
export const updateDraftOrderWorkflowId = "update-draft-order"
|
||||
|
||||
@@ -350,6 +351,10 @@ export const updateDraftOrderWorkflow = createWorkflow(
|
||||
locale: input.locale!,
|
||||
shippingMethods: order.shipping_methods,
|
||||
}),
|
||||
updateOrderTaxLinesTranslationsStep({
|
||||
order_id: input.id,
|
||||
locale: input.locale!,
|
||||
}),
|
||||
updateOrderItemsTranslationsStep({
|
||||
order_id: input.id,
|
||||
locale: input.locale!,
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
applyTranslations,
|
||||
ContainerRegistrationKeys,
|
||||
FeatureFlag,
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
export const updateOrderTaxLinesTranslationsStepId =
|
||||
"update-order-tax-lines-translations"
|
||||
|
||||
interface UpdateOrderTaxLinesTranslationsStepInput {
|
||||
order_id: string
|
||||
locale: string
|
||||
}
|
||||
|
||||
export const updateOrderTaxLinesTranslationsStep = createStep(
|
||||
updateOrderTaxLinesTranslationsStepId,
|
||||
async (data: UpdateOrderTaxLinesTranslationsStepInput, { container }) => {
|
||||
const query = container.resolve(ContainerRegistrationKeys.QUERY)
|
||||
|
||||
const isTranslationEnabled = FeatureFlag.isFeatureEnabled("translation")
|
||||
|
||||
if (!isTranslationEnabled || !data.locale) {
|
||||
return new StepResponse(void 0, [])
|
||||
}
|
||||
|
||||
const {
|
||||
data: [order],
|
||||
} = await query.graph({
|
||||
entity: "order",
|
||||
filters: { id: data.order_id },
|
||||
fields: [
|
||||
"items.tax_lines.id",
|
||||
"items.tax_lines.tax_rate_id",
|
||||
"items.tax_lines.description",
|
||||
"shipping_methods.tax_lines.id",
|
||||
"shipping_methods.tax_lines.tax_rate_id",
|
||||
"shipping_methods.tax_lines.description",
|
||||
],
|
||||
})
|
||||
|
||||
const orderModuleService = container.resolve(Modules.ORDER)
|
||||
|
||||
const originalItemTaxLines = order.items.flatMap((item) => item.tax_lines)
|
||||
const originalShippingMethodsTaxLines = order.shipping_methods.flatMap(
|
||||
(shippingMethod) => shippingMethod.tax_lines
|
||||
)
|
||||
|
||||
const translatedItemsTaxRates = originalItemTaxLines.map((taxLine) => ({
|
||||
id: taxLine.tax_rate_id,
|
||||
name: taxLine.description,
|
||||
tax_line_id: taxLine.id,
|
||||
}))
|
||||
|
||||
await applyTranslations({
|
||||
localeCode: data.locale,
|
||||
objects: translatedItemsTaxRates,
|
||||
container,
|
||||
})
|
||||
|
||||
const translatedShippingMethodsTaxRates =
|
||||
originalShippingMethodsTaxLines.map((taxLine) => ({
|
||||
id: taxLine.tax_rate_id,
|
||||
name: taxLine.description,
|
||||
tax_line_id: taxLine.id,
|
||||
}))
|
||||
|
||||
await applyTranslations({
|
||||
localeCode: data.locale,
|
||||
objects: translatedShippingMethodsTaxRates,
|
||||
container,
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
orderModuleService.upsertOrderLineItemTaxLines(
|
||||
translatedItemsTaxRates.map((taxRate) => ({
|
||||
id: taxRate.tax_line_id,
|
||||
description: taxRate.name,
|
||||
}))
|
||||
),
|
||||
orderModuleService.upsertOrderShippingMethodTaxLines(
|
||||
translatedShippingMethodsTaxRates.map((taxRate) => ({
|
||||
id: taxRate.tax_line_id,
|
||||
description: taxRate.name,
|
||||
}))
|
||||
),
|
||||
])
|
||||
|
||||
return new StepResponse(void 0, [
|
||||
originalItemTaxLines,
|
||||
originalShippingMethodsTaxLines,
|
||||
])
|
||||
},
|
||||
async (compensation, { container }) => {
|
||||
if (!compensation?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const [originalItemTaxLines, originalShippingMethodsTaxLines] = compensation
|
||||
|
||||
const orderModuleService = container.resolve(Modules.ORDER)
|
||||
|
||||
await Promise.all([
|
||||
orderModuleService.upsertOrderLineItemTaxLines(
|
||||
originalItemTaxLines.map((taxLine) => ({
|
||||
id: taxLine.id,
|
||||
description: taxLine.description,
|
||||
}))
|
||||
),
|
||||
orderModuleService.upsertOrderShippingMethodTaxLines(
|
||||
originalShippingMethodsTaxLines.map((taxLine) => ({
|
||||
id: taxLine.id,
|
||||
description: taxLine.description,
|
||||
}))
|
||||
),
|
||||
])
|
||||
}
|
||||
)
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "../steps"
|
||||
import { throwIfOrderIsCancelled } from "../utils/order-validation"
|
||||
import { findOrCreateCustomerStep } from "../../cart"
|
||||
import { updateOrderTaxLinesTranslationsStep } from "../steps/update-order-tax-lines-translations"
|
||||
|
||||
/**
|
||||
* The data to validate the order update.
|
||||
@@ -288,6 +289,10 @@ export const updateOrderWorkflow = createWorkflow(
|
||||
updateOrderShippingMethodsTranslationsStep({
|
||||
locale: input.locale!,
|
||||
shippingMethods: order.shipping_methods,
|
||||
}),
|
||||
updateOrderTaxLinesTranslationsStep({
|
||||
order_id: input.id,
|
||||
locale: input.locale!,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { OrderWorkflowDTO } from "@medusajs/framework/types"
|
||||
import type {
|
||||
ItemTaxLineDTO,
|
||||
OrderWorkflowDTO,
|
||||
ShippingTaxLineDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
createWorkflow,
|
||||
transform,
|
||||
@@ -9,11 +13,13 @@ import {
|
||||
import { useQueryGraphStep } from "../../common"
|
||||
import { getItemTaxLinesStep } from "../../tax/steps/get-item-tax-lines"
|
||||
import { setOrderTaxLinesForItemsStep } from "../steps"
|
||||
import { getTranslatedTaxLinesStep } from "../../common/steps/get-translated-tax-lines"
|
||||
|
||||
const completeOrderFields = [
|
||||
"id",
|
||||
"currency_code",
|
||||
"email",
|
||||
"locale",
|
||||
"region.id",
|
||||
"region.automatic_taxes",
|
||||
"items.id",
|
||||
@@ -65,6 +71,7 @@ const orderFields = [
|
||||
"id",
|
||||
"currency_code",
|
||||
"email",
|
||||
"locale",
|
||||
"region.id",
|
||||
"region.automatic_taxes",
|
||||
"shipping_methods.tax_lines.id",
|
||||
@@ -248,10 +255,17 @@ export const updateOrderTaxLinesWorkflow = createWorkflow(
|
||||
)
|
||||
)
|
||||
|
||||
const translatedTaxLines = getTranslatedTaxLinesStep({
|
||||
itemTaxLines: taxLineItems.lineItemTaxLines,
|
||||
shippingTaxLines: taxLineItems.shippingMethodsTaxLines,
|
||||
locale: order.locale,
|
||||
})
|
||||
|
||||
setOrderTaxLinesForItemsStep({
|
||||
order,
|
||||
item_tax_lines: taxLineItems.lineItemTaxLines,
|
||||
shipping_tax_lines: taxLineItems.shippingMethodsTaxLines,
|
||||
item_tax_lines: translatedTaxLines.itemTaxLines as ItemTaxLineDTO[],
|
||||
shipping_tax_lines:
|
||||
translatedTaxLines.shippingTaxLines as ShippingTaxLineDTO[],
|
||||
})
|
||||
|
||||
return new WorkflowResponse({
|
||||
|
||||
@@ -91,6 +91,7 @@ function normalizeTaxModuleContext(
|
||||
},
|
||||
customer,
|
||||
is_return: isReturn ?? false,
|
||||
locale: orderOrCart.locale,
|
||||
shipping_methods: orderOrCart.shipping_methods?.map((method) => ({
|
||||
id: method.id,
|
||||
name: method.name,
|
||||
|
||||
@@ -1136,7 +1136,7 @@ export interface OrderDTO {
|
||||
/**
|
||||
* The locale of the order.
|
||||
*/
|
||||
locale?: string | null
|
||||
locale?: string
|
||||
|
||||
/**
|
||||
* Holds custom data in key-value pairs.
|
||||
|
||||
@@ -421,6 +421,10 @@ export interface TaxableShippingDTO {
|
||||
* context is later passed to the underlying tax provider.
|
||||
*/
|
||||
export interface TaxCalculationContext {
|
||||
/**
|
||||
* The locale of the tax calculation.
|
||||
*/
|
||||
locale?: string
|
||||
/**
|
||||
* The customer's address
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { applyTranslations } from "./apply-translations"
|
||||
import {
|
||||
ItemTaxLineDTO,
|
||||
MedusaContainer,
|
||||
ShippingTaxLineDTO,
|
||||
} from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* Applies translations to tax lines. If you are using a tax provider that doesn't have TaxRates defined in the database,
|
||||
* you should apply the translations inside of your tax provider's `getTaxLines` method, using the `locale` provided in the context.
|
||||
*
|
||||
* @param taxLines - The tax lines to apply translations to.
|
||||
* @param locale - The locale to apply translations to.
|
||||
* @param container - The container to use for the translations.
|
||||
* @returns The tax lines with translations applied.
|
||||
*/
|
||||
export const applyTranslationsToTaxLines = async (
|
||||
taxLines: ItemTaxLineDTO[] | ShippingTaxLineDTO[],
|
||||
locale: string | undefined,
|
||||
container: MedusaContainer
|
||||
) => {
|
||||
const translatedTaxRates = taxLines.map(
|
||||
(taxLine: ItemTaxLineDTO | ShippingTaxLineDTO) => ({
|
||||
id: taxLine.rate_id,
|
||||
name: taxLine.name,
|
||||
})
|
||||
)
|
||||
|
||||
await applyTranslations({
|
||||
localeCode: locale,
|
||||
objects: translatedTaxRates,
|
||||
container,
|
||||
})
|
||||
|
||||
const rateTranslationMap = new Map<string, string>()
|
||||
for (const translatedRate of translatedTaxRates) {
|
||||
if (!!translatedRate.id) {
|
||||
rateTranslationMap.set(translatedRate.id, translatedRate.name)
|
||||
}
|
||||
}
|
||||
|
||||
for (const taxLine of taxLines) {
|
||||
if (taxLine.rate_id) {
|
||||
taxLine.name = rateTranslationMap.get(taxLine.rate_id)!
|
||||
}
|
||||
}
|
||||
|
||||
return taxLines
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./apply-translations"
|
||||
export * from "./apply-translations-to-tax-lines"
|
||||
|
||||
@@ -3,8 +3,14 @@ import {
|
||||
ItemTaxLineDTO,
|
||||
TaxableItemDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { calculateAmountsWithTax, Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
calculateAmountsWithTax,
|
||||
FeatureFlag,
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { StoreRequestWithContext } from "../types"
|
||||
import { applyTranslationsToTaxLines } from "@medusajs/framework/utils"
|
||||
import TranslationFeatureFlag from "../../../feature-flags/translation"
|
||||
|
||||
export const wrapVariantsWithTaxPrices = async <T>(
|
||||
req: StoreRequestWithContext<T>,
|
||||
@@ -31,11 +37,22 @@ export const wrapVariantsWithTaxPrices = async <T>(
|
||||
|
||||
const taxService = req.scope.resolve(Modules.TAX)
|
||||
|
||||
const taxLines = (await taxService.getTaxLines(
|
||||
let taxLines = (await taxService.getTaxLines(
|
||||
items,
|
||||
req.taxContext.taxLineContext
|
||||
)) as unknown as ItemTaxLineDTO[]
|
||||
|
||||
const isTranslationEnabled = FeatureFlag.isFeatureEnabled(
|
||||
TranslationFeatureFlag.key
|
||||
)
|
||||
if (isTranslationEnabled) {
|
||||
taxLines = (await applyTranslationsToTaxLines(
|
||||
taxLines,
|
||||
req.locale,
|
||||
req.scope
|
||||
)) as ItemTaxLineDTO[]
|
||||
}
|
||||
|
||||
const taxRatesMap = new Map<string, ItemTaxLineDTO[]>()
|
||||
|
||||
taxLines.forEach((taxLine) => {
|
||||
|
||||
@@ -5,8 +5,14 @@ import {
|
||||
MedusaContainer,
|
||||
TaxableItemDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { calculateAmountsWithTax, Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
applyTranslationsToTaxLines,
|
||||
calculateAmountsWithTax,
|
||||
FeatureFlag,
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { StoreRequestWithContext } from "../types"
|
||||
import TranslationFeatureFlag from "../../../feature-flags/translation"
|
||||
|
||||
export type RequestWithContext<
|
||||
Body,
|
||||
@@ -56,13 +62,24 @@ export const wrapProductsWithTaxPrices = async <T>(
|
||||
|
||||
const taxService = req.scope.resolve(Modules.TAX)
|
||||
|
||||
const taxRates = (await taxService.getTaxLines(
|
||||
let taxLines = (await taxService.getTaxLines(
|
||||
products.map(asTaxItem).flat(),
|
||||
req.taxContext.taxLineContext
|
||||
)) as unknown as ItemTaxLineDTO[]
|
||||
|
||||
const isTranslationEnabled = FeatureFlag.isFeatureEnabled(
|
||||
TranslationFeatureFlag.key
|
||||
)
|
||||
if (isTranslationEnabled) {
|
||||
taxLines = (await applyTranslationsToTaxLines(
|
||||
taxLines,
|
||||
req.locale,
|
||||
req.scope
|
||||
)) as ItemTaxLineDTO[]
|
||||
}
|
||||
|
||||
const taxRatesMap = new Map<string, ItemTaxLineDTO[]>()
|
||||
taxRates.forEach((taxRate) => {
|
||||
taxLines.forEach((taxRate) => {
|
||||
if (!taxRatesMap.has(taxRate.line_item_id)) {
|
||||
taxRatesMap.set(taxRate.line_item_id, [])
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ const getTaxLinesContext = async (req: MedusaRequest) => {
|
||||
country_code: req.filterableFields.country_code as string,
|
||||
province_code: req.filterableFields.province as string,
|
||||
},
|
||||
locale: req.locale,
|
||||
} as TaxCalculationContext
|
||||
|
||||
return taxContext
|
||||
|
||||
Reference in New Issue
Block a user