chore(order): big number calculations (#6651)

Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
Carlos R. L. Rodrigues
2024-03-11 17:51:00 +00:00
committed by GitHub
co-authored by Adrien de Peretti
parent 7c46b0f88b
commit d48c076b77
23 changed files with 637 additions and 197 deletions
@@ -1,4 +1,4 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
@@ -10,7 +10,10 @@ OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN, {
existing.detail.return_requested_quantity ??= 0
existing.detail.return_requested_quantity -= action.details.quantity
existing.detail.return_requested_quantity = MathBN.sub(
existing.detail.return_requested_quantity,
action.details.quantity
)
return action.details.unit_price * action.details.quantity
},
@@ -19,7 +22,10 @@ OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN, {
(item) => item.id === action.details.reference_id
)!
existing.detail.return_requested_quantity += action.details.quantity
existing.detail.return_requested_quantity = MathBN.add(
existing.detail.return_requested_quantity,
action.details.quantity
)
},
validate({ action, currentOrder }) {
const refId = action.details?.reference_id
@@ -53,7 +59,11 @@ OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN, {
)
}
if (action.details?.quantity > existing.detail?.return_requested_quantity) {
const greater = MathBN.gt(
action.details?.quantity,
existing.detail?.return_requested_quantity
)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot cancel more items than what was requested to return for item ${refId}.`
@@ -1,4 +1,4 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
@@ -10,14 +10,20 @@ OrderChangeProcessing.registerActionType(ChangeActionType.FULFILL_ITEM, {
existing.detail.fulfilled_quantity ??= 0
existing.detail.fulfilled_quantity += action.details.quantity
existing.detail.fulfilled_quantity = MathBN.add(
existing.detail.fulfilled_quantity,
action.details.quantity
)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.reference_id
)!
existing.detail.fulfilled_quantity -= action.details.quantity
existing.detail.fulfilled_quantity = MathBN.sub(
existing.detail.fulfilled_quantity,
action.details.quantity
)
},
validate({ action, currentOrder }) {
const refId = action.details?.reference_id
@@ -50,11 +56,12 @@ OrderChangeProcessing.registerActionType(ChangeActionType.FULFILL_ITEM, {
)
}
const notFulfilled =
(existing.quantity as number) -
(existing.detail?.fulfilled_quantity as number)
if (action.details?.quantity > notFulfilled) {
const notFulfilled = MathBN.sub(
existing.quantity,
existing.detail?.fulfilled_quantity
)
const greater = MathBN.gt(action.details?.quantity, notFulfilled)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot fulfill more items than what was ordered for item ${refId}.`
+15 -9
View File
@@ -1,4 +1,4 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { VirtualOrder } from "@types"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
@@ -12,18 +12,21 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, {
if (existing) {
existing.detail.quantity ??= 0
existing.quantity += action.details.quantity
existing.detail.quantity += action.details.quantity
existing.quantity = MathBN.add(existing.quantity, action.details.quantity)
existing.detail.quantity = MathBN.add(
existing.detail.quantity,
action.details.quantity
)
} else {
currentOrder.items.push({
id: action.reference_id!,
unit_price: action.details.unit_price,
quantity: action.details.quantity,
// detail: {}
} as VirtualOrder["items"][0])
}
return action.details.unit_price * action.details.quantity
return MathBN.mult(action.details.unit_price, action.details.quantity)
},
revert({ action, currentOrder }) {
const existingIndex = currentOrder.items.findIndex(
@@ -32,10 +35,13 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, {
if (existingIndex > -1) {
const existing = currentOrder.items[existingIndex]
existing.quantity -= action.details.quantity
existing.detail.quantity -= action.details.quantity
existing.quantity = MathBN.sub(existing.quantity, action.details.quantity)
existing.detail.quantity = MathBN.sub(
existing.detail.quantity,
action.details.quantity
)
if (existing.quantity <= 0) {
if (MathBN.lte(existing.quantity, 0)) {
currentOrder.items.splice(existingIndex, 1)
}
}
@@ -63,7 +69,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, {
)
}
if (action.details?.quantity < 1) {
if (MathBN.lt(action.details?.quantity, 1)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity of item ${refId} must be greater than 0.`
+20 -12
View File
@@ -1,4 +1,4 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { VirtualOrder } from "@types"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
@@ -14,14 +14,17 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_REMOVE, {
existing.detail.quantity ??= 0
existing.quantity -= action.details.quantity
existing.detail.quantity -= action.details.quantity
existing.quantity = MathBN.sub(existing.quantity, action.details.quantity)
existing.detail.quantity = MathBN.sub(
existing.detail.quantity,
action.details.quantity
)
if (existing.quantity <= 0) {
if (MathBN.lte(existing.quantity, 0)) {
currentOrder.items.splice(existingIndex, 1)
}
return existing.unit_price * action.details.quantity
return MathBN.mult(existing.unit_price, action.details.quantity)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
@@ -29,8 +32,11 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_REMOVE, {
)
if (existing) {
existing.quantity += action.details.quantity
existing.detail.quantity += action.details.quantity
existing.quantity = MathBN.add(existing.quantity, action.details.quantity)
existing.detail.quantity = MathBN.add(
existing.detail.quantity,
action.details.quantity
)
} else {
currentOrder.items.push({
id: action.reference_id!,
@@ -70,18 +76,20 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_REMOVE, {
)
}
if (action.details?.quantity < 1) {
if (MathBN.lt(action.details?.quantity, 1)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity of item ${refId} must be greater than 0.`
)
}
const notFulfilled =
(existing.quantity as number) -
(existing.detail?.fulfilled_quantity as number)
const notFulfilled = MathBN.sub(
existing.quantity,
existing.detail?.fulfilled_quantity
)
if (action.details?.quantity > notFulfilled) {
const greater = MathBN.gt(action.details?.quantity, notFulfilled)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot remove fulfilled item: Item ${refId}.`
@@ -1,4 +1,4 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { EVENT_STATUS } from "@types"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
@@ -18,32 +18,47 @@ OrderChangeProcessing.registerActionType(
existing.detail.return_dismissed_quantity ??= 0
existing.detail.return_requested_quantity ??= 0
existing.detail.return_dismissed_quantity += toReturn
existing.detail.return_requested_quantity -= toReturn
existing.detail.return_dismissed_quantity = MathBN.add(
existing.detail.return_dismissed_quantity,
toReturn
)
existing.detail.return_requested_quantity = MathBN.sub(
existing.detail.return_requested_quantity,
toReturn
)
if (previousEvents) {
for (const previousEvent of previousEvents) {
previousEvent.original_ = JSON.parse(JSON.stringify(previousEvent))
let ret = Math.min(toReturn, previousEvent.details.quantity)
toReturn -= ret
let ret = MathBN.min(toReturn, previousEvent.details.quantity)
toReturn = MathBN.sub(toReturn, ret)
previousEvent.details.quantity -= ret
if (previousEvent.details.quantity <= 0) {
previousEvent.details.quantity = MathBN.sub(
previousEvent.details.quantity,
ret
)
if (MathBN.lte(previousEvent.details.quantity, 0)) {
previousEvent.status = EVENT_STATUS.DONE
}
}
}
return existing.unit_price * action.details.quantity
return MathBN.mult(existing.unit_price, action.details.quantity)
},
revert({ action, currentOrder, previousEvents }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.return_dismissed_quantity -= action.details.quantity
existing.detail.return_requested_quantity += action.details.quantity
existing.detail.return_dismissed_quantity = MathBN.sub(
existing.detail.return_dismissed_quantity,
action.details.quantity
)
existing.detail.return_requested_quantity = MathBN.add(
existing.detail.return_requested_quantity,
action.details.quantity
)
if (previousEvents) {
for (const previousEvent of previousEvents) {
@@ -1,4 +1,9 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import {
MathBN,
MedusaError,
isDefined,
transformPropertiesToBigNumber,
} from "@medusajs/utils"
import { EVENT_STATUS } from "@types"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
@@ -16,32 +21,48 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RECEIVE_RETURN_ITEM, {
existing.detail.return_received_quantity ??= 0
existing.detail.return_requested_quantity ??= 0
existing.detail.return_received_quantity += toReturn
existing.detail.return_requested_quantity -= toReturn
existing.detail.return_received_quantity = MathBN.add(
existing.detail.return_received_quantity,
toReturn
)
existing.detail.return_requested_quantity = MathBN.sub(
existing.detail.return_requested_quantity,
toReturn
)
if (previousEvents) {
for (const previousEvent of previousEvents) {
previousEvent.original_ = JSON.parse(JSON.stringify(previousEvent))
let ret = Math.min(toReturn, previousEvent.details.quantity)
toReturn -= ret
let ret = MathBN.min(toReturn, previousEvent.details.quantity)
toReturn = MathBN.sub(toReturn, ret)
previousEvent.details.quantity -= ret
if (previousEvent.details.quantity <= 0) {
previousEvent.details.quantity = MathBN.sub(
previousEvent.details.quantity,
ret
)
if (MathBN.lte(previousEvent.details.quantity, 0)) {
previousEvent.status = EVENT_STATUS.DONE
}
}
}
return existing.unit_price * action.details.quantity
return MathBN.mult(existing.unit_price, action.details.quantity)
},
revert({ action, currentOrder, previousEvents }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.return_received_quantity -= action.details.quantity
existing.detail.return_requested_quantity += action.details.quantity
existing.detail.return_received_quantity = MathBN.sub(
existing.detail.return_received_quantity,
action.details.quantity
)
existing.detail.return_requested_quantity = MathBN.add(
existing.detail.return_requested_quantity,
action.details.quantity
)
if (previousEvents) {
for (const previousEvent of previousEvents) {
@@ -52,6 +73,8 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RECEIVE_RETURN_ITEM, {
previousEvent.details = JSON.parse(
JSON.stringify(previousEvent.original_.details)
)
transformPropertiesToBigNumber(previousEvent.details?.metadata)
delete previousEvent.original_
previousEvent.status = EVENT_STATUS.PENDING
@@ -84,7 +107,9 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RECEIVE_RETURN_ITEM, {
}
const quantityRequested = existing?.detail?.return_requested_quantity || 0
if (action.details?.quantity > quantityRequested) {
const greater = MathBN.gt(action.details?.quantity, quantityRequested)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot receive more items than what was requested to be returned for item ${refId}.`
@@ -1,4 +1,4 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
@@ -11,16 +11,22 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RETURN_ITEM, {
)!
existing.detail.return_requested_quantity ??= 0
existing.detail.return_requested_quantity += action.details.quantity
existing.detail.return_requested_quantity = MathBN.add(
existing.detail.return_requested_quantity,
action.details.quantity
)
return existing.unit_price * action.details.quantity
return MathBN.mult(existing.unit_price, action.details.quantity)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.return_requested_quantity -= action.details.quantity
existing.detail.return_requested_quantity = MathBN.sub(
existing.detail.return_requested_quantity,
action.details.quantity
)
},
validate({ action, currentOrder }) {
const refId = action.details?.reference_id
@@ -47,11 +53,13 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RETURN_ITEM, {
)
}
const quantityAvailable =
(existing!.detail?.shipped_quantity ?? 0) -
(existing!.detail?.return_requested_quantity ?? 0)
const quantityAvailable = MathBN.sub(
existing!.detail?.shipped_quantity ?? 0,
existing!.detail?.return_requested_quantity ?? 0
)
if (action.details?.quantity > quantityAvailable) {
const greater = MathBN.gt(action.details?.quantity, quantityAvailable)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot request to return more items than what was shipped for item ${refId}.`
+16 -8
View File
@@ -1,4 +1,4 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
@@ -10,14 +10,20 @@ OrderChangeProcessing.registerActionType(ChangeActionType.SHIP_ITEM, {
existing.detail.shipped_quantity ??= 0
existing.detail.shipped_quantity += action.details.quantity
existing.detail.shipped_quantity = MathBN.add(
existing.detail.shipped_quantity,
action.details.quantity
)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.reference_id
)!
existing.detail.shipped_quantity -= action.details.quantity
existing.detail.shipped_quantity = MathBN.sub(
existing.detail.shipped_quantity,
action.details.quantity
)
},
validate({ action, currentOrder }) {
const refId = action.details?.reference_id
@@ -43,18 +49,20 @@ OrderChangeProcessing.registerActionType(ChangeActionType.SHIP_ITEM, {
)
}
if (action.details?.quantity < 1) {
if (MathBN.lt(action.details?.quantity, 1)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity of item ${refId} must be greater than 0.`
)
}
const notShipped =
(existing.detail?.fulfilled_quantity as number) -
(existing.detail?.shipped_quantity as number)
const notShipped = MathBN.sub(
existing.detail?.fulfilled_quantity,
existing.detail?.shipped_quantity
)
if (action.details?.quantity > notShipped) {
const greater = MathBN.gt(action.details?.quantity, notShipped)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot ship more items than what was fulfilled for item ${refId}.`
@@ -1,4 +1,4 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
@@ -9,14 +9,20 @@ OrderChangeProcessing.registerActionType(ChangeActionType.WRITE_OFF_ITEM, {
)!
existing.detail.written_off_quantity ??= 0
existing.detail.written_off_quantity += action.details.quantity
existing.detail.written_off_quantity = MathBN.add(
existing.detail.written_off_quantity,
action.details.quantity
)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.written_off_quantity -= action.details.quantity
existing.detail.written_off_quantity = MathBN.sub(
existing.detail.written_off_quantity,
action.details.quantity
)
},
validate({ action, currentOrder }) {
const refId = action.details?.reference_id
@@ -44,7 +50,8 @@ OrderChangeProcessing.registerActionType(ChangeActionType.WRITE_OFF_ITEM, {
}
const quantityAvailable = existing!.quantity ?? 0
if (action.details?.quantity > quantityAvailable) {
const greater = MathBN.gt(action.details?.quantity, quantityAvailable)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Cannot claim more items than what was ordered."
@@ -1,5 +1,9 @@
import { OrderSummaryDTO } from "@medusajs/types"
import { isDefined } from "@medusajs/utils"
import { BigNumberInput, OrderSummaryDTO } from "@medusajs/types"
import {
MathBN,
isDefined,
transformPropertiesToBigNumber,
} from "@medusajs/utils"
import {
ActionTypeDefinition,
EVENT_STATUS,
@@ -11,7 +15,7 @@ import {
} from "@types"
type InternalOrderSummary = OrderSummaryCalculated & {
futureTemporarySum: number
futureTemporarySum: BigNumberInput
}
export class OrderChangeProcessing {
@@ -26,7 +30,7 @@ export class OrderChangeProcessing {
private actions: InternalOrderChangeEvent[]
private actionsProcessed: { [key: string]: InternalOrderChangeEvent[] } = {}
private groupTotal: Record<string, number> = {}
private groupTotal: Record<string, BigNumberInput> = {}
private summary: InternalOrderSummary
public static registerActionType(key: string, type: ActionTypeDefinition) {
@@ -46,9 +50,9 @@ export class OrderChangeProcessing {
this.transactions = JSON.parse(JSON.stringify(transactions ?? []))
this.actions = JSON.parse(JSON.stringify(actions ?? []))
const transactionTotal = transactions.reduce((acc, transaction) => {
return acc + transaction.amount
}, 0)
const transactionTotal = MathBN.add(...transactions.map((tr) => tr.amount))
transformPropertiesToBigNumber(this.order.metadata)
this.summary = {
futureDifference: 0,
@@ -57,8 +61,8 @@ export class OrderChangeProcessing {
pendingDifference: 0,
futureTemporarySum: 0,
differenceSum: 0,
currentOrderTotal: order?.summary?.total ?? 0,
originalOrderTotal: order?.summary?.total ?? 0,
currentOrderTotal: this.order.summary?.total ?? 0,
originalOrderTotal: this.order.summary?.total ?? 0,
transactionTotal,
}
}
@@ -97,55 +101,71 @@ export class OrderChangeProcessing {
...OrderChangeProcessing.typeDefinition[action.action],
}
const amount = action.amount! * (type.isDeduction ? -1 : 1)
const amount = MathBN.mult(action.amount!, type.isDeduction ? -1 : 1)
if (action.group_id && !action.evaluationOnly) {
this.groupTotal[action.group_id] ??= 0
this.groupTotal[action.group_id] += amount
this.groupTotal[action.group_id] = MathBN.add(
this.groupTotal[action.group_id],
amount
)
}
if (type.awaitRequired && !this.isEventDone(action)) {
if (action.evaluationOnly) {
summary.futureTemporarySum += amount
summary.futureTemporarySum = MathBN.add(
summary.futureTemporarySum,
amount
)
} else {
summary.temporaryDifference += amount
summary.temporaryDifference = MathBN.add(
summary.temporaryDifference,
amount
)
}
}
if (action.evaluationOnly) {
summary.futureDifference += amount
summary.futureDifference = MathBN.add(summary.futureDifference, amount)
} else {
if (!this.isEventDone(action) && !action.group_id) {
summary.differenceSum += amount
summary.differenceSum = MathBN.add(summary.differenceSum, amount)
}
summary.currentOrderTotal += amount
summary.currentOrderTotal = MathBN.add(
summary.currentOrderTotal,
amount
)
}
}
const groupSum = Object.values(this.groupTotal).reduce((acc, amount) => {
return acc + amount
}, 0)
const groupSum = MathBN.add(...Object.values(this.groupTotal))
summary.differenceSum += groupSum
summary.differenceSum = MathBN.add(summary.differenceSum, groupSum)
summary.transactionTotal = this.transactions.reduce((acc, transaction) => {
return acc + transaction.amount
}, 0)
summary.transactionTotal = MathBN.sum(
...this.transactions.map((tr) => tr.amount)
)
summary.futureTemporaryDifference =
summary.futureDifference - summary.futureTemporarySum
summary.futureTemporaryDifference = MathBN.sub(
summary.futureDifference,
summary.futureTemporarySum
)
summary.temporaryDifference =
summary.differenceSum - summary.temporaryDifference
summary.temporaryDifference = MathBN.sub(
summary.differenceSum,
summary.temporaryDifference
)
summary.pendingDifference =
summary.currentOrderTotal - summary.transactionTotal
summary.pendingDifference = MathBN.sub(
summary.currentOrderTotal,
summary.transactionTotal
)
}
private processAction_(
action: InternalOrderChangeEvent,
isReplay = false
): number | void {
): BigNumberInput | void {
const type = {
...OrderChangeProcessing.defaultConfig,
...OrderChangeProcessing.typeDefinition[action.action],
@@ -166,7 +186,7 @@ export class OrderChangeProcessing {
)
}
let calculatedAmount: number = action.amount ?? 0
let calculatedAmount = action.amount ?? 0
const params = {
actions: this.actions,
action,
@@ -181,7 +201,7 @@ export class OrderChangeProcessing {
}
if (typeof type.operation === "function") {
calculatedAmount = type.operation(params) as number
calculatedAmount = type.operation(params) as BigNumberInput
// the action.amount has priority over the calculated amount
if (!isDefined(action.amount)) {
@@ -207,7 +227,10 @@ export class OrderChangeProcessing {
}
if (action.resolve.amount && !action.evaluationOnly) {
this.groupTotal[groupId] ??= 0
this.groupTotal[groupId] -= action.resolve.amount
this.groupTotal[groupId] = MathBN.sub(
this.groupTotal[groupId],
action.resolve.amount
)
}
}