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:
Carlos R. L. RodriguesandAdrien de Peretti authored and GitHub committed 2025-03-09 12:43:18 +00:00
1 parent 4b3869ef2c
commit b7678983a9
12 files changed
+1051 -144

No files matched your search

@@ -2272,6 +2272,343 @@ moduleIntegrationTestRunner<ICartModuleService>({
})
})
describe("setShippingMethodTaxLines", () => {
it("should set shipping item tax lines for a cart", async () => {
const [createdCart] = await service.createCarts([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addShippingMethods(createdCart.id, [
{
name: "test",
amount: 100,
},
])
const [itemTwo] = await service.addShippingMethods(createdCart.id, [
{
name: "test-2",
amount: 200,
},
])
const taxLines = await service.setShippingMethodTaxLines(
createdCart.id,
[
{
shipping_method_id: itemOne.id,
rate: 20,
code: "TX",
},
{
shipping_method_id: itemTwo.id,
rate: 20,
code: "TX",
},
]
)
expect(taxLines).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_method_id: itemOne.id,
rate: 20,
code: "TX",
}),
expect.objectContaining({
shipping_method_id: itemTwo.id,
rate: 20,
code: "TX",
}),
])
)
})
it("should replace shipping item tax lines for a cart", async () => {
const [createdCart] = await service.createCarts([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addShippingMethods(createdCart.id, [
{
name: "test",
amount: 100,
},
])
const taxLines = await service.setShippingMethodTaxLines(
createdCart.id,
[
{
shipping_method_id: itemOne.id,
rate: 20.753,
code: "TX",
},
]
)
expect(taxLines).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_method_id: itemOne.id,
rate: 20.753,
code: "TX",
}),
])
)
await service.setShippingMethodTaxLines(createdCart.id, [
{
shipping_method_id: itemOne.id,
rate: 25.14789,
code: "TX-2",
},
])
const cart = await service.retrieveCart(createdCart.id, {
relations: ["shipping_methods.tax_lines"],
})
expect(cart.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: itemOne.id,
tax_lines: expect.arrayContaining([
expect.objectContaining({
shipping_method_id: itemOne.id,
rate: 25.14789,
code: "TX-2",
}),
]),
}),
])
)
expect(cart.shipping_methods?.length).toBe(1)
expect(cart.shipping_methods?.[0].tax_lines?.length).toBe(1)
})
it("should remove all shipping item tax lines for a cart", async () => {
const [createdCart] = await service.createCarts([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addShippingMethods(createdCart.id, [
{
name: "test",
amount: 100,
},
])
const taxLines = await service.setShippingMethodTaxLines(
createdCart.id,
[
{
shipping_method_id: itemOne.id,
rate: 20,
code: "TX",
},
]
)
expect(taxLines).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_method_id: itemOne.id,
rate: 20,
code: "TX",
}),
])
)
await service.setShippingMethodTaxLines(createdCart.id, [])
const cart = await service.retrieveCart(createdCart.id, {
relations: ["shipping_methods.tax_lines"],
})
expect(cart.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: itemOne.id,
tax_lines: [],
}),
])
)
expect(cart.shipping_methods?.length).toBe(1)
expect(cart.shipping_methods?.[0].tax_lines?.length).toBe(0)
})
it("should update shipping item tax lines for a cart", async () => {
const [createdCart] = await service.createCarts([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addShippingMethods(createdCart.id, [
{
name: "test",
amount: 100,
},
])
const taxLines = await service.setShippingMethodTaxLines(
createdCart.id,
[
{
shipping_method_id: itemOne.id,
rate: 20,
code: "TX",
},
]
)
expect(taxLines).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_method_id: itemOne.id,
rate: 20,
code: "TX",
}),
])
)
await service.setShippingMethodTaxLines(createdCart.id, [
{
id: taxLines[0].id,
shipping_method_id: itemOne.id,
rate: 25,
code: "TX",
},
])
const cart = await service.retrieveCart(createdCart.id, {
relations: ["shipping_methods.tax_lines"],
})
expect(cart.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: itemOne.id,
tax_lines: [
expect.objectContaining({
id: taxLines[0].id,
shipping_method_id: itemOne.id,
rate: 25,
code: "TX",
}),
],
}),
])
)
expect(cart.shipping_methods?.length).toBe(1)
expect(cart.shipping_methods?.[0].tax_lines?.length).toBe(1)
})
it("should remove, update, and create shipping item tax lines for a cart", async () => {
const [createdCart] = await service.createCarts([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addShippingMethods(createdCart.id, [
{
name: "test",
amount: 100,
},
])
const taxLines = await service.setShippingMethodTaxLines(
createdCart.id,
[
{
shipping_method_id: itemOne.id,
rate: 20,
code: "TX",
},
{
shipping_method_id: itemOne.id,
rate: 25,
code: "TX",
},
]
)
expect(taxLines).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_method_id: itemOne.id,
rate: 20,
code: "TX",
}),
expect.objectContaining({
shipping_method_id: itemOne.id,
rate: 25,
code: "TX",
}),
])
)
const taxLine = taxLines.find(
(tx) => tx.shipping_method_id === itemOne.id
)
await service.setShippingMethodTaxLines(createdCart.id, [
// update
{
id: taxLine.id,
rate: 40,
code: "TX",
},
// create
{
shipping_method_id: itemOne.id,
rate: 25,
code: "TX-2",
},
// remove: should remove the initial tax line for itemOne
])
const cart = await service.retrieveCart(createdCart.id, {
relations: ["shipping_methods.tax_lines"],
})
expect(cart.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: itemOne.id,
tax_lines: [
expect.objectContaining({
id: taxLine!.id,
shipping_method_id: itemOne.id,
rate: 40,
code: "TX",
}),
expect.objectContaining({
shipping_method_id: itemOne.id,
rate: 25,
code: "TX-2",
}),
],
}),
])
)
expect(cart.shipping_methods?.length).toBe(1)
expect(cart.shipping_methods?.[0].tax_lines?.length).toBe(2)
})
})
describe("addLineItemAdjustments", () => {
it("should add line item tax lines for items in a cart", async () => {
const [createdCart] = await service.createCarts([
+152 -110
View File
@@ -13,6 +13,7 @@ import {
createRawPropertiesFromBigNumber,
decorateCartTotals,
deduplicate,
generateEntityId,
InjectManager,
InjectTransactionManager,
isObject,
@@ -20,6 +21,7 @@ import {
MedusaContext,
MedusaError,
ModulesSdkUtils,
promiseAll,
} from "@medusajs/framework/utils"
import {
Address,
@@ -357,9 +359,7 @@ export default class CartModuleService
const serializedResult = await this.baseRepository_.serialize<
CartTypes.CartDTO[]
>(result, {
populate: true,
})
>(result)
return isString(dataOrIdOrSelector) ? serializedResult[0] : serializedResult
}
@@ -448,10 +448,7 @@ export default class CartModuleService
}
return await this.baseRepository_.serialize<CartTypes.CartLineItemDTO[]>(
items,
{
populate: true,
}
items
)
}
@@ -557,10 +554,7 @@ export default class CartModuleService
)
return await this.baseRepository_.serialize<CartTypes.CartLineItemDTO[]>(
items,
{
populate: true,
}
items
)
}
@@ -718,7 +712,7 @@ export default class CartModuleService
return await this.baseRepository_.serialize<
CartTypes.CartShippingMethodDTO[]
>(methods, { populate: true })
>(methods)
}
@InjectTransactionManager()
@@ -809,9 +803,79 @@ export default class CartModuleService
return await this.baseRepository_.serialize<
CartTypes.LineItemAdjustmentDTO[]
>(addedAdjustments, {
populate: true,
})
>(addedAdjustments)
}
@InjectTransactionManager()
async upsertLineItemTaxLines(
taxLines: (
| CartTypes.CreateLineItemTaxLineDTO
| CartTypes.UpdateLineItemTaxLineDTO
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<CartTypes.LineItemTaxLineDTO[]> {
const result = await this.lineItemTaxLineService_.upsert(
taxLines as CartTypes.UpdateLineItemTaxLineDTO[],
sharedContext
)
return await this.baseRepository_.serialize<CartTypes.LineItemTaxLineDTO[]>(
result
)
}
@InjectTransactionManager()
async upsertLineItemAdjustments(
adjustments: (
| CartTypes.CreateLineItemAdjustmentDTO
| CartTypes.UpdateLineItemAdjustmentDTO
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<CartTypes.LineItemAdjustmentDTO[]> {
let result = await this.lineItemAdjustmentService_.upsert(
adjustments,
sharedContext
)
return await this.baseRepository_.serialize<
CartTypes.LineItemAdjustmentDTO[]
>(result)
}
@InjectTransactionManager()
async upsertShippingMethodTaxLines(
taxLines: (
| CartTypes.CreateShippingMethodTaxLineDTO
| CartTypes.UpdateShippingMethodTaxLineDTO
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<CartTypes.ShippingMethodTaxLineDTO[]> {
const result = await this.shippingMethodTaxLineService_.upsert(
taxLines as UpdateShippingMethodTaxLineDTO[],
sharedContext
)
return await this.baseRepository_.serialize<
CartTypes.ShippingMethodTaxLineDTO[]
>(result)
}
@InjectTransactionManager()
async upsertShippingMethodAdjustments(
adjustments: (
| CartTypes.CreateShippingMethodAdjustmentDTO
| CartTypes.UpdateShippingMethodAdjustmentDTO
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<CartTypes.ShippingMethodAdjustmentDTO[]> {
const result = await this.shippingMethodAdjustmentService_.upsert(
adjustments,
sharedContext
)
return await this.baseRepository_.serialize<
CartTypes.ShippingMethodAdjustmentDTO[]
>(result)
}
@InjectTransactionManager()
@@ -864,9 +928,7 @@ export default class CartModuleService
return await this.baseRepository_.serialize<
CartTypes.LineItemAdjustmentDTO[]
>(result, {
populate: true,
})
>(result)
}
@InjectTransactionManager()
@@ -921,9 +983,7 @@ export default class CartModuleService
return await this.baseRepository_.serialize<
CartTypes.ShippingMethodAdjustmentDTO[]
>(result, {
populate: true,
})
>(result)
}
async addShippingMethodAdjustments(
@@ -986,17 +1046,13 @@ export default class CartModuleService
if (isObject(cartIdOrData)) {
return await this.baseRepository_.serialize<CartTypes.ShippingMethodAdjustmentDTO>(
addedAdjustments[0],
{
populate: true,
}
{}
)
}
return await this.baseRepository_.serialize<
CartTypes.ShippingMethodAdjustmentDTO[]
>(addedAdjustments, {
populate: true,
})
>(addedAdjustments)
}
addLineItemTaxLines(
@@ -1046,9 +1102,7 @@ export default class CartModuleService
const serialized = await this.baseRepository_.serialize<
CartTypes.LineItemTaxLineDTO[]
>(addedTaxLines, {
populate: true,
})
>(addedTaxLines)
if (isObject(cartIdOrData)) {
return serialized[0]
@@ -1066,49 +1120,44 @@ export default class CartModuleService
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<CartTypes.LineItemTaxLineDTO[]> {
const cart = await this.retrieveCart(
cartId,
{ select: ["id"], relations: ["items.tax_lines"] },
sharedContext
)
const existingTaxLines = await this.listLineItemTaxLines(
{ item: { cart_id: cart.id } },
{ select: ["id"] },
sharedContext
)
const taxLinesSet = new Set(
taxLines
.map((taxLine) => (taxLine as CartTypes.UpdateLineItemTaxLineDTO)?.id)
.filter(Boolean)
)
const toDelete: CartTypes.LineItemTaxLineDTO[] = []
// From the existing tax lines, find the ones that are not passed in taxLines
existingTaxLines.forEach((taxLine: CartTypes.LineItemTaxLineDTO) => {
if (!taxLinesSet.has(taxLine.id)) {
toDelete.push(taxLine)
}
const normalizedTaxLines = (
taxLines as CartTypes.UpdateLineItemTaxLineDTO[]
).map((taxLine) => {
// Pre generate the id so that we can optimized the actions below
taxLine.id = generateEntityId(taxLine.id, "calitxl")
return taxLine
})
if (toDelete.length) {
await this.lineItemTaxLineService_.softDelete(
toDelete.map((taxLine) => taxLine!.id),
sharedContext
const taxLineIdsSet = new Set<string>(
normalizedTaxLines.map(
(taxLine) => (taxLine as CartTypes.UpdateLineItemTaxLineDTO)?.id
)
)
const deleteConstraints: {
id?: {
$nin: string[]
}
item: { cart_id: string }
} = {
item: { cart_id: cartId },
}
const result = taxLines.length
? await this.lineItemTaxLineService_.upsert(taxLines, sharedContext)
: []
if (taxLineIdsSet.size) {
deleteConstraints.id = {
$nin: Array.from(taxLineIdsSet),
}
}
const [result] = await promiseAll([
normalizedTaxLines.length
? this.lineItemTaxLineService_.upsert(normalizedTaxLines, sharedContext)
: [],
this.lineItemTaxLineService_.softDelete(deleteConstraints, sharedContext),
])
return await this.baseRepository_.serialize<CartTypes.LineItemTaxLineDTO[]>(
result,
{
populate: true,
}
result
)
}
@@ -1159,10 +1208,7 @@ export default class CartModuleService
const serialized =
await this.baseRepository_.serialize<CartTypes.ShippingMethodTaxLineDTO>(
addedTaxLines[0],
{
populate: true,
}
addedTaxLines[0]
)
if (isObject(cartIdOrData)) {
@@ -1181,53 +1227,49 @@ export default class CartModuleService
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<CartTypes.ShippingMethodTaxLineDTO[]> {
const cart = await this.retrieveCart(
cartId,
{ select: ["id"], relations: ["shipping_methods.tax_lines"] },
sharedContext
)
const existingTaxLines = await this.listShippingMethodTaxLines(
{ shipping_method: { cart_id: cart.id } },
{ select: ["id"] },
sharedContext
)
const taxLinesSet = new Set(
taxLines
.map(
(taxLine) => (taxLine as CartTypes.UpdateShippingMethodTaxLineDTO)?.id
)
.filter(Boolean)
)
const toDelete: CartTypes.ShippingMethodTaxLineDTO[] = []
// From the existing tax lines, find the ones that are not passed in taxLines
existingTaxLines.forEach((taxLine: CartTypes.ShippingMethodTaxLineDTO) => {
if (!taxLinesSet.has(taxLine.id)) {
toDelete.push(taxLine)
}
const normalizedTaxLines = (
taxLines as CartTypes.UpdateShippingMethodTaxLineDTO[]
).map((taxLine) => {
taxLine.id = generateEntityId(taxLine.id, "casmtxl")
return taxLine
})
if (toDelete.length) {
await this.shippingMethodTaxLineService_.softDelete(
toDelete.map((taxLine) => taxLine!.id),
sharedContext
const taxLineIdsSet = new Set(
normalizedTaxLines.map(
(taxLine) => (taxLine as CartTypes.UpdateShippingMethodTaxLineDTO)?.id
)
)
const deleteConstraints: {
id?: {
$nin: string[]
}
shipping_method: { cart_id: string }
} = {
shipping_method: { cart_id: cartId },
}
const result = taxLines.length
? await this.shippingMethodTaxLineService_.upsert(
taxLines as UpdateShippingMethodTaxLineDTO[],
sharedContext
)
: []
if (taxLineIdsSet.size) {
deleteConstraints.id = {
$nin: Array.from(taxLineIdsSet),
}
}
const [result] = await promiseAll([
taxLines.length
? this.shippingMethodTaxLineService_.upsert(
taxLines as UpdateShippingMethodTaxLineDTO[],
sharedContext
)
: [],
this.shippingMethodTaxLineService_.softDelete(
deleteConstraints,
sharedContext
),
])
return await this.baseRepository_.serialize<
CartTypes.ShippingMethodTaxLineDTO[]
>(result, {
populate: true,
})
>(result)
}
}