From f57dc98c7a15a31890ed325e3f9037d6758b831a Mon Sep 17 00:00:00 2001 From: "Carlos R. L. Rodrigues" <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Date: Mon, 20 May 2024 15:48:57 -0300 Subject: [PATCH] chore(order): Paid Total and Refunded Total (#7373) --- .../src/order/steps/archive-orders.ts | 8 +- .../src/order/steps/complete-orders.ts | 4 +- .../src/order/workflows/archive-orders.ts | 2 +- .../src/order/workflows/complete-orders.ts | 2 +- .../core/types/src/http/order/admin/index.ts | 2 + packages/core/types/src/order/common.ts | 7 +- packages/core/types/src/order/mutations.ts | 2 +- packages/core/types/src/order/service.ts | 30 +- .../api-v2/admin/orders/[id]/archive/route.ts | 2 +- .../admin/orders/[id]/complete/route.ts | 2 +- .../api-v2/admin/orders/[id]/refund/route.ts | 19 -- .../api-v2/admin/orders/fulfillments/route.ts | 2 +- .../api-v2/admin/returns/[id]/cancel/route.ts | 4 +- .../admin/returns/[id]/receive/route.ts | 5 +- .../src/api-v2/admin/returns/[id]/route.ts | 2 +- .../src/api-v2/store/return/validators.ts | 1 - .../__tests__/create-order.ts | 90 ++++++ .../src/migrations/Migration20240219102530.ts | 16 +- .../modules/order/src/models/order-summary.ts | 3 + .../modules/order/src/models/transaction.ts | 23 ++ .../__tests__/util/actions/exchanges.ts | 2 + .../src/services/order-module-service.ts | 268 +++++++++++------- .../modules/order/src/types/utils/index.ts | 2 + .../order/src/utils/calculate-order-change.ts | 17 +- 24 files changed, 340 insertions(+), 175 deletions(-) delete mode 100644 packages/medusa/src/api-v2/admin/orders/[id]/refund/route.ts diff --git a/packages/core/core-flows/src/order/steps/archive-orders.ts b/packages/core/core-flows/src/order/steps/archive-orders.ts index a400d484b4..42d2f7aba0 100644 --- a/packages/core/core-flows/src/order/steps/archive-orders.ts +++ b/packages/core/core-flows/src/order/steps/archive-orders.ts @@ -2,19 +2,19 @@ import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { IOrderModuleService } from "@medusajs/types" import { StepResponse, createStep } from "@medusajs/workflows-sdk" -type CompleteOrdersStepInput = { - order_ids: string[] +type ArchiveOrdersStepInput = { + orderIds: string[] } export const archiveOrdersStepId = "archive-orders" export const archiveOrdersStep = createStep( archiveOrdersStepId, - async (data: CompleteOrdersStepInput, { container }) => { + async (data: ArchiveOrdersStepInput, { container }) => { const service = container.resolve( ModuleRegistrationName.ORDER ) - const archived = await service.archive(data.order_ids) + const archived = await service.archive(data.orderIds) return new StepResponse( archived, archived.map((store) => { diff --git a/packages/core/core-flows/src/order/steps/complete-orders.ts b/packages/core/core-flows/src/order/steps/complete-orders.ts index ad49f14df7..ae049408f5 100644 --- a/packages/core/core-flows/src/order/steps/complete-orders.ts +++ b/packages/core/core-flows/src/order/steps/complete-orders.ts @@ -3,7 +3,7 @@ import { IOrderModuleService } from "@medusajs/types" import { StepResponse, createStep } from "@medusajs/workflows-sdk" type CompleteOrdersStepInput = { - order_ids: string[] + orderIds: string[] } export const completeOrdersStepId = "complete-orders" @@ -14,7 +14,7 @@ export const completeOrdersStep = createStep( ModuleRegistrationName.ORDER ) - const completed = await service.completeOrder(data.order_ids) + const completed = await service.completeOrder(data.orderIds) return new StepResponse( completed, completed.map((store) => { diff --git a/packages/core/core-flows/src/order/workflows/archive-orders.ts b/packages/core/core-flows/src/order/workflows/archive-orders.ts index 9ffac0bfcf..3419b21882 100644 --- a/packages/core/core-flows/src/order/workflows/archive-orders.ts +++ b/packages/core/core-flows/src/order/workflows/archive-orders.ts @@ -3,7 +3,7 @@ import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" import { archiveOrdersStep } from "../steps" type ArchiveOrdersStepInput = { - order_ids: string[] + orderIds: string[] } export const archiveOrderWorkflowId = "archive-order-workflow" diff --git a/packages/core/core-flows/src/order/workflows/complete-orders.ts b/packages/core/core-flows/src/order/workflows/complete-orders.ts index c20e0027bc..e8ac7c7e44 100644 --- a/packages/core/core-flows/src/order/workflows/complete-orders.ts +++ b/packages/core/core-flows/src/order/workflows/complete-orders.ts @@ -3,7 +3,7 @@ import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" import { completeOrdersStep } from "../steps" type CompleteOrdersStepInput = { - order_ids: string[] + orderIds: string[] } export const completeOrderWorkflowId = "complete-order-workflow" diff --git a/packages/core/types/src/http/order/admin/index.ts b/packages/core/types/src/http/order/admin/index.ts index 620b90d3d5..bde9c301ac 100644 --- a/packages/core/types/src/http/order/admin/index.ts +++ b/packages/core/types/src/http/order/admin/index.ts @@ -20,6 +20,8 @@ interface OrderSummary { future_projected_total: number balance: number future_balance: number + paid_total: number + refunded_total: number } interface OrderAdjustmentLine { diff --git a/packages/core/types/src/order/common.ts b/packages/core/types/src/order/common.ts index 038aaa2573..807d4e39d1 100644 --- a/packages/core/types/src/order/common.ts +++ b/packages/core/types/src/order/common.ts @@ -38,6 +38,9 @@ export type OrderSummaryDTO = { balance: BigNumberValue future_balance: BigNumberValue + + paid_total: BigNumberValue + refunded_total: BigNumberValue } export interface OrderAdjustmentLineDTO { @@ -1266,10 +1269,6 @@ export interface OrderTransactionDTO { * The ID of the reference */ reference_id: string - /** - * The metadata of the transaction - */ - metadata: Record | null /** * When the transaction was created */ diff --git a/packages/core/types/src/order/mutations.ts b/packages/core/types/src/order/mutations.ts index dcbc5327fb..b8e0a70c3a 100644 --- a/packages/core/types/src/order/mutations.ts +++ b/packages/core/types/src/order/mutations.ts @@ -317,7 +317,7 @@ export interface UpdateOrderChangeActionDTO { export interface CreateOrderTransactionDTO { order_id: string description?: string - reference_type?: string + reference?: string reference_id?: string internal_note?: string created_by?: string diff --git a/packages/core/types/src/order/service.ts b/packages/core/types/src/order/service.ts index 97a7e5d2ee..3d0a8ed6df 100644 --- a/packages/core/types/src/order/service.ts +++ b/packages/core/types/src/order/service.ts @@ -59,8 +59,6 @@ import { UpdateOrderReturnReasonWithSelectorDTO, UpdateOrderShippingMethodAdjustmentDTO, UpdateOrderShippingMethodTaxLineDTO, - UpdateOrderTransactionDTO, - UpdateOrderTransactionWithSelectorDTO, UpsertOrderLineItemAdjustmentDTO, } from "./mutations" @@ -1400,14 +1398,8 @@ export interface IOrderModuleService extends IModuleService { revertLastVersion(orderId: string, sharedContext?: Context): Promise - retrieveTransaction( - id: string, - config?: FindConfig, - sharedContext?: Context - ): Promise - listTransactions( - filters: FilterableOrderTransactionProps, + filters?: FilterableOrderTransactionProps, config?: FindConfig, sharedContext?: Context ): Promise @@ -1422,33 +1414,19 @@ export interface IOrderModuleService extends IModuleService { sharedContext?: Context ): Promise - updateTransactions( - data: UpdateOrderTransactionWithSelectorDTO[] - ): Promise - updateTransactions( - selector: Partial, - data: Partial, - sharedContext?: Context - ): Promise - updateTransactions( - id: string, - data: Partial, - sharedContext?: Context - ): Promise - deleteTransactions( - returnReasonIds: string[], + transactionIds: string | object | string[] | object[], sharedContext?: Context ): Promise softDeleteTransactions( - storeIds: string[], + transactionIds: string[], config?: SoftDeleteReturn, sharedContext?: Context ): Promise | void> restoreTransactions( - storeIds: string[], + transactionIds: string[], config?: RestoreReturn, sharedContext?: Context ): Promise | void> diff --git a/packages/medusa/src/api-v2/admin/orders/[id]/archive/route.ts b/packages/medusa/src/api-v2/admin/orders/[id]/archive/route.ts index e2dad32b1e..25ae624645 100644 --- a/packages/medusa/src/api-v2/admin/orders/[id]/archive/route.ts +++ b/packages/medusa/src/api-v2/admin/orders/[id]/archive/route.ts @@ -14,7 +14,7 @@ export const POST = async ( const { id } = req.params const { errors } = await archiveOrderWorkflow(req.scope).run({ - input: { order_ids: [req.validatedBody.order_id] }, + input: { orderIds: [req.validatedBody.order_id] }, throwOnError: false, }) diff --git a/packages/medusa/src/api-v2/admin/orders/[id]/complete/route.ts b/packages/medusa/src/api-v2/admin/orders/[id]/complete/route.ts index 48846d6090..9f58e05542 100644 --- a/packages/medusa/src/api-v2/admin/orders/[id]/complete/route.ts +++ b/packages/medusa/src/api-v2/admin/orders/[id]/complete/route.ts @@ -14,7 +14,7 @@ export const POST = async ( const { id } = req.params const { errors } = await completeOrderWorkflow(req.scope).run({ - input: { order_ids: [req.validatedBody.order_id] }, + input: { orderIds: [req.validatedBody.order_id] }, throwOnError: false, }) diff --git a/packages/medusa/src/api-v2/admin/orders/[id]/refund/route.ts b/packages/medusa/src/api-v2/admin/orders/[id]/refund/route.ts deleted file mode 100644 index 56fed6a12b..0000000000 --- a/packages/medusa/src/api-v2/admin/orders/[id]/refund/route.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { remoteQueryObjectFromString } from "@medusajs/utils" -import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" - -export const GET = async (req: MedusaRequest, res: MedusaResponse) => { - const remoteQuery = req.scope.resolve("remoteQuery") - - const variables = { id: req.params.id } - - // TODO: refund payment - v1.x - packages/medusa/src/api/routes/admin/orders/refund-payment.ts - - const queryObject = remoteQueryObjectFromString({ - entryPoint: "order", - variables, - fields: req.remoteQueryConfig.fields, - }) - - const [order] = await remoteQuery(queryObject) - res.status(200).json({ order }) -} diff --git a/packages/medusa/src/api-v2/admin/orders/fulfillments/route.ts b/packages/medusa/src/api-v2/admin/orders/fulfillments/route.ts index b49d0f0870..d291c62313 100644 --- a/packages/medusa/src/api-v2/admin/orders/fulfillments/route.ts +++ b/packages/medusa/src/api-v2/admin/orders/fulfillments/route.ts @@ -12,7 +12,7 @@ export const POST = async ( const variables = { id: req.params.id } - // TODO: Workflow fulfill items, create fulfillments and adjust inventory - v1.x - packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts + // TODO: Workflow fulfill items, create fulfillments - v1.x - packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts const queryObject = remoteQueryObjectFromString({ entryPoint: "order", diff --git a/packages/medusa/src/api-v2/admin/returns/[id]/cancel/route.ts b/packages/medusa/src/api-v2/admin/returns/[id]/cancel/route.ts index 27e7705b7e..f8d5e2b5a5 100644 --- a/packages/medusa/src/api-v2/admin/returns/[id]/cancel/route.ts +++ b/packages/medusa/src/api-v2/admin/returns/[id]/cancel/route.ts @@ -7,14 +7,14 @@ import { MedusaResponse, } from "../../../../../types/routing" -export const GET = async ( +export const POST = async ( req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const queryObject = remoteQueryObjectFromString({ - entryPoint: "order", + entryPoint: "return", variables: { filters: { ...req.filterableFields, diff --git a/packages/medusa/src/api-v2/admin/returns/[id]/receive/route.ts b/packages/medusa/src/api-v2/admin/returns/[id]/receive/route.ts index a3983a15aa..f8d5e2b5a5 100644 --- a/packages/medusa/src/api-v2/admin/returns/[id]/receive/route.ts +++ b/packages/medusa/src/api-v2/admin/returns/[id]/receive/route.ts @@ -7,18 +7,17 @@ import { MedusaResponse, } from "../../../../../types/routing" -export const GET = async ( +export const POST = async ( req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const queryObject = remoteQueryObjectFromString({ - entryPoint: "order", + entryPoint: "return", variables: { filters: { ...req.filterableFields, - is_draft_order: false, }, ...req.remoteQueryConfig.pagination, }, diff --git a/packages/medusa/src/api-v2/admin/returns/[id]/route.ts b/packages/medusa/src/api-v2/admin/returns/[id]/route.ts index 77f18351fe..4347c182c7 100644 --- a/packages/medusa/src/api-v2/admin/returns/[id]/route.ts +++ b/packages/medusa/src/api-v2/admin/returns/[id]/route.ts @@ -14,7 +14,7 @@ export const GET = async ( const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const queryObject = remoteQueryObjectFromString({ - entryPoint: "order", + entryPoint: "return", variables: { filters: { ...req.filterableFields, diff --git a/packages/medusa/src/api-v2/store/return/validators.ts b/packages/medusa/src/api-v2/store/return/validators.ts index bcef0d9e3a..c5cf9ef8d7 100644 --- a/packages/medusa/src/api-v2/store/return/validators.ts +++ b/packages/medusa/src/api-v2/store/return/validators.ts @@ -32,7 +32,6 @@ export const StorePostReturnsReqSchema = z.object({ return_shipping: ReturnShippingSchema, note: z.string().optional(), receive_now: z.boolean().optional(), - refund_amount: z.number().optional(), location_id: z.string().optional(), }) export type StorePostReturnsReqSchemaType = z.infer< diff --git a/packages/modules/order/integration-tests/__tests__/create-order.ts b/packages/modules/order/integration-tests/__tests__/create-order.ts index b5cf3ada3a..d771bc3f6b 100644 --- a/packages/modules/order/integration-tests/__tests__/create-order.ts +++ b/packages/modules/order/integration-tests/__tests__/create-order.ts @@ -196,9 +196,99 @@ moduleIntegrationTestRunner({ const createdOrder = await service.create(input) const serializedOrder = JSON.parse(JSON.stringify(createdOrder)) + expect(serializedOrder).toEqual(expectation) }) + it("should create an order, shipping method and items. Including taxes and adjustments associated with them and add new transactions", async function () { + const inpCopy = JSON.parse(JSON.stringify(input)) + inpCopy.transactions.push({ + amount: 10, + currency_code: "USD", + }) + const created = await service.create(inpCopy) + + const refund = await service.addTransactions([ + { + order_id: created.id, + amount: -20, + currency_code: "USD", + }, + ]) + + const serializedOrder = JSON.parse( + JSON.stringify( + await service.retrieve(created.id, { + select: ["id", "summary"], + }) + ) + ) + + expect(serializedOrder.summary).toEqual( + expect.objectContaining({ + paid_total: 68, + refunded_total: 20, + }) + ) + + await service.softDeleteTransactions(refund[0].id) + + const serializedOrder2 = JSON.parse( + JSON.stringify( + await service.retrieve(created.id, { + select: ["id", "summary"], + }) + ) + ) + + expect(serializedOrder2.summary).toEqual( + expect.objectContaining({ + paid_total: 68, + refunded_total: 0, + }) + ) + + await service.addTransactions([ + { + order_id: created.id, + amount: -50, + currency_code: "USD", + }, + ]) + + const serializedOrder3 = JSON.parse( + JSON.stringify( + await service.retrieve(created.id, { + select: ["id", "summary"], + }) + ) + ) + + expect(serializedOrder3.summary).toEqual( + expect.objectContaining({ + paid_total: 68, + refunded_total: 50, + }) + ) + + await service.restoreTransactions(refund[0].id) + + const serializedOrder4 = JSON.parse( + JSON.stringify( + await service.retrieve(created.id, { + select: ["id", "summary"], + }) + ) + ) + + expect(serializedOrder4.summary).toEqual( + expect.objectContaining({ + paid_total: 68, + refunded_total: 70, + }) + ) + }) + it("should transform requested fields and relations to match the db schema and return the order", async function () { const createdOrder = await service.create(input) const getOrder = await service.retrieve(createdOrder.id, { diff --git a/packages/modules/order/src/migrations/Migration20240219102530.ts b/packages/modules/order/src/migrations/Migration20240219102530.ts index f6a5a0c851..87d8508514 100644 --- a/packages/modules/order/src/migrations/Migration20240219102530.ts +++ b/packages/modules/order/src/migrations/Migration20240219102530.ts @@ -445,6 +445,7 @@ export class Migration20240219102530 extends Migration { CREATE TABLE IF NOT EXISTS "order_transaction" ( "id" TEXT NOT NULL, "order_id" TEXT NOT NULL, + "version" INTEGER NOT NULL DEFAULT 1, "amount" NUMERIC NOT NULL, "raw_amount" JSONB NOT NULL, "currency_code" TEXT NOT NULL, @@ -452,20 +453,25 @@ export class Migration20240219102530 extends Migration { "reference_id" TEXT NULL, "created_at" TIMESTAMPTZ NOT NULL DEFAULT Now(), "updated_at" TIMESTAMPTZ NOT NULL DEFAULT Now(), + "deleted_at" timestamptz NULL, CONSTRAINT "order_transaction_pkey" PRIMARY KEY ("id") ); - CREATE INDEX IF NOT EXISTS "IDX_order_transaction_order_id" ON "order_transaction" ( - order_id - ); + CREATE INDEX IF NOT EXISTS "IDX_order_transaction_order_id_version" ON "order_transaction" ( + order_id, + version + ) + WHERE deleted_at IS NOT NULL; CREATE INDEX IF NOT EXISTS "IDX_order_transaction_currency_code" ON "order_transaction" ( currency_code - ); + ) + WHERE deleted_at IS NOT NULL; CREATE INDEX IF NOT EXISTS "IDX_order_transaction_reference_id" ON "order_transaction" ( reference_id - ); + ) + WHERE deleted_at IS NOT NULL; CREATE TABLE IF NOT EXISTS "return_reason" ( diff --git a/packages/modules/order/src/models/order-summary.ts b/packages/modules/order/src/models/order-summary.ts index 6fb87fab26..4870bb9d72 100644 --- a/packages/modules/order/src/models/order-summary.ts +++ b/packages/modules/order/src/models/order-summary.ts @@ -36,6 +36,9 @@ type OrderSummaryTotals = { balance: BigNumber future_balance: BigNumber + + paid_total: BigNumber + refunded_total: BigNumber } const OrderIdVersionIndex = createPsqlIndexStatementHelper({ diff --git a/packages/modules/order/src/models/transaction.ts b/packages/modules/order/src/models/transaction.ts index a9ffa3d0af..aaa8e943cb 100644 --- a/packages/modules/order/src/models/transaction.ts +++ b/packages/modules/order/src/models/transaction.ts @@ -33,7 +33,20 @@ const CurrencyCodeIndex = createPsqlIndexStatementHelper({ columns: "currency_code", }) +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order_transaction", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", +}) + +const OrderIdVersionIndex = createPsqlIndexStatementHelper({ + tableName: "order_transaction", + columns: ["order_id", "version"], + where: "deleted_at IS NOT NULL", +}) + @Entity({ tableName: "order_transaction" }) +@OrderIdVersionIndex.MikroORMIndex() export default class Transaction { [OptionalProps]?: OptionalLineItemProps @@ -55,6 +68,12 @@ export default class Transaction { }) order: Order + @Property({ + columnType: "integer", + defaultRaw: "1", + }) + version: number = 1 + @MikroOrmBigNumberProperty() amount: BigNumber | number @@ -93,6 +112,10 @@ export default class Transaction { }) updated_at: Date + @Property({ columnType: "timestamptz", nullable: true }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "ordtrx") diff --git a/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts b/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts index c3df9d3948..afb6418fa3 100644 --- a/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts +++ b/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts @@ -104,6 +104,8 @@ describe("Order Exchange - Actions", function () { future_temporary_difference: 0, pending_difference: 312.5, difference_sum: 42.5, + paid_total: 0, + refunded_total: 0, }) const toJson = JSON.parse(JSON.stringify(changes.order.items)) diff --git a/packages/modules/order/src/services/order-module-service.ts b/packages/modules/order/src/services/order-module-service.ts index 522ab9f834..5bc9773384 100644 --- a/packages/modules/order/src/services/order-module-service.ts +++ b/packages/modules/order/src/services/order-module-service.ts @@ -1,4 +1,5 @@ import { + BigNumberInput, Context, CreateOrderChangeActionDTO, DAL, @@ -9,11 +10,13 @@ import { ModulesSdkTypes, OrderDTO, OrderTypes, + RestoreReturn, + SoftDeleteReturn, UpdateOrderItemWithSelectorDTO, UpdateOrderReturnReasonDTO, - UpdateOrderTransactionDTO, } from "@medusajs/types" import { + BigNumber, createRawPropertiesFromBigNumber, decorateCartTotals, deduplicate, @@ -22,12 +25,14 @@ import { InjectTransactionManager, isObject, isString, + MathBN, MedusaContext, MedusaError, ModulesSdkUtils, OrderChangeStatus, OrderStatus, promiseAll, + transformPropertiesToBigNumber, } from "@medusajs/utils" import { Address, @@ -129,13 +134,13 @@ export default class OrderModuleService< dto: OrderTypes.OrderShippingMethodAdjustmentDTO } ShippingMethodTaxLine: { dto: OrderTypes.OrderShippingMethodTaxLineDTO } - Transaction: { dto: OrderTypes.OrderTransactionDTO } OrderChange: { dto: OrderTypes.OrderChangeDTO } OrderChangeAction: { dto: OrderTypes.OrderChangeActionDTO } OrderItem: { dto: OrderTypes.OrderItemDTO } - OrderSummary: { dto: OrderTypes.OrderSummaryDTO } OrderShippingMethod: { dto: OrderShippingMethod } ReturnReason: { dto: OrderTypes.OrderReturnReasonDTO } + OrderSummary: { dto: OrderTypes.OrderSummaryDTO } + Transaction: { dto: OrderTypes.OrderTransactionDTO } } >(Order, generateMethodForModels, entityNameToLinkableKeysMap) implements IOrderModuleService @@ -2273,18 +2278,18 @@ export default class OrderModuleService< await this.confirmOrderChange(change[0].id, sharedContext) } - public async addTransactions( + async addTransactions( transactionData: OrderTypes.CreateOrderTransactionDTO, sharedContext?: Context ): Promise - public async addTransactions( + async addTransactions( transactionData: OrderTypes.CreateOrderTransactionDTO[], sharedContext?: Context ): Promise - @InjectTransactionManager("baseRepository_") - public async addTransactions( + @InjectManager("baseRepository_") + async addTransactions( transactionData: | OrderTypes.CreateOrderTransactionDTO | OrderTypes.CreateOrderTransactionDTO[], @@ -2292,12 +2297,33 @@ export default class OrderModuleService< ): Promise< OrderTypes.OrderTransactionDTO | OrderTypes.OrderTransactionDTO[] > { + const orders = await this.orderService_.list( + { + id: Array.isArray(transactionData) + ? transactionData.map((t) => t.order_id) + : transactionData.order_id, + }, + { + select: ["id", "version"], + }, + sharedContext + ) + const data = Array.isArray(transactionData) ? transactionData : [transactionData] + for (const order of orders) { + const trxs = data.filter((t) => t.order_id === order.id) + for (const trx of trxs) { + ;(trx as any).version = order.version + } + } + const created = await this.transactionService_.create(data, sharedContext) + await this.updateOrderPaidRefundableAmount_(created, false, sharedContext) + return await this.baseRepository_.serialize( !Array.isArray(transactionData) ? created[0] : created, { @@ -2306,120 +2332,158 @@ export default class OrderModuleService< ) } - updateTransactions( - data: OrderTypes.UpdateOrderTransactionWithSelectorDTO[] - ): Promise - - updateTransactions( - selector: Partial, - data: OrderTypes.UpdateOrderTransactionDTO, - sharedContext?: Context - ): Promise - - updateTransactions( - id: string, - data: Partial, - sharedContext?: Context - ): Promise - @InjectManager("baseRepository_") - async updateTransactions( - idOrDataOrSelector: - | string - | OrderTypes.UpdateOrderTransactionWithSelectorDTO[] - | Partial, - data?: - | OrderTypes.UpdateOrderTransactionDTO - | Partial, - @MedusaContext() sharedContext: Context = {} - ): Promise< - OrderTypes.OrderTransactionDTO[] | OrderTypes.OrderTransactionDTO - > { - let trxs: Transaction[] = [] - if (isString(idOrDataOrSelector)) { - const trx = await this.updateTransaction_( - idOrDataOrSelector, - data as Partial, - sharedContext - ) + // @ts-ignore + async deleteTransactions( + transactionIds: string | object | string[] | object[], + sharedContext?: Context + ): Promise { + const data = Array.isArray(transactionIds) + ? transactionIds + : [transactionIds] - return await this.baseRepository_.serialize( - trx, - { - populate: true, - } - ) - } - - const toUpdate = Array.isArray(idOrDataOrSelector) - ? idOrDataOrSelector - : [ - { - selector: idOrDataOrSelector, - data: data, - } as OrderTypes.UpdateOrderTransactionWithSelectorDTO, - ] - - trxs = await this.updateTransactionsWithSelector_(toUpdate, sharedContext) - - return await this.baseRepository_.serialize< - OrderTypes.OrderTransactionDTO[] - >(trxs, { - populate: true, - }) - } - - @InjectTransactionManager("baseRepository_") - protected async updateTransaction_( - trxId: string, - data: Partial, - @MedusaContext() sharedContext: Context = {} - ): Promise { - const [trx] = await this.transactionService_.update( - [{ id: trxId, ...data }], + const transactions = await super.listTransactions( + { + id: data, + }, + { + select: ["order_id", "version", "amount"], + }, sharedContext ) - return trx + await this.transactionService_.delete(data, sharedContext) + + await this.updateOrderPaidRefundableAmount_( + transactions, + true, + sharedContext + ) + } + + @InjectManager("baseRepository_") + // @ts-ignore + async softDeleteTransactions( + transactionIds: string | object | string[] | object[], + config?: SoftDeleteReturn, + sharedContext?: Context + ): Promise | void> { + const transactions = await super.listTransactions( + { + id: transactionIds, + }, + { + select: ["order_id", "amount"], + }, + sharedContext + ) + + const returned = await super.softDeleteTransactions( + transactionIds, + config, + sharedContext + ) + + await this.updateOrderPaidRefundableAmount_( + transactions, + true, + sharedContext + ) + + return returned + } + + @InjectManager("baseRepository_") + // @ts-ignore + async restoreTransactions( + transactionIds: string | object | string[] | object[], + config?: RestoreReturn, + sharedContext?: Context + ): Promise | void> { + const transactions = await super.listTransactions( + { + id: transactionIds, + }, + { + select: ["order_id", "amount"], + withDeleted: true, + }, + sharedContext + ) + + const returned = await super.restoreTransactions( + transactionIds as string[], + config, + sharedContext + ) + + await this.updateOrderPaidRefundableAmount_( + transactions, + false, + sharedContext + ) + + return returned } @InjectTransactionManager("baseRepository_") - protected async updateTransactionsWithSelector_( - updates: OrderTypes.UpdateOrderTransactionWithSelectorDTO[], - @MedusaContext() sharedContext: Context = {} - ): Promise { - let toUpdate: UpdateOrderTransactionDTO[] = [] + private async updateOrderPaidRefundableAmount_( + transactionData: { + order_id: string + amount: BigNumber | number | BigNumberInput + }[], + isRemoved: boolean, + sharedContext?: Context + ) { + const summaries: any = await super.listOrderSummaries( + { + order_id: transactionData.map((trx) => trx.order_id), + }, + {}, + sharedContext + ) - for (const { selector, data } of updates) { - const trxs = await super.listTransactions( - { ...selector }, - {}, - sharedContext + summaries.forEach((summary) => { + let trxs = transactionData.filter( + (trx) => trx.order_id === summary.order_id ) - trxs.forEach((trx) => { - toUpdate.push({ - ...data, - id: trx.id, - }) - }) - } + if (!trxs.length) { + return + } + transformPropertiesToBigNumber(trxs) - return await this.transactionService_.update(toUpdate, sharedContext) + const op = isRemoved ? MathBN.sub : MathBN.add + for (const trx of trxs) { + if (MathBN.gt(trx.amount, 0)) { + summary.totals.paid_total = new BigNumber( + op(summary.totals.paid_total, trx.amount) + ) + } else { + summary.totals.refunded_total = new BigNumber( + op(summary.totals.refunded_total, MathBN.abs(trx.amount)) + ) + } + } + }) + + createRawPropertiesFromBigNumber(summaries) + + await this.orderSummaryService_.update(summaries, sharedContext) } - public async createReturnReasons( + async createReturnReasons( transactionData: OrderTypes.CreateOrderReturnReasonDTO, sharedContext?: Context ): Promise - public async createReturnReasons( + async createReturnReasons( transactionData: OrderTypes.CreateOrderReturnReasonDTO[], sharedContext?: Context ): Promise @InjectTransactionManager("baseRepository_") - public async createReturnReasons( + async createReturnReasons( returnReasonData: | OrderTypes.CreateOrderReturnReasonDTO | OrderTypes.CreateOrderReturnReasonDTO[], @@ -2547,7 +2611,7 @@ export default class OrderModuleService< } @InjectTransactionManager("baseRepository_") - public async receiveReturn( + async receiveReturn( data: OrderTypes.ReceiveOrderReturnDTO, sharedContext?: Context ): Promise { @@ -2622,7 +2686,9 @@ export default class OrderModuleService< if (notAllowed.length) { throw new MedusaError( MedusaError.Types.INVALID_DATA, - `Pending Order cannot be archived: ${notAllowed.join(", ")}.` + `Orders ${notAllowed.join( + ", " + )} are completed, canceled, or in draft and cannot be archived` ) } @@ -2672,7 +2738,7 @@ export default class OrderModuleService< if (notAllowed.length) { throw new MedusaError( MedusaError.Types.INVALID_DATA, - `Canceled Order cannot be completed: ${notAllowed.join(", ")}.` + `Orders ${notAllowed.join(", ")} are canceled and cannot be completed` ) } diff --git a/packages/modules/order/src/types/utils/index.ts b/packages/modules/order/src/types/utils/index.ts index 8617cd7b50..1ec0999be9 100644 --- a/packages/modules/order/src/types/utils/index.ts +++ b/packages/modules/order/src/types/utils/index.ts @@ -44,6 +44,8 @@ export interface OrderSummaryCalculated { future_temporary_difference: BigNumberInput temporary_difference: BigNumberInput difference_sum: BigNumberInput + paid_total: BigNumberInput + refunded_total: BigNumberInput } export interface OrderTransaction { diff --git a/packages/modules/order/src/utils/calculate-order-change.ts b/packages/modules/order/src/utils/calculate-order-change.ts index 66087958a4..91d4980227 100644 --- a/packages/modules/order/src/utils/calculate-order-change.ts +++ b/packages/modules/order/src/utils/calculate-order-change.ts @@ -51,7 +51,18 @@ export class OrderChangeProcessing { this.transactions = JSON.parse(JSON.stringify(transactions ?? [])) this.actions = JSON.parse(JSON.stringify(actions ?? [])) - const transactionTotal = MathBN.add(...transactions.map((tr) => tr.amount)) + let paid = MathBN.convert(0) + let refunded = MathBN.convert(0) + let transactionTotal = MathBN.convert(0) + + for (const tr of transactions) { + if (MathBN.lt(tr.amount, 0)) { + refunded = MathBN.add(refunded, MathBN.abs(tr.amount)) + } else { + paid = MathBN.add(paid, tr.amount) + } + transactionTotal = MathBN.add(transactionTotal, tr.amount) + } transformPropertiesToBigNumber(this.order.metadata) @@ -65,6 +76,8 @@ export class OrderChangeProcessing { current_order_total: this.order.total ?? 0, original_order_total: this.order.total ?? 0, transaction_total: transactionTotal, + paid_total: paid, + refunded_total: refunded, } } @@ -358,6 +371,8 @@ export class OrderChangeProcessing { ), 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), } as unknown as OrderSummaryDTO return orderSummary