fix(order): calculate taxes on order edit flows (#11518)

* fix(order): calcualte taxes on order edit flows

* merge summary

* tests

* fix pending difference

* comments

* claim test

* revert method
This commit is contained in:
Carlos R. L. Rodrigues
2025-02-21 15:27:40 +01:00
committed by GitHub
parent 065df75e7d
commit 0c957350a6
19 changed files with 501 additions and 442 deletions
+13 -14
View File
@@ -17,23 +17,22 @@ enum ChangeActionType {
}
type OrderSummary {
total: Float
subtotal: Float
total_tax: Float
ordered_total: Float
fulfilled_total: Float
returned_total: Float
return_request_total: Float
write_off_total: Float
projected_total: Float
net_total: Float
net_subtotal: Float
net_total_tax: Float
balance: Float
pending_difference: Float
current_order_total: Float
original_order_total: Float
transaction_total: Float
paid_total: Float
refunded_total: Float
pending_difference: Float
credit_line_total: Float
accounting_total: Float
raw_pending_difference: JSON
raw_current_order_total: JSON
raw_original_order_total: JSON
raw_transaction_total: JSON
raw_paid_total: JSON
raw_refunded_total: JSON
raw_credit_line_total: JSON
raw_accounting_total: JSON
}
type OrderShippingMethodAdjustment {
@@ -45,21 +45,18 @@ describe("Action: Credit Line Add", function () {
"original_order_total": 30,
"current_order_total": 30,
"pending_difference": 30,
"difference_sum": 0,
"paid_total": 0,
"refunded_total": 0,
"credit_line_total": 0
}
Upon adding a credit line, the current order total will decrease with the difference_sum going in
the negatives making it possible for the merchant to balance the order to then enable a refund.
Upon adding a credit line, the current order total will decrease making it possible for the merchant to balance the order to then enable a refund.
{
"transaction_total": 0,
"original_order_total": 30,
"current_order_total": 60,
"pending_difference": 0,
"difference_sum": -30,
"paid_total": 0,
"refunded_total": 0,
"credit_line_total": 30
@@ -81,7 +78,6 @@ describe("Action: Credit Line Add", function () {
original_order_total: 30,
current_order_total: 30,
pending_difference: 30,
difference_sum: 0,
paid_total: 0,
refunded_total: 0,
credit_line_total: 0,
@@ -110,7 +106,6 @@ describe("Action: Credit Line Add", function () {
original_order_total: 30,
current_order_total: 0,
pending_difference: 0,
difference_sum: 0,
paid_total: 0,
refunded_total: 0,
credit_line_total: 30,
@@ -147,7 +142,6 @@ describe("Action: Credit Line Add", function () {
original_order_total: 30,
current_order_total: -10,
pending_difference: -10,
difference_sum: 0,
paid_total: 0,
refunded_total: 0,
credit_line_total: 40,
@@ -119,7 +119,6 @@ describe("Order Exchange - Actions", function () {
original_order_total: 270,
current_order_total: 312.5,
pending_difference: 312.5,
difference_sum: 42.5,
paid_total: 0,
refunded_total: 0,
credit_line_total: 0,
@@ -731,11 +731,13 @@ export default class OrderModuleService
shipping_methods,
items,
}) as any
const calculated = calculateOrderChange({
order: orderWithTotals,
actions: [],
transactions: order.transactions,
})
createRawPropertiesFromBigNumber(calculated)
ord.summary = {
@@ -1469,6 +1471,26 @@ export default class OrderModuleService
})
}
@InjectTransactionManager()
async upsertOrderLineItemAdjustments(
adjustments: (
| OrderTypes.CreateOrderLineItemAdjustmentDTO
| OrderTypes.UpdateOrderLineItemAdjustmentDTO
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<OrderTypes.OrderLineItemAdjustmentDTO[]> {
let result = await this.orderLineItemAdjustmentService_.upsert(
adjustments,
sharedContext
)
return await this.baseRepository_.serialize<
OrderTypes.OrderLineItemAdjustmentDTO[]
>(result, {
populate: true,
})
}
@InjectTransactionManager()
async setOrderLineItemAdjustments(
orderId: string,
@@ -1520,6 +1542,26 @@ export default class OrderModuleService
})
}
@InjectTransactionManager()
async upsertOrderShippingMethodAdjustments(
adjustments: (
| OrderTypes.CreateOrderShippingMethodAdjustmentDTO
| OrderTypes.UpdateOrderShippingMethodAdjustmentDTO
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<OrderTypes.OrderShippingMethodAdjustmentDTO[]> {
const result = await this.orderShippingMethodAdjustmentService_.upsert(
adjustments,
sharedContext
)
return await this.baseRepository_.serialize<
OrderTypes.OrderShippingMethodAdjustmentDTO[]
>(result, {
populate: true,
})
}
@InjectTransactionManager()
async setOrderShippingMethodAdjustments(
orderId: string,
@@ -1721,6 +1763,26 @@ export default class OrderModuleService
return serialized
}
@InjectTransactionManager()
async upsertOrderLineItemTaxLines(
taxLines: (
| OrderTypes.CreateOrderLineItemTaxLineDTO
| OrderTypes.UpdateOrderLineItemTaxLineDTO
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<OrderTypes.OrderLineItemTaxLineDTO[]> {
const result = await this.orderLineItemTaxLineService_.upsert(
taxLines as UpdateOrderLineItemTaxLineDTO[],
sharedContext
)
return await this.baseRepository_.serialize<
OrderTypes.OrderLineItemTaxLineDTO[]
>(result, {
populate: true,
})
}
@InjectTransactionManager()
async setOrderLineItemTaxLines(
orderId: string,
@@ -1834,6 +1896,26 @@ export default class OrderModuleService
return serialized
}
@InjectTransactionManager()
async upsertOrderShippingMethodTaxLines(
taxLines: (
| OrderTypes.CreateOrderShippingMethodTaxLineDTO
| OrderTypes.UpdateOrderShippingMethodTaxLineDTO
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<OrderTypes.OrderShippingMethodTaxLineDTO[]> {
const result = await this.orderShippingMethodTaxLineService_.upsert(
taxLines as UpdateOrderShippingMethodTaxLineDTO[],
sharedContext
)
return await this.baseRepository_.serialize<
OrderTypes.OrderShippingMethodTaxLineDTO[]
>(result, {
populate: true,
})
}
@InjectTransactionManager()
async setOrderShippingMethodTaxLines(
orderId: string,
@@ -2115,7 +2197,7 @@ export default class OrderModuleService
orderId,
{
select: ["id", "version", "items.detail", "summary", "total"],
relations: ["transactions", "items", "shipping_methods"],
relations: ["transactions", "credit_lines"],
},
sharedContext
)
@@ -2131,7 +2213,7 @@ export default class OrderModuleService
)
const { itemsToUpsert, shippingMethodsToUpsert, calculatedOrders } =
applyChangesToOrder(
await applyChangesToOrder(
[order],
{ [order.id]: orderChange.actions },
{ addActionReferenceToObject: true }
@@ -2139,9 +2221,34 @@ export default class OrderModuleService
const calculated = calculatedOrders[order.id]
await this.includeTaxLinesAndAdjustementsToPreview(
calculated.order,
itemsToUpsert,
shippingMethodsToUpsert,
sharedContext
)
const calcOrder = calculated.order
const orderWithTotals = decorateCartTotals(
calcOrder as DecorateCartLikeInputDTO
)
calcOrder.summary = calculated.getSummaryFromOrder(orderWithTotals)
createRawPropertiesFromBigNumber(calcOrder)
return calcOrder
}
private async includeTaxLinesAndAdjustementsToPreview(
order,
itemsToUpsert,
shippingMethodsToUpsert,
sharedContext
) {
const addedItems = {}
const addedShippingMethods = {}
for (const item of calculated.order.items) {
for (const item of order.items) {
const isExistingItem = item.id === item.detail?.item_id
if (!isExistingItem) {
addedItems[item.id] = {
@@ -2156,7 +2263,7 @@ export default class OrderModuleService
}
}
for (const sm of calculated.order.shipping_methods) {
for (const sm of order.shipping_methods) {
if (!isDefined(sm.shipping_option_id)) {
addedShippingMethods[sm.id] = sm
}
@@ -2171,7 +2278,7 @@ export default class OrderModuleService
sharedContext
)
calculated.order.items.forEach((item, idx) => {
order.items.forEach((item, idx) => {
if (!addedItems[item.id]) {
return
}
@@ -2187,7 +2294,7 @@ export default class OrderModuleService
const compareAtUnitPrice =
newItem?.compare_at_unit_price ?? item.compare_at_unit_price
calculated.order.items[idx] = {
order.items[idx] = {
...lineItem,
actions,
quantity: newItem.quantity,
@@ -2210,7 +2317,7 @@ export default class OrderModuleService
sharedContext
)
calculated.order.shipping_methods.forEach((sm, idx) => {
order.shipping_methods.forEach((sm, idx) => {
if (!addedShippingMethods[sm.id]) {
return
}
@@ -2227,7 +2334,7 @@ export default class OrderModuleService
sm.shipping_method_id = sm.id
delete sm.id
calculated.order.shipping_methods[idx] = {
order.shipping_methods[idx] = {
...shippingMethod,
actions,
detail: {
@@ -2237,15 +2344,6 @@ export default class OrderModuleService
}
})
}
const calcOrder = calculated.order
decorateCartTotals(calcOrder as DecorateCartLikeInputDTO)
calcOrder.summary = calculated.summary
createRawPropertiesFromBigNumber(calcOrder)
return calcOrder
}
async cancelOrderChange(
@@ -2982,27 +3080,25 @@ export default class OrderModuleService
{ id: deduplicate(ordersIds) },
{
select: ["id", "version", "items.detail", "summary", "total"],
relations: [
"transactions",
"items",
"items.detail",
"shipping_methods",
],
relations: ["transactions", "credit_lines"],
},
sharedContext
)
orders = formatOrder(orders, {
entity: Order,
}) as OrderDTO[]
const {
itemsToUpsert,
shippingMethodsToUpsert,
summariesToUpsert,
orderToUpdate,
} = applyChangesToOrder(orders, actionsMap, {
} = await applyChangesToOrder(orders, actionsMap, {
addActionReferenceToObject: true,
includeTaxLinesAndAdjustementsToPreview: async (...args) => {
args.push(sharedContext)
return await this.includeTaxLinesAndAdjustementsToPreview.apply(
this,
args
)
},
})
await promiseAll([
@@ -3130,7 +3226,7 @@ export default class OrderModuleService
id: transactionIds,
},
{
select: ["order_id", "amount"],
select: ["order_id", "version", "amount"],
},
sharedContext
)
@@ -3162,7 +3258,7 @@ export default class OrderModuleService
id: transactionIds,
},
{
select: ["order_id", "amount"],
select: ["order_id", "version", "amount"],
withDeleted: true,
},
sharedContext
@@ -3187,6 +3283,7 @@ export default class OrderModuleService
private async updateOrderPaidRefundableAmount_(
transactionData: {
order_id: string
version: number
amount: BigNumber | number | BigNumberInput
}[],
isRemoved: boolean,
@@ -3195,6 +3292,7 @@ export default class OrderModuleService
const summaries: any = await super.listOrderSummaries(
{
order_id: transactionData.map((trx) => trx.order_id),
version: transactionData[0].version,
},
{},
sharedContext
@@ -3212,6 +3310,8 @@ export default class OrderModuleService
const op = isRemoved ? MathBN.sub : MathBN.add
const initialTrxTotal = summary.totals.transaction_total
for (const trx of trxs) {
if (MathBN.gt(trx.amount, 0)) {
summary.totals.paid_total = new BigNumber(
@@ -3228,11 +3328,12 @@ export default class OrderModuleService
)
}
const initialDiff = MathBN.sub(
summary.totals.transaction_total,
initialTrxTotal
)
summary.totals.pending_difference = new BigNumber(
MathBN.sub(
summary.totals.current_order_total,
summary.totals.transaction_total
)
MathBN.sub(summary.totals.pending_difference, initialDiff)
)
})
@@ -62,6 +62,17 @@ export type VirtualOrder = {
amount: BigNumberInput
}[]
summary?: {
pending_difference: BigNumberInput
current_order_total: BigNumberInput
original_order_total: BigNumberInput
transaction_total: BigNumberInput
paid_total: BigNumberInput
refunded_total: BigNumberInput
credit_line_total: BigNumberInput
accounting_total: BigNumberInput
}
total: BigNumberInput
customer_id?: string
@@ -80,7 +91,6 @@ export interface OrderSummaryCalculated {
original_order_total: BigNumberInput
transaction_total: BigNumberInput
pending_difference: BigNumberInput
difference_sum: BigNumberInput
paid_total: BigNumberInput
refunded_total: BigNumberInput
credit_line_total: BigNumberInput
@@ -1,11 +1,13 @@
import {
InferEntityType,
OrderChangeActionDTO,
OrderDTO,
} from "@medusajs/framework/types"
import {
ChangeActionType,
MathBN,
createRawPropertiesFromBigNumber,
decorateCartTotals,
isDefined,
} from "@medusajs/framework/utils"
import { OrderItem, OrderShippingMethod } from "@models"
@@ -18,11 +20,12 @@ export interface ApplyOrderChangeDTO extends OrderChangeActionDTO {
applied: boolean
}
export function applyChangesToOrder(
export async function applyChangesToOrder(
orders: any[],
actionsMap: Record<string, any[]>,
options?: {
addActionReferenceToObject?: boolean
includeTaxLinesAndAdjustementsToPreview?: (...args) => void
}
) {
const itemsToUpsert: InferEntityType<typeof OrderItem>[] = []
@@ -49,8 +52,6 @@ export function applyChangesToOrder(
createRawPropertiesFromBigNumber(calculated)
calculatedOrders[order.id] = calculated
const version = actionsMap[order.id]?.[0]?.version ?? order.version
const orderAttributes: {
version?: number
@@ -95,14 +96,6 @@ export function applyChangesToOrder(
itemsToUpsert.push(itemToUpsert)
}
const orderSummary = order.summary as any
summariesToUpsert.push({
id: orderSummary?.version === version ? orderSummary.id : undefined,
order_id: order.id,
version,
totals: calculated.summary,
})
if (version > order.version) {
for (const shippingMethod of calculated.order.shipping_methods ?? []) {
const shippingMethod_ = shippingMethod as any
@@ -140,6 +133,26 @@ export function applyChangesToOrder(
orderAttributes.version = version
}
// Including tax lines and adjustments for added items and shipping methods
if (options?.includeTaxLinesAndAdjustementsToPreview) {
await options?.includeTaxLinesAndAdjustementsToPreview(
calculated.order,
itemsToUpsert,
shippingMethodsToUpsert
)
decorateCartTotals(calculated.order)
}
const orderSummary = order.summary
summariesToUpsert.push({
id: orderSummary?.version === version ? orderSummary.id : undefined,
order_id: order.id,
version,
totals: calculated.getSummaryFromOrder(
calculated.order as unknown as OrderDTO
),
})
if (Object.keys(orderAttributes).length > 0) {
orderToUpdate.push({
selector: {
@@ -150,6 +163,8 @@ export function applyChangesToOrder(
},
})
}
calculatedOrders[order.id] = calculated
}
return {
@@ -1,4 +1,8 @@
import { BigNumberInput, OrderSummaryDTO } from "@medusajs/framework/types"
import {
BigNumberInput,
OrderDTO,
OrderSummaryDTO,
} from "@medusajs/framework/types"
import {
BigNumber,
ChangeActionType,
@@ -77,7 +81,6 @@ export class OrderChangeProcessing {
this.summary = {
pending_difference: 0,
difference_sum: 0,
current_order_total: this.order.total ?? 0,
original_order_total: this.order.total ?? 0,
transaction_total: transactionTotal,
@@ -96,10 +99,6 @@ export class OrderChangeProcessing {
status === EVENT_STATUS.DONE
)
}
private isEventDone(action: InternalOrderChangeEvent): boolean {
const status = action.status
return status === EVENT_STATUS.DONE
}
public processActions() {
let creditLineTotal = (this.order.credit_lines || []).reduce(
@@ -136,10 +135,6 @@ export class OrderChangeProcessing {
if (action.action === ChangeActionType.CREDIT_LINE_ADD) {
creditLineTotal = MathBN.add(creditLineTotal, amount)
} else {
if (!this.isEventDone(action) && !action.change_id) {
summary.difference_sum = MathBN.add(summary.difference_sum, amount)
}
summary.current_order_total = MathBN.add(
summary.current_order_total,
amount
@@ -147,8 +142,6 @@ export class OrderChangeProcessing {
}
}
const groupSum = MathBN.add(...Object.values(this.groupTotal))
summary.difference_sum = MathBN.add(summary.difference_sum, groupSum)
summary.credit_line_total = creditLineTotal
summary.accounting_total = MathBN.sub(
summary.current_order_total,
@@ -224,7 +217,6 @@ export class OrderChangeProcessing {
original_order_total: new BigNumber(summary.original_order_total),
current_order_total: new BigNumber(summary.current_order_total),
pending_difference: new BigNumber(summary.pending_difference),
difference_sum: new BigNumber(summary.difference_sum),
paid_total: new BigNumber(summary.paid_total),
refunded_total: new BigNumber(summary.refunded_total),
credit_line_total: new BigNumber(summary.credit_line_total),
@@ -234,6 +226,58 @@ export class OrderChangeProcessing {
return orderSummary
}
// Calculate the order summary from a calculated order including taxes
public getSummaryFromOrder(order: OrderDTO): OrderSummaryDTO {
const summary_ = this.summary
const total = order.total
const orderSummary = {
transaction_total: new BigNumber(summary_.transaction_total),
original_order_total: new BigNumber(summary_.original_order_total),
current_order_total: new BigNumber(total),
pending_difference: new BigNumber(summary_.pending_difference),
paid_total: new BigNumber(summary_.paid_total),
refunded_total: new BigNumber(summary_.refunded_total),
credit_line_total: new BigNumber(summary_.credit_line_total),
accounting_total: new BigNumber(summary_.accounting_total),
} as any
orderSummary.accounting_total = new BigNumber(
MathBN.sub(
orderSummary.current_order_total,
orderSummary.credit_line_total
)
)
orderSummary.current_order_total = new BigNumber(
MathBN.sub(
orderSummary.current_order_total,
orderSummary.credit_line_total
)
)
orderSummary.pending_difference = MathBN.sub(
orderSummary.current_order_total,
orderSummary.transaction_total
)
// return requested becomes pending difference
for (const item of order.items ?? []) {
const item_ = item as any
if (MathBN.gt(item_.return_requested_total, 0)) {
orderSummary.pending_difference = MathBN.sub(
orderSummary.pending_difference,
item_.return_requested_total
)
}
}
orderSummary.pending_difference = new BigNumber(
orderSummary.pending_difference
)
return orderSummary
}
public getCurrentOrder(): VirtualOrder {
return this.order
}
@@ -259,7 +303,9 @@ export function calculateOrderChange({
calc.processActions()
return {
instance: calc,
summary: calc.getSummary(),
getSummaryFromOrder: (order: OrderDTO) => calc.getSummaryFromOrder(order),
order: calc.getCurrentOrder(),
}
}