diff --git a/packages/order/integration-tests/__tests__/order-edit.ts b/packages/order/integration-tests/__tests__/order-edit.ts index 44a1dab9b8..5bab0d9dec 100644 --- a/packages/order/integration-tests/__tests__/order-edit.ts +++ b/packages/order/integration-tests/__tests__/order-edit.ts @@ -1,7 +1,8 @@ import { Modules } from "@medusajs/modules-sdk" import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types" -import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" +import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils" import { ChangeActionType } from "../../src/utils" +import { BigNumber } from "@medusajs/utils" jest.setTimeout(100000) @@ -17,7 +18,7 @@ moduleIntegrationTestRunner({ title: "Item 1", subtitle: "Subtitle 1", thumbnail: "thumbnail1.jpg", - quantity: 1, + quantity: new BigNumber(1), product_id: "product1", product_title: "Product 1", product_description: "Description 1", diff --git a/packages/order/src/services/__tests__/util/actions/exchanges.ts b/packages/order/src/services/__tests__/util/actions/exchanges.ts index de2ef8e134..1c8063e600 100644 --- a/packages/order/src/services/__tests__/util/actions/exchanges.ts +++ b/packages/order/src/services/__tests__/util/actions/exchanges.ts @@ -96,18 +96,20 @@ describe("Order Exchange - Actions", function () { actions: actions, }) - expect(changes.summary).toEqual({ - transactionTotal: 0, + const sumToJSON = JSON.parse(JSON.stringify(changes.summary)) + expect(sumToJSON).toEqual({ + transactionTotal: "0", originalOrderTotal: 270, - currentOrderTotal: 312.5, - temporaryDifference: 62.5, + currentOrderTotal: "312.5", + temporaryDifference: "62.5", futureDifference: 0, - futureTemporaryDifference: 0, - pendingDifference: 312.5, - differenceSum: 42.5, + futureTemporaryDifference: "0", + pendingDifference: "312.5", + differenceSum: "42.5", }) - expect(changes.order.items).toEqual([ + const toJson = JSON.parse(JSON.stringify(changes.order.items)) + expect(toJson).toEqual([ { id: "1", quantity: 1, @@ -144,7 +146,7 @@ describe("Order Exchange - Actions", function () { quantity: 3, shipped_quantity: 3, fulfilled_quantity: 3, - return_requested_quantity: 1, + return_requested_quantity: "1", return_received_quantity: 0, return_dismissed_quantity: 0, written_off_quantity: 0, diff --git a/packages/order/src/services/__tests__/util/actions/returns.ts b/packages/order/src/services/__tests__/util/actions/returns.ts index 45356e4bde..5e43d6c9f0 100644 --- a/packages/order/src/services/__tests__/util/actions/returns.ts +++ b/packages/order/src/services/__tests__/util/actions/returns.ts @@ -125,7 +125,8 @@ describe("Order Return - Actions", function () { actions, }) - expect(changes.order.items).toEqual([ + const toJson = JSON.parse(JSON.stringify(changes.order.items)) + expect(toJson).toEqual([ { id: "1", quantity: 1, @@ -148,7 +149,7 @@ describe("Order Return - Actions", function () { quantity: 2, shipped_quantity: 1, fulfilled_quantity: 1, - return_requested_quantity: 1, + return_requested_quantity: "1", return_received_quantity: 0, return_dismissed_quantity: 0, written_off_quantity: 0, @@ -162,7 +163,7 @@ describe("Order Return - Actions", function () { quantity: 3, shipped_quantity: 3, fulfilled_quantity: 3, - return_requested_quantity: 2, + return_requested_quantity: "2", return_received_quantity: 0, return_dismissed_quantity: 0, written_off_quantity: 0, @@ -253,7 +254,10 @@ describe("Order Return - Actions", function () { ], }) - expect(receivedChanges.order.items).toEqual([ + const toJsonReceived = JSON.parse( + JSON.stringify(receivedChanges.order.items) + ) + expect(toJsonReceived).toEqual([ { id: "1", quantity: 1, @@ -276,7 +280,7 @@ describe("Order Return - Actions", function () { quantity: 2, shipped_quantity: 1, fulfilled_quantity: 1, - return_requested_quantity: 1, + return_requested_quantity: "1", return_received_quantity: 0, return_dismissed_quantity: 0, written_off_quantity: 0, @@ -290,9 +294,9 @@ describe("Order Return - Actions", function () { quantity: 3, shipped_quantity: 3, fulfilled_quantity: 3, - return_requested_quantity: 0, - return_received_quantity: 1, - return_dismissed_quantity: 1, + return_requested_quantity: "0", + return_received_quantity: "1", + return_dismissed_quantity: "1", written_off_quantity: 0, }, }, diff --git a/packages/order/src/types/utils/index.ts b/packages/order/src/types/utils/index.ts index 5ccaf57451..481b39846c 100644 --- a/packages/order/src/types/utils/index.ts +++ b/packages/order/src/types/utils/index.ts @@ -1,29 +1,31 @@ +import { BigNumberInput } from "@medusajs/types" + export type VirtualOrder = { items: { id: string - unit_price: number - quantity: number + unit_price: BigNumberInput + quantity: BigNumberInput detail: { id?: string - quantity: number - shipped_quantity: number - fulfilled_quantity: number - return_requested_quantity: number - return_received_quantity: number - return_dismissed_quantity: number - written_off_quantity: number + quantity: BigNumberInput + shipped_quantity: BigNumberInput + fulfilled_quantity: BigNumberInput + return_requested_quantity: BigNumberInput + return_received_quantity: BigNumberInput + return_dismissed_quantity: BigNumberInput + written_off_quantity: BigNumberInput metadata?: Record } }[] shipping_methods: { id: string - price: number + price: BigNumberInput }[] summary: { - total: number + total: BigNumberInput } metadata?: Record @@ -36,23 +38,23 @@ export enum EVENT_STATUS { } export interface OrderSummaryCalculated { - currentOrderTotal: number - originalOrderTotal: number - transactionTotal: number - futureDifference: number - pendingDifference: number - futureTemporaryDifference: number - temporaryDifference: number - differenceSum: number + currentOrderTotal: BigNumberInput + originalOrderTotal: BigNumberInput + transactionTotal: BigNumberInput + futureDifference: BigNumberInput + pendingDifference: BigNumberInput + futureTemporaryDifference: BigNumberInput + temporaryDifference: BigNumberInput + differenceSum: BigNumberInput } export interface OrderTransaction { - amount: number + amount: BigNumberInput } export interface OrderChangeEvent { action: string - amount?: number + amount?: BigNumberInput reference?: string reference_id?: string @@ -66,7 +68,7 @@ export interface OrderChangeEvent { resolve?: { group_id?: string reference_id?: string - amount?: number + amount?: BigNumberInput } } @@ -91,8 +93,8 @@ export interface ActionTypeDefinition { versioning?: boolean void?: boolean commitsAction?: string - operation?: (obj: OrderReferences) => number | void - revert?: (obj: OrderReferences) => number | void + operation?: (obj: OrderReferences) => BigNumberInput | void + revert?: (obj: OrderReferences) => BigNumberInput | void validate?: (obj: OrderReferences) => void [key: string]: unknown } diff --git a/packages/order/src/utils/actions/cancel-return.ts b/packages/order/src/utils/actions/cancel-return.ts index e39e69d4c2..20d1a47e95 100644 --- a/packages/order/src/utils/actions/cancel-return.ts +++ b/packages/order/src/utils/actions/cancel-return.ts @@ -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}.` diff --git a/packages/order/src/utils/actions/fulfill-item.ts b/packages/order/src/utils/actions/fulfill-item.ts index 17649dac3b..693f0ce830 100644 --- a/packages/order/src/utils/actions/fulfill-item.ts +++ b/packages/order/src/utils/actions/fulfill-item.ts @@ -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}.` diff --git a/packages/order/src/utils/actions/item-add.ts b/packages/order/src/utils/actions/item-add.ts index 6f112d748c..ffcc5e6f6c 100644 --- a/packages/order/src/utils/actions/item-add.ts +++ b/packages/order/src/utils/actions/item-add.ts @@ -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.` diff --git a/packages/order/src/utils/actions/item-remove.ts b/packages/order/src/utils/actions/item-remove.ts index eed706cc48..17bfe416b2 100644 --- a/packages/order/src/utils/actions/item-remove.ts +++ b/packages/order/src/utils/actions/item-remove.ts @@ -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}.` diff --git a/packages/order/src/utils/actions/receive-damaged-return-item.ts b/packages/order/src/utils/actions/receive-damaged-return-item.ts index 1ddcad5b69..636cba24b5 100644 --- a/packages/order/src/utils/actions/receive-damaged-return-item.ts +++ b/packages/order/src/utils/actions/receive-damaged-return-item.ts @@ -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) { diff --git a/packages/order/src/utils/actions/receive-return-item.ts b/packages/order/src/utils/actions/receive-return-item.ts index 6ce6105aad..feb6aac04f 100644 --- a/packages/order/src/utils/actions/receive-return-item.ts +++ b/packages/order/src/utils/actions/receive-return-item.ts @@ -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}.` diff --git a/packages/order/src/utils/actions/return-item.ts b/packages/order/src/utils/actions/return-item.ts index ca606e1e82..f66e207826 100644 --- a/packages/order/src/utils/actions/return-item.ts +++ b/packages/order/src/utils/actions/return-item.ts @@ -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}.` diff --git a/packages/order/src/utils/actions/ship-item.ts b/packages/order/src/utils/actions/ship-item.ts index 2558f96b2f..0465fa511a 100644 --- a/packages/order/src/utils/actions/ship-item.ts +++ b/packages/order/src/utils/actions/ship-item.ts @@ -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}.` diff --git a/packages/order/src/utils/actions/write-off-item.ts b/packages/order/src/utils/actions/write-off-item.ts index 3a5237601e..fd48bd8a85 100644 --- a/packages/order/src/utils/actions/write-off-item.ts +++ b/packages/order/src/utils/actions/write-off-item.ts @@ -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." diff --git a/packages/order/src/utils/calculate-order-change.ts b/packages/order/src/utils/calculate-order-change.ts index 3132e3ce88..6e739b7bc4 100644 --- a/packages/order/src/utils/calculate-order-change.ts +++ b/packages/order/src/utils/calculate-order-change.ts @@ -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 = {} + private groupTotal: Record = {} 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 + ) } } diff --git a/packages/types/src/totals/big-number.ts b/packages/types/src/totals/big-number.ts index e6ef0dbaa1..fc93c79ed9 100644 --- a/packages/types/src/totals/big-number.ts +++ b/packages/types/src/totals/big-number.ts @@ -1,8 +1,8 @@ -import BigNumber from "bignumber.js" +import BigNumberJS from "bignumber.js" export type BigNumberRawValue = { value: string | number [key: string]: unknown } -export type BigNumberInput = BigNumberRawValue | number | string | BigNumber +export type BigNumberInput = BigNumberRawValue | number | string | BigNumberJS diff --git a/packages/utils/src/dal/mikro-orm/big-number-field.ts b/packages/utils/src/dal/mikro-orm/big-number-field.ts index 5641b34679..56142e34c1 100644 --- a/packages/utils/src/dal/mikro-orm/big-number-field.ts +++ b/packages/utils/src/dal/mikro-orm/big-number-field.ts @@ -1,7 +1,7 @@ -import { BigNumberInput } from "@medusajs/types" import { Property } from "@mikro-orm/core" import { isPresent, trimZeros } from "../../common" import { BigNumber } from "../../totals/big-number" +import { BigNumberInput } from "@medusajs/types" export function MikroOrmBigNumberProperty( options: Parameters[0] & { @@ -13,47 +13,52 @@ export function MikroOrmBigNumberProperty( Object.defineProperty(target, columnName, { get() { - return this.__helper.__data[columnName] + let value = this.__helper?.__data?.[columnName] + + if (!value && this[rawColumnName]) { + value = new BigNumber(this[rawColumnName].value, { + precision: this[rawColumnName].precision, + }).numeric + } + + return value }, set(value: BigNumberInput) { if (options?.nullable && !isPresent(value)) { this.__helper.__data[columnName] = null + this.__helper.__data[rawColumnName] this[rawColumnName] = null - - return - } - - let bigNumber: BigNumber - - if (value instanceof BigNumber) { - bigNumber = value - } else if (this[rawColumnName]) { - const precision = this[rawColumnName].precision - - this[rawColumnName].value = trimZeros( - new BigNumber(value, { - precision, - }).raw!.value as string - ) - - bigNumber = new BigNumber(this[rawColumnName]) } else { - bigNumber = new BigNumber(value) + let bigNumber: BigNumber + + if (value instanceof BigNumber) { + bigNumber = value + } else if (this[rawColumnName]) { + const precision = this[rawColumnName].precision + bigNumber = new BigNumber(value, { + precision, + }) + } else { + bigNumber = new BigNumber(value) + } + + const raw = bigNumber.raw! + raw.value = trimZeros(raw.value as string) + + this.__helper.__data[columnName] = bigNumber.numeric + this.__helper.__data[rawColumnName] = raw + + this[rawColumnName] = raw } - this.__helper.__data[columnName] = bigNumber.numeric - - const raw = bigNumber.raw! - raw.value = trimZeros(raw.value as string) - - this[rawColumnName] = raw - this.__helper.__touched = !this.__helper.hydrator.isRunning() }, + enumerable: true, + configurable: true, }) Property({ - type: "number", + type: "any", columnType: "numeric", trackChanges: false, ...options, diff --git a/packages/utils/src/dal/mikro-orm/mikro-orm-repository.ts b/packages/utils/src/dal/mikro-orm/mikro-orm-repository.ts index 58cb7542a8..a167dcc4cf 100644 --- a/packages/utils/src/dal/mikro-orm/mikro-orm-repository.ts +++ b/packages/utils/src/dal/mikro-orm/mikro-orm-repository.ts @@ -278,10 +278,7 @@ export function mikroOrmBaseRepositoryFactory( async update(data: { entity; update }[], context?: Context): Promise { const manager = this.getActiveManager(context) const entities = data.map((data_) => { - return manager.assign( - data_.entity, - data_.update as RequiredEntityData - ) + return manager.assign(data_.entity, data_.update) }) manager.persist(entities) diff --git a/packages/utils/src/totals/__tests__/transform-properties-to-bigbumber.spec.ts b/packages/utils/src/totals/__tests__/transform-properties-to-bigbumber.spec.ts new file mode 100644 index 0000000000..df05bdc38d --- /dev/null +++ b/packages/utils/src/totals/__tests__/transform-properties-to-bigbumber.spec.ts @@ -0,0 +1,130 @@ +import { BigNumber } from "../big-number" +import { transformPropertiesToBigNumber } from "../transform-properties-to-bignumber" + +describe("Transfor Properties to BigNumber", function () { + it("should transform all properties containing matching prefix _raw to BigNumber", function () { + const obj = { + price: 42, + raw_price: { + value: "42", + precision: 10, + }, + field: 111, + metadata: { + numeric_field: 100, + raw_numeric_field: { + value: "100", + }, + random_field: 134, + }, + + abc: null, + raw_abc: { + value: "9.00000010000103991234", + precision: 20, + }, + } + + transformPropertiesToBigNumber(obj) + + const price = obj.price as unknown as BigNumber + expect(price).toBeInstanceOf(BigNumber) + expect(price.numeric).toEqual(42) + expect(price.raw).toEqual({ + value: "42", + precision: 10, + }) + + expect(obj.field).toBe(111) + + const metaNum = obj.metadata.numeric_field as unknown as BigNumber + expect(metaNum).toBeInstanceOf(BigNumber) + expect(metaNum.numeric).toEqual(100) + expect(metaNum.raw).toEqual({ + value: "100", + precision: 20, + }) + expect(obj.metadata.random_field).toBe(134) + + const abc = obj.abc as unknown as BigNumber + expect(abc).toBeInstanceOf(BigNumber) + expect(abc.numeric).toEqual(9.00000010000104) + expect(abc.raw).toEqual({ + value: "9.00000010000103991234", + precision: 20, + }) + }) + + it("should transform all properties on the option 'include' to BigNumber", function () { + const obj = { + price: 42, + raw_price: { + value: "42", + precision: 10, + }, + field: 111, + metadata: { + random_field: 134, + }, + } + + transformPropertiesToBigNumber(obj, { + include: ["metadata.random_field"], + }) + + expect(obj.price).toBeInstanceOf(BigNumber) + + const price = obj.price as unknown as BigNumber + expect(price.numeric).toEqual(42) + expect(price.raw).toEqual({ + value: "42", + precision: 10, + }) + + expect(obj.field).toBe(111) + + const metaNum = obj.metadata.random_field as unknown as BigNumber + expect(metaNum).toBeInstanceOf(BigNumber) + expect(metaNum.numeric).toEqual(134) + expect(metaNum.raw).toEqual({ + value: "134.00000000000000000", + precision: 20, + }) + }) + + it("should transform all properties containing matching prefix _raw to BigNumber excluding selected ones", function () { + const obj = { + price: 42, + raw_price: { + value: "42", + precision: 10, + }, + metadata: { + numeric_field: 100, + raw_numeric_field: { + value: "100", + }, + }, + + abc: null, + raw_abc: { + value: "9.00000010000103991234", + precision: 20, + }, + } + + transformPropertiesToBigNumber(obj, { + exclude: ["abc", "metadata.numeric_field"], + }) + + const price = obj.price as unknown as BigNumber + expect(obj.price).toBeInstanceOf(BigNumber) + expect(price.numeric).toEqual(42) + expect(price.raw).toEqual({ + value: "42", + precision: 10, + }) + + expect(obj.abc).toEqual(null) + }) +}) diff --git a/packages/utils/src/totals/big-number.ts b/packages/utils/src/totals/big-number.ts index 10514cc40f..d2afdcae64 100644 --- a/packages/utils/src/totals/big-number.ts +++ b/packages/utils/src/totals/big-number.ts @@ -7,18 +7,24 @@ export class BigNumber { private numeric_: number private raw_?: BigNumberRawValue + private bignumber_?: BigNumberJS - constructor(rawValue: BigNumberInput, options?: { precision?: number }) { + constructor( + rawValue: BigNumberInput | BigNumber, + options?: { precision?: number } + ) { this.setRawValueOrThrow(rawValue, options) } setRawValueOrThrow( - rawValue: BigNumberInput, + rawValue: BigNumberInput | BigNumber, { precision }: { precision?: number } = {} ) { precision ??= BigNumber.DEFAULT_PRECISION - if (BigNumberJS.isBigNumber(rawValue)) { + if (rawValue instanceof BigNumber) { + Object.assign(this, rawValue) + } else if (BigNumberJS.isBigNumber(rawValue)) { /** * Example: * const bnUnitValue = new BigNumberJS("10.99") @@ -29,6 +35,7 @@ export class BigNumber { value: rawValue.toPrecision(precision), precision, } + this.bignumber_ = rawValue } else if (isString(rawValue)) { /** * Example: const unitValue = "1234.1234" @@ -40,26 +47,31 @@ export class BigNumber { value: bigNum.toPrecision(precision), precision, } + this.bignumber_ = bigNum } else if (isBigNumber(rawValue)) { /** * Example: const unitValue = { value: "1234.1234" } */ const definedPrecision = rawValue.precision ?? precision - this.numeric_ = BigNumberJS(rawValue.value).toNumber() + const bigNum = new BigNumberJS(rawValue.value) + this.numeric_ = bigNum.toNumber() this.raw_ = { ...rawValue, precision: definedPrecision, } + this.bignumber_ = bigNum } else if (typeof rawValue === `number` && !Number.isNaN(rawValue)) { /** * Example: const unitValue = 1234 */ this.numeric_ = rawValue as number + const bigNum = new BigNumberJS(rawValue as number) this.raw_ = { - value: BigNumberJS(rawValue as number).toPrecision(precision), + value: bigNum.toPrecision(precision), precision, } + this.bignumber_ = bigNum } else { throw new Error( `Invalid BigNumber value: ${rawValue}. Should be one of: string, number, BigNumber (bignumber.js), BigNumberRawValue` @@ -80,27 +92,33 @@ export class BigNumber { const newValue = new BigNumber(value) this.numeric_ = newValue.numeric_ this.raw_ = newValue.raw_ + this.bignumber_ = newValue.bignumber_ } get raw(): BigNumberRawValue | undefined { return this.raw_ } + get bigNumber(): BigNumberJS | undefined { + return this.bignumber_ + } + set raw(rawValue: BigNumberInput) { const newValue = new BigNumber(rawValue) this.numeric_ = newValue.numeric_ this.raw_ = newValue.raw_ + this.bignumber_ = newValue.bignumber_ } toJSON() { - return this.raw_ + return this.bignumber_ + ? this.bignumber_?.toNumber() + : this.raw_ ? new BigNumberJS(this.raw_.value).toNumber() : this.numeric_ } valueOf() { - return this.raw_ - ? new BigNumberJS(this.raw_.value).toNumber() - : this.numeric_ + return this.bignumber_ } } diff --git a/packages/utils/src/totals/index.ts b/packages/utils/src/totals/index.ts index 32969d2385..73f19e75ac 100644 --- a/packages/utils/src/totals/index.ts +++ b/packages/utils/src/totals/index.ts @@ -7,7 +7,10 @@ import { BigNumber as BigNumberJs } from "bignumber.js" import { BigNumber } from "./big-number" import { toBigNumberJs } from "./to-big-number-js" +export * from "./math" export * from "./promotion" +export * from "./to-big-number-js" +export * from "./transform-properties-to-bignumber" type GetLineItemTotalsContext = { includeTax?: boolean diff --git a/packages/utils/src/totals/math.ts b/packages/utils/src/totals/math.ts new file mode 100644 index 0000000000..5c2bfc27ba --- /dev/null +++ b/packages/utils/src/totals/math.ts @@ -0,0 +1,108 @@ +import { BigNumberInput, BigNumberRawValue } from "@medusajs/types" +import { BigNumber as BigNumberJS } from "bignumber.js" +import { isDefined } from "../common" +import { BigNumber } from "./big-number" + +type BNInput = BigNumberInput | BigNumber +export class MathBN { + static convert(num: BNInput): BigNumberJS { + if (num instanceof BigNumber) { + return num.bigNumber! + } else if (num instanceof BigNumberJS) { + return num + } else if (isDefined((num as BigNumberRawValue)?.value)) { + return new BigNumberJS((num as BigNumberRawValue).value) + } + + return new BigNumberJS(num as BigNumberJS | number) + } + + static add(...nums: BNInput[]): BigNumberJS { + let sum = new BigNumberJS(0) + for (const num of nums) { + const n = MathBN.convert(num) + sum = sum.plus(n) + } + return sum + } + + static sum(...nums: BNInput[]): BigNumberJS { + return MathBN.add(...nums) + } + + static sub(...nums: BNInput[]): BigNumberJS { + let agg = MathBN.convert(nums[0]) + for (let i = 1; i < nums.length; i++) { + const n = MathBN.convert(nums[i]) + agg = agg.minus(n) + } + return agg + } + + static mult(n1: BNInput, n2: BNInput): BigNumberJS { + const num1 = MathBN.convert(n1) + const num2 = MathBN.convert(n2) + return num1.times(num2) + } + + static div(n1: BNInput, n2: BNInput): BigNumberJS { + const num1 = MathBN.convert(n1) + const num2 = MathBN.convert(n2) + return num1.dividedBy(num2) + } + + static abs(n: BNInput): BigNumberJS { + const num = MathBN.convert(n) + return num.absoluteValue() + } + + static mod(n1: BNInput, n2: BNInput): BigNumberJS { + const num1 = MathBN.convert(n1) + const num2 = MathBN.convert(n2) + return num1.modulo(num2) + } + + static exp(n: BNInput, exp = 2): BigNumberJS { + const num = MathBN.convert(n) + const expBy = MathBN.convert(exp) + return num.exponentiatedBy(expBy) + } + + static min(...nums: BNInput[]): BigNumberJS { + return BigNumberJS.minimum(...nums.map((num) => MathBN.convert(num))) + } + + static max(...nums: BNInput[]): BigNumberJS { + return BigNumberJS.maximum(...nums.map((num) => MathBN.convert(num))) + } + + static gt(n1: BNInput, n2: BNInput): boolean { + const num1 = MathBN.convert(n1) + const num2 = MathBN.convert(n2) + return num1.isGreaterThan(num2) + } + + static gte(n1: BNInput, n2: BNInput): boolean { + const num1 = MathBN.convert(n1) + const num2 = MathBN.convert(n2) + return num1.isGreaterThanOrEqualTo(num2) + } + + static lt(n1: BNInput, n2: BNInput): boolean { + const num1 = MathBN.convert(n1) + const num2 = MathBN.convert(n2) + return num1.isLessThan(num2) + } + + static lte(n1: BNInput, n2: BNInput): boolean { + const num1 = MathBN.convert(n1) + const num2 = MathBN.convert(n2) + return num1.isLessThanOrEqualTo(num2) + } + + static eq(n1: BNInput, n2: BNInput): boolean { + const num1 = MathBN.convert(n1) + const num2 = MathBN.convert(n2) + return num1.isEqualTo(num2) + } +} diff --git a/packages/utils/src/totals/to-big-number-js.ts b/packages/utils/src/totals/to-big-number-js.ts index d3e94467e5..5fa4904009 100644 --- a/packages/utils/src/totals/to-big-number-js.ts +++ b/packages/utils/src/totals/to-big-number-js.ts @@ -2,14 +2,11 @@ import { BigNumberInput } from "@medusajs/types" import { BigNumber as BigNumberJs } from "bignumber.js" import { isDefined, toCamelCase } from "../common" import { BigNumber } from "./big-number" - type InputEntity = { [key in V]?: InputEntityField } type InputEntityField = number | string | BigNumber - type Camelize = V extends `${infer A}_${infer B}` ? `${A}${Camelize>}` : V - type Output = { [key in Camelize]: BigNumberJs } export function toBigNumberJs( diff --git a/packages/utils/src/totals/transform-properties-to-bignumber.ts b/packages/utils/src/totals/transform-properties-to-bignumber.ts new file mode 100644 index 0000000000..c85cdc9180 --- /dev/null +++ b/packages/utils/src/totals/transform-properties-to-bignumber.ts @@ -0,0 +1,56 @@ +import { BigNumber } from "./big-number" + +export function transformPropertiesToBigNumber( + obj, + { + prefix = "raw_", + include = [], + exclude = [], + }: { + prefix?: string + include?: string[] + exclude?: string[] + } = {} +) { + const stack = [{ current: obj, path: "" }] + + while (stack.length > 0) { + const { current, path } = stack.pop()! + + if ( + current == null || + typeof current !== "object" || + current instanceof BigNumber + ) { + continue + } + + if (Array.isArray(current)) { + current.forEach((element, index) => + stack.push({ current: element, path }) + ) + } else { + for (const key of Object.keys(current)) { + const value = current[key] + const currentPath = path ? `${path}.${key}` : key + + if (value != null && !exclude.includes(currentPath)) { + if (key.startsWith(prefix)) { + const newKey = key.replace(prefix, "") + + const newPath = path ? `${path}.${newKey}` : newKey + if (!exclude.includes(newPath)) { + current[newKey] = new BigNumber(value) + continue + } + } else if (include.includes(currentPath)) { + current[key] = new BigNumber(value) + continue + } + } + + stack.push({ current: value, path: currentPath }) + } + } + } +}