chore(cart, core-flows): Improve tax lines algo management (#11715)
Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
co-authored by
Adrien de Peretti
parent
4b3869ef2c
commit
b7678983a9
@@ -6,7 +6,7 @@ import {
|
||||
ItemTaxLineDTO,
|
||||
ShippingTaxLineDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { Modules, promiseAll } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
/**
|
||||
@@ -30,13 +30,13 @@ export interface SetTaxLinesForItemsStepInput {
|
||||
export const setTaxLinesForItemsStepId = "set-tax-lines-for-items"
|
||||
/**
|
||||
* This step sets the tax lines of shipping methods and line items in a cart.
|
||||
*
|
||||
*
|
||||
* :::tip
|
||||
*
|
||||
*
|
||||
* You can use the {@link retrieveCartStep} to retrieve a cart's details.
|
||||
*
|
||||
*
|
||||
* :::
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const data = setTaxLinesForItemsStep({
|
||||
* // retrieve the details of the cart from another workflow
|
||||
@@ -64,21 +64,31 @@ export const setTaxLinesForItemsStep = createStep(
|
||||
const { cart, item_tax_lines, shipping_tax_lines } = data
|
||||
const cartService = container.resolve<ICartModuleService>(Modules.CART)
|
||||
|
||||
const existingShippingMethodTaxLines =
|
||||
await cartService.listShippingMethodTaxLines({
|
||||
shipping_method_id: shipping_tax_lines.map((t) => t.shipping_line_id),
|
||||
})
|
||||
const [existingShippingMethodTaxLines, existingLineItemTaxLines] =
|
||||
await promiseAll([
|
||||
shipping_tax_lines.length
|
||||
? cartService.listShippingMethodTaxLines({
|
||||
shipping_method_id: shipping_tax_lines.map(
|
||||
(t) => t.shipping_line_id
|
||||
),
|
||||
})
|
||||
: [],
|
||||
|
||||
const existingLineItemTaxLines = await cartService.listLineItemTaxLines({
|
||||
item_id: item_tax_lines.map((t) => t.line_item_id),
|
||||
})
|
||||
item_tax_lines.length
|
||||
? cartService.listLineItemTaxLines({
|
||||
item_id: item_tax_lines.map((t) => t.line_item_id),
|
||||
})
|
||||
: [],
|
||||
])
|
||||
|
||||
const itemsTaxLinesData = normalizeItemTaxLinesForCart(item_tax_lines)
|
||||
await cartService.setLineItemTaxLines(cart.id, itemsTaxLinesData)
|
||||
|
||||
const shippingTaxLinesData =
|
||||
normalizeShippingTaxLinesForCart(shipping_tax_lines)
|
||||
await cartService.setShippingMethodTaxLines(cart.id, shippingTaxLinesData)
|
||||
|
||||
await promiseAll([
|
||||
cartService.setLineItemTaxLines(cart.id, itemsTaxLinesData),
|
||||
cartService.setShippingMethodTaxLines(cart.id, shippingTaxLinesData),
|
||||
])
|
||||
|
||||
return new StepResponse(null, {
|
||||
cart,
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import {
|
||||
CartWorkflowDTO,
|
||||
CreateLineItemTaxLineDTO,
|
||||
CreateShippingMethodTaxLineDTO,
|
||||
ICartModuleService,
|
||||
ItemTaxLineDTO,
|
||||
LineItemTaxLineDTO,
|
||||
ShippingMethodTaxLineDTO,
|
||||
ShippingTaxLineDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { Modules, promiseAll } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
/**
|
||||
* The details of the tax lines to set in a cart.
|
||||
*/
|
||||
export interface SetTaxLinesForItemsStepInput {
|
||||
/**
|
||||
* The cart's details.
|
||||
*/
|
||||
cart: CartWorkflowDTO
|
||||
/**
|
||||
* The tax lines to set for line items.
|
||||
*/
|
||||
item_tax_lines: ItemTaxLineDTO[]
|
||||
/**
|
||||
* The tax lines to set for shipping methods.
|
||||
*/
|
||||
shipping_tax_lines: ShippingTaxLineDTO[]
|
||||
}
|
||||
|
||||
export const upsertTaxLinesForItemsStepId = "set-tax-lines-for-items"
|
||||
/**
|
||||
* This step sets the tax lines of shipping methods and line items in a cart.
|
||||
*
|
||||
* :::tip
|
||||
*
|
||||
* You can use the {@link retrieveCartStep} to retrieve a cart's details.
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* @example
|
||||
* const data = upsertTaxLinesForItemsStep({
|
||||
* // retrieve the details of the cart from another workflow
|
||||
* // or in another step using the Cart Module's service
|
||||
* cart,
|
||||
* "item_tax_lines": [{
|
||||
* "rate": 48,
|
||||
* "code": "CODE123",
|
||||
* "name": "Tax rate 2",
|
||||
* "provider_id": "provider_1",
|
||||
* "line_item_id": "litem_123"
|
||||
* }],
|
||||
* "shipping_tax_lines": [{
|
||||
* "rate": 49,
|
||||
* "code": "CODE456",
|
||||
* "name": "Tax rate 1",
|
||||
* "provider_id": "provider_1",
|
||||
* "shipping_line_id": "sm_123"
|
||||
* }]
|
||||
* })
|
||||
*/
|
||||
export const upsertTaxLinesForItemsStep = createStep(
|
||||
upsertTaxLinesForItemsStepId,
|
||||
async (data: SetTaxLinesForItemsStepInput, { container }) => {
|
||||
const { cart, item_tax_lines, shipping_tax_lines } = data
|
||||
const cartService = container.resolve<ICartModuleService>(Modules.CART)
|
||||
|
||||
const [existingShippingMethodTaxLines, existingLineItemTaxLines] =
|
||||
await promiseAll([
|
||||
shipping_tax_lines.length
|
||||
? cartService.listShippingMethodTaxLines({
|
||||
shipping_method_id: shipping_tax_lines.map(
|
||||
(t) => t.shipping_line_id
|
||||
),
|
||||
})
|
||||
: [],
|
||||
|
||||
item_tax_lines.length
|
||||
? cartService.listLineItemTaxLines({
|
||||
item_id: item_tax_lines.map((t) => t.line_item_id),
|
||||
})
|
||||
: [],
|
||||
])
|
||||
|
||||
const itemsTaxLinesData = normalizeItemTaxLinesForCart(
|
||||
item_tax_lines,
|
||||
existingLineItemTaxLines
|
||||
)
|
||||
const shippingTaxLinesData = normalizeShippingTaxLinesForCart(
|
||||
shipping_tax_lines,
|
||||
existingShippingMethodTaxLines
|
||||
)
|
||||
|
||||
await promiseAll([
|
||||
itemsTaxLinesData.length
|
||||
? cartService.upsertLineItemTaxLines(itemsTaxLinesData)
|
||||
: [],
|
||||
shippingTaxLinesData.length
|
||||
? cartService.upsertShippingMethodTaxLines(shippingTaxLinesData)
|
||||
: [],
|
||||
])
|
||||
|
||||
return new StepResponse(null, {
|
||||
cart,
|
||||
existingLineItemTaxLines,
|
||||
existingShippingMethodTaxLines,
|
||||
})
|
||||
},
|
||||
async (revertData, { container }) => {
|
||||
if (!revertData) {
|
||||
return
|
||||
}
|
||||
|
||||
const { existingLineItemTaxLines, existingShippingMethodTaxLines } =
|
||||
revertData
|
||||
|
||||
const cartService = container.resolve<ICartModuleService>(Modules.CART)
|
||||
|
||||
if (existingLineItemTaxLines) {
|
||||
await cartService.upsertLineItemTaxLines(
|
||||
existingLineItemTaxLines.map((taxLine) => ({
|
||||
description: taxLine.description,
|
||||
tax_rate_id: taxLine.tax_rate_id,
|
||||
code: taxLine.code,
|
||||
rate: taxLine.rate,
|
||||
provider_id: taxLine.provider_id,
|
||||
item_id: taxLine.item_id,
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
await cartService.upsertShippingMethodTaxLines(
|
||||
existingShippingMethodTaxLines.map((taxLine) => ({
|
||||
description: taxLine.description,
|
||||
tax_rate_id: taxLine.tax_rate_id,
|
||||
code: taxLine.code,
|
||||
rate: taxLine.rate,
|
||||
provider_id: taxLine.provider_id,
|
||||
shipping_method_id: taxLine.shipping_method_id,
|
||||
}))
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
function normalizeItemTaxLinesForCart(
|
||||
taxLines: ItemTaxLineDTO[],
|
||||
existingTaxLines: LineItemTaxLineDTO[]
|
||||
): CreateLineItemTaxLineDTO[] {
|
||||
return taxLines.map((taxLine: ItemTaxLineDTO & { id?: string }) => ({
|
||||
id: existingTaxLines.find((t) => t.item_id === taxLine.line_item_id)?.id,
|
||||
description: taxLine.name,
|
||||
tax_rate_id: taxLine.rate_id,
|
||||
code: taxLine.code!,
|
||||
rate: taxLine.rate!,
|
||||
provider_id: taxLine.provider_id,
|
||||
item_id: taxLine.line_item_id,
|
||||
}))
|
||||
}
|
||||
|
||||
function normalizeShippingTaxLinesForCart(
|
||||
taxLines: ShippingTaxLineDTO[],
|
||||
existingTaxLines: ShippingMethodTaxLineDTO[]
|
||||
): CreateShippingMethodTaxLineDTO[] {
|
||||
return taxLines.map((taxLine: ShippingTaxLineDTO & { id?: string }) => ({
|
||||
id: existingTaxLines.find(
|
||||
(t) => t.shipping_method_id === taxLine.shipping_line_id
|
||||
)?.id,
|
||||
description: taxLine.name,
|
||||
tax_rate_id: taxLine.rate_id,
|
||||
code: taxLine.code!,
|
||||
rate: taxLine.rate!,
|
||||
provider_id: taxLine.provider_id,
|
||||
shipping_method_id: taxLine.shipping_line_id,
|
||||
}))
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export interface AddShippingMethodToCartWorkflowInput {
|
||||
id: string
|
||||
/**
|
||||
* Custom data useful for the fulfillment provider processing the shipping option or method.
|
||||
*
|
||||
*
|
||||
* Learn more in [this documentation](https://docs.medusajs.com/resources/commerce-modules/fulfillment/shipping-option#data-property).
|
||||
*/
|
||||
data?: Record<string, unknown>
|
||||
@@ -48,11 +48,11 @@ export interface AddShippingMethodToCartWorkflowInput {
|
||||
|
||||
export const addShippingMethodToCartWorkflowId = "add-shipping-method-to-cart"
|
||||
/**
|
||||
* This workflow adds a shipping method to a cart. It's executed by the
|
||||
* This workflow adds a shipping method to a cart. It's executed by the
|
||||
* [Add Shipping Method Store API Route](https://docs.medusajs.com/api/store#carts_postcartsidshippingmethods).
|
||||
*
|
||||
*
|
||||
* You can use this workflow within your own customizations or custom workflows, allowing you to wrap custom logic around adding a shipping method to the cart.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await addShippingMethodToCartWorkflow(container)
|
||||
* .run({
|
||||
@@ -71,11 +71,11 @@ export const addShippingMethodToCartWorkflowId = "add-shipping-method-to-cart"
|
||||
* ]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Add a shipping method to a cart.
|
||||
*
|
||||
*
|
||||
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
|
||||
*/
|
||||
export const addShippingMethodToCartWorkflow = createWorkflow(
|
||||
@@ -186,7 +186,7 @@ export const addShippingMethodToCartWorkflow = createWorkflow(
|
||||
cart.shipping_methods.map((sm) => sm.id)
|
||||
)
|
||||
|
||||
parallelize(
|
||||
const [, createdShippingMethods] = parallelize(
|
||||
removeShippingMethodFromCartStep({
|
||||
shipping_method_ids: currentShippingMethods,
|
||||
}),
|
||||
@@ -200,7 +200,7 @@ export const addShippingMethodToCartWorkflow = createWorkflow(
|
||||
)
|
||||
|
||||
refreshCartItemsWorkflow.runAsStep({
|
||||
input: { cart_id: cart.id },
|
||||
input: { cart_id: cart.id, shipping_methods: createdShippingMethods },
|
||||
})
|
||||
|
||||
return new WorkflowResponse(void 0, {
|
||||
|
||||
@@ -173,7 +173,7 @@ export const addToCartWorkflow = createWorkflow(
|
||||
},
|
||||
})
|
||||
|
||||
parallelize(
|
||||
const [createdLineItems, updatedLineItems] = parallelize(
|
||||
createLineItemsStep({
|
||||
id: cart.id,
|
||||
items: itemsToCreate,
|
||||
@@ -184,8 +184,15 @@ export const addToCartWorkflow = createWorkflow(
|
||||
})
|
||||
)
|
||||
|
||||
const allItems = transform(
|
||||
{ createdLineItems, updatedLineItems },
|
||||
({ createdLineItems = [], updatedLineItems = [] }) => {
|
||||
return createdLineItems.concat(updatedLineItems)
|
||||
}
|
||||
)
|
||||
|
||||
refreshCartItemsWorkflow.runAsStep({
|
||||
input: { cart_id: cart.id },
|
||||
input: { cart_id: cart.id, items: allItems },
|
||||
})
|
||||
|
||||
emitEventStep({
|
||||
|
||||
@@ -26,6 +26,7 @@ import { refreshCartShippingMethodsWorkflow } from "./refresh-cart-shipping-meth
|
||||
import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection"
|
||||
import { updateCartPromotionsWorkflow } from "./update-cart-promotions"
|
||||
import { updateTaxLinesWorkflow } from "./update-tax-lines"
|
||||
import { upsertTaxLinesWorkflow } from "./upsert-tax-lines"
|
||||
|
||||
/**
|
||||
* The details of the cart to refresh.
|
||||
@@ -44,6 +45,23 @@ export type RefreshCartItemsWorkflowInput = {
|
||||
* Force refresh the cart items
|
||||
*/
|
||||
force_refresh?: boolean
|
||||
|
||||
/**
|
||||
* The items to refresh.
|
||||
*/
|
||||
items?: any[]
|
||||
|
||||
/**
|
||||
* The shipping methods to refresh.
|
||||
*/
|
||||
shipping_methods?: any[]
|
||||
|
||||
/**
|
||||
* Whether to force re-calculating tax amounts, which
|
||||
* may include sending requests to a third-part tax provider, depending
|
||||
* on the configurations of the cart's tax region.
|
||||
*/
|
||||
force_tax_calculation?: boolean
|
||||
}
|
||||
|
||||
export const refreshCartItemsWorkflowId = "refresh-cart-items"
|
||||
@@ -162,8 +180,33 @@ export const refreshCartItemsWorkflow = createWorkflow(
|
||||
input: refreshCartInput,
|
||||
})
|
||||
|
||||
updateTaxLinesWorkflow.runAsStep({
|
||||
input: refreshCartInput,
|
||||
when({ input }, ({ input }) => {
|
||||
return !!input.force_refresh
|
||||
}).then(() => {
|
||||
updateTaxLinesWorkflow.runAsStep({
|
||||
input: refreshCartInput,
|
||||
})
|
||||
})
|
||||
|
||||
when({ input }, ({ input }) => {
|
||||
return (
|
||||
!input.force_refresh &&
|
||||
(!!input.items?.length || !!input.shipping_methods?.length)
|
||||
)
|
||||
}).then(() => {
|
||||
upsertTaxLinesWorkflow.runAsStep({
|
||||
input: transform(
|
||||
{ refetchedCart, input },
|
||||
({ refetchedCart, input }) => {
|
||||
return {
|
||||
cart: refetchedCart,
|
||||
items: input.items ?? [],
|
||||
shipping_methods: input.shipping_methods ?? [],
|
||||
force_tax_calculation: input.force_tax_calculation,
|
||||
}
|
||||
}
|
||||
),
|
||||
})
|
||||
})
|
||||
|
||||
const cartPromoCodes = transform(
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import {
|
||||
CartLineItemDTO,
|
||||
CartShippingMethodDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
WorkflowData,
|
||||
createWorkflow,
|
||||
transform,
|
||||
when,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { useRemoteQueryStep } from "../../common"
|
||||
import { getItemTaxLinesStep } from "../../tax/steps/get-item-tax-lines"
|
||||
import { upsertTaxLinesForItemsStep } from "../steps/upsert-tax-lines-for-items"
|
||||
|
||||
const cartFields = [
|
||||
"id",
|
||||
"currency_code",
|
||||
"email",
|
||||
"region.id",
|
||||
"region.automatic_taxes",
|
||||
"items.id",
|
||||
"items.variant_id",
|
||||
"items.product_id",
|
||||
"items.product_title",
|
||||
"items.product_description",
|
||||
"items.product_subtitle",
|
||||
"items.product_type",
|
||||
"items.product_type_id",
|
||||
"items.product_collection",
|
||||
"items.product_handle",
|
||||
"items.variant_sku",
|
||||
"items.variant_barcode",
|
||||
"items.variant_title",
|
||||
"items.title",
|
||||
"items.quantity",
|
||||
"items.unit_price",
|
||||
"items.tax_lines.id",
|
||||
"items.tax_lines.description",
|
||||
"items.tax_lines.code",
|
||||
"items.tax_lines.rate",
|
||||
"items.tax_lines.provider_id",
|
||||
"shipping_methods.tax_lines.id",
|
||||
"shipping_methods.tax_lines.description",
|
||||
"shipping_methods.tax_lines.code",
|
||||
"shipping_methods.tax_lines.rate",
|
||||
"shipping_methods.tax_lines.provider_id",
|
||||
"shipping_methods.shipping_option_id",
|
||||
"shipping_methods.amount",
|
||||
"customer.id",
|
||||
"customer.email",
|
||||
"customer.metadata",
|
||||
"customer.groups.id",
|
||||
"shipping_address.id",
|
||||
"shipping_address.address_1",
|
||||
"shipping_address.address_2",
|
||||
"shipping_address.city",
|
||||
"shipping_address.postal_code",
|
||||
"shipping_address.country_code",
|
||||
"shipping_address.region_code",
|
||||
"shipping_address.province",
|
||||
"shipping_address.metadata",
|
||||
]
|
||||
|
||||
/**
|
||||
* The details of the cart to upsert tax lines for.
|
||||
*/
|
||||
export type UpsertTaxLinesWorkflowInput = {
|
||||
/**
|
||||
* The cart's ID.
|
||||
*/
|
||||
cart_id?: string
|
||||
/**
|
||||
* The Cart reference.
|
||||
*/
|
||||
cart?: any
|
||||
/**
|
||||
* The items to upsert their tax lines.
|
||||
* If not specified, taxes are upsertd for all of the cart's
|
||||
* line items.
|
||||
*/
|
||||
items: CartLineItemDTO[]
|
||||
/**
|
||||
* The shipping methods to upsert their tax lines.
|
||||
* If not specified, taxes are upsertd for all of the cart's
|
||||
* shipping methods.
|
||||
*/
|
||||
shipping_methods: CartShippingMethodDTO[]
|
||||
|
||||
/**
|
||||
* Whether to force re-calculating tax amounts, which
|
||||
* may include sending requests to a third-part tax provider, depending
|
||||
* on the configurations of the cart's tax region.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
force_tax_calculation?: boolean
|
||||
}
|
||||
|
||||
export const upsertTaxLinesWorkflowId = "upsert-tax-lines"
|
||||
/**
|
||||
* This workflow upserts a cart's tax lines that are applied on line items and shipping methods. You can upsert the line item's quantity, unit price, and more. This workflow is executed
|
||||
* by the [Calculate Taxes Store API Route](https://docs.medusajs.com/api/store#carts_postcartsidtaxes).
|
||||
*
|
||||
* You can use this workflow within your own customizations or custom workflows, allowing you to upsert a cart's tax lines in your custom flows.
|
||||
*
|
||||
* @example
|
||||
* const { result } = await upsertTaxLinesWorkflow(container)
|
||||
* .run({
|
||||
* input: {
|
||||
* cart_id: "cart_123",
|
||||
* items: [],
|
||||
* shipping_methods: [],
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
* Update a cart's tax lines.
|
||||
*/
|
||||
export const upsertTaxLinesWorkflow = createWorkflow(
|
||||
upsertTaxLinesWorkflowId,
|
||||
(input: WorkflowData<UpsertTaxLinesWorkflowInput>): WorkflowData<void> => {
|
||||
const fetchCart = when({ input }, ({ input }) => {
|
||||
return !input.cart
|
||||
}).then(() => {
|
||||
return useRemoteQueryStep({
|
||||
entry_point: "cart",
|
||||
fields: cartFields,
|
||||
variables: { id: input.cart_id },
|
||||
throw_if_key_not_found: true,
|
||||
list: false,
|
||||
})
|
||||
})
|
||||
|
||||
const cart = transform({ fetchCart, input }, ({ fetchCart, input }) => {
|
||||
return input.cart ?? fetchCart
|
||||
})
|
||||
|
||||
const taxLineItems = getItemTaxLinesStep(
|
||||
transform({ input, cart }, (data) => ({
|
||||
orderOrCart: data.cart,
|
||||
items: data.input.items ?? [],
|
||||
shipping_methods: data.input.shipping_methods ?? [],
|
||||
force_tax_calculation: data.input.force_tax_calculation,
|
||||
}))
|
||||
)
|
||||
|
||||
upsertTaxLinesForItemsStep({
|
||||
cart,
|
||||
item_tax_lines: taxLineItems.lineItemTaxLines,
|
||||
shipping_tax_lines: taxLineItems.shippingMethodsTaxLines,
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -129,14 +129,14 @@ function normalizeLineItemsForShipping(
|
||||
export const getItemTaxLinesStepId = "get-item-tax-lines"
|
||||
/**
|
||||
* This step retrieves the tax lines for an order or cart's line items and shipping methods.
|
||||
*
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
*
|
||||
* You can retrieve an order, cart, item, shipping method, and address details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
|
||||
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
|
||||
*
|
||||
*
|
||||
* :::
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const data = getItemTaxLinesStep({
|
||||
* orderOrCart: {
|
||||
|
||||
Reference in New Issue
Block a user