feat(order): receive return and additional calculations (#7314)

This commit is contained in:
Carlos R. L. Rodrigues
2024-05-14 08:35:31 -03:00
committed by GitHub
parent d680c7ee4c
commit 4ef70d37bf
9 changed files with 259 additions and 87 deletions

View File

@@ -403,6 +403,7 @@ export interface CreateOrderReturnDTO {
order_id: string
description?: string
reference?: string
reference_id?: string
internal_note?: string
created_by?: string
shipping_method: Omit<CreateOrderShippingMethodDTO, "order_id"> | string
@@ -415,6 +416,22 @@ export interface CreateOrderReturnDTO {
metadata?: Record<string, unknown> | null
}
export interface ReceiveOrderReturnDTO {
order_id: string
description?: string
internal_note?: string
reference?: string
reference_id?: string
created_by?: string
items: {
id: string
quantity: BigNumberInput
internal_note?: string
metadata?: Record<string, unknown> | null
}[]
metadata?: Record<string, unknown> | null
}
/** ORDER bundled action flows */
export interface CreateOrderReturnReasonDTO {

View File

@@ -45,6 +45,7 @@ import {
CreateOrderShippingMethodTaxLineDTO,
CreateOrderTransactionDTO,
DeclineOrderChangeDTO,
ReceiveOrderReturnDTO,
RegisterOrderFulfillmentDTO,
RegisterOrderShipmentDTO,
UpdateOrderAddressDTO,
@@ -1520,4 +1521,9 @@ export interface IOrderModuleService extends IModuleService {
returnData: CreateOrderReturnDTO,
sharedContext?: Context
): Promise<void>
receiveReturn(
returnData: ReceiveOrderReturnDTO,
sharedContext?: Context
): Promise<void>
}

View File

@@ -634,4 +634,94 @@ describe("Total calculation", function () {
original_shipping_total: 27.5,
})
})
it("should calculate order with items + taxes + adjustments", function () {
const cart = {
items: [
{
unit_price: 50,
quantity: 2,
fulfilled_quantity: 2,
shipped_quantity: 2,
return_requested_quantity: 2,
return_received_quantity: 1,
return_dismissed_quantity: 1,
written_off_quantity: 0,
tax_lines: [
{
rate: 10,
},
],
adjustments: [
{
amount: 20,
},
],
},
],
}
const serialized = JSON.parse(JSON.stringify(decorateCartTotals(cart)))
expect(serialized).toEqual({
items: [
{
unit_price: 50,
quantity: 2,
fulfilled_quantity: 2,
shipped_quantity: 2,
return_requested_quantity: 2,
return_received_quantity: 1,
return_dismissed_quantity: 1,
written_off_quantity: 0,
tax_lines: [
{
rate: 10,
total: 8,
subtotal: 10,
},
],
adjustments: [
{
amount: 20,
subtotal: 20,
total: 22,
},
],
subtotal: 100,
total: 88,
original_total: 110,
discount_total: 20,
discount_tax_total: 2,
tax_total: 8,
original_tax_total: 10,
fulfilled_total: 88,
shipped_total: 88,
return_requested_total: 88,
return_received_total: 44,
return_dismissed_total: 44,
write_off_total: 0,
},
],
total: 88,
subtotal: 100,
tax_total: 8,
discount_total: 20,
discount_tax_total: 2,
original_total: 90,
original_tax_total: 10,
item_total: 88,
item_subtotal: 100,
item_tax_total: 8,
original_item_total: 110,
original_item_subtotal: 100,
original_item_tax_total: 10,
fulfilled_total: 88,
shipped_total: 88,
return_requested_total: 88,
return_received_total: 44,
return_dismissed_total: 44,
write_off_total: 0,
})
})
})

View File

@@ -43,6 +43,15 @@ export function decorateCartTotals(
): CartLikeWithTotals {
transformPropertiesToBigNumber(cartLike)
const optionalFields = {
fulfilled_quantity: "fulfilled_total",
shipped_quantity: "shipped_total",
return_requested_quantity: "return_requested_total",
return_received_quantity: "return_received_total",
return_dismissed_quantity: "return_dismissed_total",
written_off_quantity: "write_off_total",
}
const items = (cartLike.items ?? []) as unknown as GetItemTotalInput[]
const shippingMethods = (cartLike.shipping_methods ??
[]) as unknown as GetShippingMethodTotalInput[]
@@ -51,12 +60,15 @@ export function decorateCartTotals(
const itemsTotals = getLineItemsTotals(items, {
includeTax,
extraQuantityFields: optionalFields,
})
const shippingMethodsTotals = getShippingMethodsTotals(shippingMethods, {
includeTax,
})
const extraTotals = {}
let subtotal = MathBN.convert(0)
let discountTotal = MathBN.convert(0)
@@ -117,6 +129,13 @@ export function decorateCartTotals(
itemOriginalTaxTotal
)
for (const key of Object.values(optionalFields)) {
if (key in itemTotals) {
extraTotals[key] ??= MathBN.convert(0)
extraTotals[key] = MathBN.add(extraTotals[key], itemTotals[key] ?? 0)
}
}
return itemTotals
})
@@ -213,6 +232,10 @@ export function decorateCartTotals(
cart.original_item_total = new BigNumber(itemsOriginalTotal)
cart.original_item_subtotal = new BigNumber(itemsOriginalSubtotal)
cart.original_item_tax_total = new BigNumber(itemsOriginalTaxTotal)
for (const key of Object.keys(extraTotals)) {
cart[key] = new BigNumber(extraTotals[key])
}
}
if (cart.shipping_methods) {

View File

@@ -6,6 +6,7 @@ import { calculateTaxTotal } from "../tax"
interface GetLineItemsTotalsContext {
includeTax?: boolean
extraQuantityFields?: Record<string, string>
}
export interface GetItemTotalInput {
@@ -15,6 +16,13 @@ export interface GetItemTotalInput {
is_tax_inclusive?: boolean
tax_lines?: Pick<TaxLineDTO, "rate">[]
adjustments?: Pick<AdjustmentLineDTO, "amount">[]
fulfilled_quantity?: BigNumber
shipped_quantity?: BigNumber
return_requested_quantity?: BigNumber
return_received_quantity?: BigNumber
return_dismissed_quantity?: BigNumber
written_off_quantity?: BigNumber
}
export interface GetItemTotalOutput {
@@ -31,6 +39,13 @@ export interface GetItemTotalOutput {
tax_total: BigNumber
original_tax_total: BigNumber
fulfilled_total?: BigNumber
shipped_total?: BigNumber
return_requested_total?: BigNumber
return_received_total?: BigNumber
return_dismissed_total?: BigNumber
write_off_total?: BigNumber
}
export function getLineItemsTotals(
@@ -43,6 +58,7 @@ export function getLineItemsTotals(
for (const item of items) {
itemsTotals[item.id ?? index] = getLineItemTotals(item, {
includeTax: context.includeTax || item.is_tax_inclusive,
extraQuantityFields: context.extraQuantityFields,
})
index++
}
@@ -122,5 +138,17 @@ function getLineItemTotals(
totals.original_total = new BigNumber(originalTotal)
}
const totalPerUnit = MathBN.div(totals.total, item.quantity)
const optionalFields = {
...(context.extraQuantityFields ?? {}),
}
for (const field in optionalFields) {
if (field in item) {
const totalField = optionalFields[field]
totals[totalField] = new BigNumber(MathBN.mult(totalPerUnit, item[field]))
}
}
return totals
}

View File

@@ -96,14 +96,14 @@ describe("Order Exchange - Actions", function () {
const sumToJSON = JSON.parse(JSON.stringify(changes.summary))
expect(sumToJSON).toEqual({
transactionTotal: 0,
originalOrderTotal: 270,
currentOrderTotal: 312.5,
temporaryDifference: 62.5,
futureDifference: 0,
futureTemporaryDifference: 0,
pendingDifference: 312.5,
differenceSum: 42.5,
transaction_total: 0,
original_order_total: 270,
current_order_total: 312.5,
temporary_difference: 62.5,
future_difference: 0,
future_temporary_difference: 0,
pending_difference: 312.5,
difference_sum: 42.5,
})
const toJson = JSON.parse(JSON.stringify(changes.order.items))

View File

@@ -2238,8 +2238,8 @@ export default class OrderModuleService<
return {
action: ChangeActionType.RETURN_ITEM,
internal_note: item.internal_note,
reference: data.reference,
reference_id: shippingMethodId,
reference: data.reference ?? "fulfillment",
reference_id: data.reference_id ?? shippingMethodId,
details: {
reference_id: item.id,
quantity: item.quantity,
@@ -2251,8 +2251,8 @@ export default class OrderModuleService<
if (shippingMethodId) {
actions.push({
action: ChangeActionType.SHIPPING_ADD,
reference: data.reference,
reference_id: shippingMethodId,
reference: data.reference ?? "fulfillment",
reference_id: data.reference_id ?? shippingMethodId,
amount: calculatedAmount.total,
})
}
@@ -2544,4 +2544,37 @@ export default class OrderModuleService<
return await this.returnReasonService_.update(toUpdate, sharedContext)
}
public async receiveReturn(
data: OrderTypes.ReceiveOrderReturnDTO,
sharedContext?: Context
): Promise<void> {
const items = data.items.map((item) => {
return {
action: ChangeActionType.RECEIVE_RETURN_ITEM,
internal_note: item.internal_note,
reference: data.reference,
reference_id: data.reference_id,
details: {
reference_id: item.id,
quantity: item.quantity,
metadata: item.metadata,
},
}
})
const change = await this.createOrderChange_(
{
order_id: data.order_id,
description: data.description,
internal_note: data.internal_note,
created_by: data.created_by,
metadata: data.metadata,
actions: items,
},
sharedContext
)
await this.confirmOrderChange(change[0].id, sharedContext)
}
}

View File

@@ -36,14 +36,14 @@ export enum EVENT_STATUS {
}
export interface OrderSummaryCalculated {
currentOrderTotal: BigNumberInput
originalOrderTotal: BigNumberInput
transactionTotal: BigNumberInput
futureDifference: BigNumberInput
pendingDifference: BigNumberInput
futureTemporaryDifference: BigNumberInput
temporaryDifference: BigNumberInput
differenceSum: BigNumberInput
current_order_total: BigNumberInput
original_order_total: BigNumberInput
transaction_total: BigNumberInput
future_difference: BigNumberInput
pending_difference: BigNumberInput
future_temporary_difference: BigNumberInput
temporary_difference: BigNumberInput
difference_sum: BigNumberInput
}
export interface OrderTransaction {

View File

@@ -15,8 +15,8 @@ import {
VirtualOrder,
} from "@types"
type InternalOrderSummary = OrderSummaryCalculated & {
futureTemporarySum: BigNumberInput
interface InternalOrderSummary extends OrderSummaryCalculated {
future_temporary_sum: BigNumberInput
}
export class OrderChangeProcessing {
@@ -56,15 +56,15 @@ export class OrderChangeProcessing {
transformPropertiesToBigNumber(this.order.metadata)
this.summary = {
futureDifference: 0,
futureTemporaryDifference: 0,
temporaryDifference: 0,
pendingDifference: 0,
futureTemporarySum: 0,
differenceSum: 0,
currentOrderTotal: this.order.total ?? 0,
originalOrderTotal: this.order.total ?? 0,
transactionTotal,
future_difference: 0,
future_temporary_difference: 0,
temporary_difference: 0,
pending_difference: 0,
future_temporary_sum: 0,
difference_sum: 0,
current_order_total: this.order.total ?? 0,
original_order_total: this.order.total ?? 0,
transaction_total: transactionTotal,
}
}
@@ -114,26 +114,29 @@ export class OrderChangeProcessing {
if (type.awaitRequired && !this.isEventDone(action)) {
if (action.evaluationOnly) {
summary.futureTemporarySum = MathBN.add(
summary.futureTemporarySum,
summary.future_temporary_sum = MathBN.add(
summary.future_temporary_sum,
amount
)
} else {
summary.temporaryDifference = MathBN.add(
summary.temporaryDifference,
summary.temporary_difference = MathBN.add(
summary.temporary_difference,
amount
)
}
}
if (action.evaluationOnly) {
summary.futureDifference = MathBN.add(summary.futureDifference, amount)
summary.future_difference = MathBN.add(
summary.future_difference,
amount
)
} else {
if (!this.isEventDone(action) && !action.group_id) {
summary.differenceSum = MathBN.add(summary.differenceSum, amount)
summary.difference_sum = MathBN.add(summary.difference_sum, amount)
}
summary.currentOrderTotal = MathBN.add(
summary.currentOrderTotal,
summary.current_order_total = MathBN.add(
summary.current_order_total,
amount
)
}
@@ -141,25 +144,25 @@ export class OrderChangeProcessing {
const groupSum = MathBN.add(...Object.values(this.groupTotal))
summary.differenceSum = MathBN.add(summary.differenceSum, groupSum)
summary.difference_sum = MathBN.add(summary.difference_sum, groupSum)
summary.transactionTotal = MathBN.sum(
summary.transaction_total = MathBN.sum(
...this.transactions.map((tr) => tr.amount)
)
summary.futureTemporaryDifference = MathBN.sub(
summary.futureDifference,
summary.futureTemporarySum
summary.future_temporary_difference = MathBN.sub(
summary.future_difference,
summary.future_temporary_sum
)
summary.temporaryDifference = MathBN.sub(
summary.differenceSum,
summary.temporaryDifference
summary.temporary_difference = MathBN.sub(
summary.difference_sum,
summary.temporary_difference
)
summary.pendingDifference = MathBN.sub(
summary.currentOrderTotal,
summary.transactionTotal
summary.pending_difference = MathBN.sub(
summary.current_order_total,
summary.transaction_total
)
}
@@ -345,46 +348,18 @@ export class OrderChangeProcessing {
public getSummary(): OrderSummaryDTO {
const summary = this.summary
const orderSummary = {
transactionTotal: new BigNumber(summary.transactionTotal),
originalOrderTotal: new BigNumber(summary.originalOrderTotal),
currentOrderTotal: new BigNumber(summary.currentOrderTotal),
temporaryDifference: new BigNumber(summary.temporaryDifference),
futureDifference: new BigNumber(summary.futureDifference),
futureTemporaryDifference: new BigNumber(
summary.futureTemporaryDifference
transaction_total: new BigNumber(summary.transaction_total),
original_order_total: new BigNumber(summary.original_order_total),
current_order_total: new BigNumber(summary.current_order_total),
temporary_difference: new BigNumber(summary.temporary_difference),
future_difference: new BigNumber(summary.future_difference),
future_temporary_difference: new BigNumber(
summary.future_temporary_difference
),
pendingDifference: new BigNumber(summary.pendingDifference),
differenceSum: new BigNumber(summary.differenceSum),
pending_difference: new BigNumber(summary.pending_difference),
difference_sum: new BigNumber(summary.difference_sum),
} as unknown as OrderSummaryDTO
/*
{
total: summary.currentOrderTotal
subtotal: number
total_tax: number
ordered_total: summary.originalOrderTotal
fulfilled_total: number
returned_total: number
return_request_total: number
write_off_total: number
projected_total: number
net_total: number
net_subtotal: number
net_total_tax: number
future_total: number
future_subtotal: number
future_total_tax: number
future_projected_total: number
balance: summary.pendingDifference
future_balance: number
}
*/
return orderSummary
}