diff --git a/integration-tests/modules/__tests__/order/order.spec.ts b/integration-tests/modules/__tests__/order/order.spec.ts index 8576522a28..0e1f2d4999 100644 --- a/integration-tests/modules/__tests__/order/order.spec.ts +++ b/integration-tests/modules/__tests__/order/order.spec.ts @@ -257,7 +257,7 @@ medusaIntegrationTestRunner({ value: "1", precision: 20, }, - detail: { + detail: expect.objectContaining({ id: expect.any(String), order_id: expect.any(String), version: 1, @@ -301,7 +301,7 @@ medusaIntegrationTestRunner({ return_received_quantity: 0, return_dismissed_quantity: 0, written_off_quantity: 0, - }, + }), subtotal: 50, total: 50, original_total: 50, diff --git a/packages/core/types/src/order/common.ts b/packages/core/types/src/order/common.ts index bb455632de..a1155e8cb3 100644 --- a/packages/core/types/src/order/common.ts +++ b/packages/core/types/src/order/common.ts @@ -1109,6 +1109,12 @@ export interface OrderDTO { raw_original_shipping_tax_total: BigNumberRawValue } +type ReturnStatus = "requested" | "received" | "partially_received" | "canceled" + +export interface ReturnDTO extends Omit { + status: ReturnStatus +} + export type PaymentStatus = | "not_paid" | "awaiting" diff --git a/packages/core/types/src/order/mutations.ts b/packages/core/types/src/order/mutations.ts index 992ac61a0b..5546cbc212 100644 --- a/packages/core/types/src/order/mutations.ts +++ b/packages/core/types/src/order/mutations.ts @@ -249,6 +249,9 @@ export interface UpdateOrderShippingMethodAdjustmentDTO { export interface CreateOrderChangeDTO { order_id: string + return_id?: string + claim_id?: string + exchange_id?: string description?: string internal_note?: string requested_by?: string @@ -298,6 +301,10 @@ export interface ConfirmOrderChangeDTO { export interface CreateOrderChangeActionDTO { order_change_id?: string + order_id?: string + return_id?: string + claim_id?: string + exchange_id?: string version?: number reference?: string reference_id?: string @@ -368,39 +375,53 @@ export interface UpdateOrderItemWithSelectorDTO { /** ORDER bundled action flows */ +interface BaseOrderBundledItemActionsDTO { + id: string + quantity: BigNumberInput + internal_note?: string + metadata?: Record | null +} interface BaseOrderBundledActionsDTO { order_id: string + return_id?: string + claim_id?: string + exchange_id?: string + description?: string internal_note?: string reference?: string reference_id?: string created_by?: string - items: { - id: string - quantity: BigNumberInput - internal_note?: string - metadata?: Record | null - }[] metadata?: Record | null } export interface RegisterOrderFulfillmentDTO - extends BaseOrderBundledActionsDTO {} + extends BaseOrderBundledActionsDTO { + items: BaseOrderBundledItemActionsDTO[] +} -export interface CancelOrderFulfillmentDTO extends BaseOrderBundledActionsDTO {} +export interface CancelOrderFulfillmentDTO extends BaseOrderBundledActionsDTO { + items: BaseOrderBundledItemActionsDTO[] +} -export interface RegisterOrderShipmentDTO extends BaseOrderBundledActionsDTO {} +export interface RegisterOrderShipmentDTO extends BaseOrderBundledActionsDTO { + items: BaseOrderBundledItemActionsDTO[] +} export interface CreateOrderReturnDTO extends BaseOrderBundledActionsDTO { + items: BaseOrderBundledItemActionsDTO[] shipping_method: Omit | string } export interface CancelOrderReturnDTO { - order_id: string return_id: string } -export interface ReceiveOrderReturnDTO extends BaseOrderBundledActionsDTO {} +export interface ReceiveOrderReturnDTO + extends Omit { + items: BaseOrderBundledItemActionsDTO[] + return_id: string +} /** ORDER bundled action flows */ diff --git a/packages/core/types/src/order/service.ts b/packages/core/types/src/order/service.ts index d2c14abc1e..eeb99334a4 100644 --- a/packages/core/types/src/order/service.ts +++ b/packages/core/types/src/order/service.ts @@ -26,6 +26,7 @@ import { OrderShippingMethodDTO, OrderShippingMethodTaxLineDTO, OrderTransactionDTO, + ReturnDTO, } from "./common" import { CancelOrderChangeDTO, @@ -130,6 +131,24 @@ export interface IOrderModuleService extends IModuleService { sharedContext?: Context ): Promise<[OrderDTO[], number]> + retrieveReturn( + returnId: string, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listReturns( + filters?: FilterableOrderProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listAndCountReturns( + filters?: FilterableOrderProps, + config?: FindConfig, + sharedContext?: Context + ): Promise<[ReturnDTO[], number]> + /** * This method creates {return type}(s) * @@ -1513,7 +1532,7 @@ export interface IOrderModuleService extends IModuleService { createReturn( returnData: CreateOrderReturnDTO, sharedContext?: Context - ): Promise + ): Promise // TODO: ReturnDTO /* cancelReturn( @@ -1525,5 +1544,5 @@ export interface IOrderModuleService extends IModuleService { receiveReturn( returnData: ReceiveOrderReturnDTO, sharedContext?: Context - ): Promise + ): Promise // TODO: ReturnDTO } diff --git a/packages/core/utils/src/order/status.ts b/packages/core/utils/src/order/status.ts index b8886ae9a6..40d2affaee 100644 --- a/packages/core/utils/src/order/status.ts +++ b/packages/core/utils/src/order/status.ts @@ -33,27 +33,23 @@ export enum OrderStatus { /** * @enum * - * The order change's status. + * The return's status. */ -export enum OrderChangeStatus { +export enum ReturnStatus { /** - * The order change is confirmed. - */ - CONFIRMED = "confirmed", - /** - * The order change is declined. - */ - DECLINED = "declined", - /** - * The order change is requested. + * The return is requested. */ REQUESTED = "requested", /** - * The order change is pending. + * The return is received. */ - PENDING = "pending", + RECEIVED = "received", /** - * The order change is canceled. + * The return is partially received. + */ + PARTIALLY_RECEIVED = "partially_received", + /** + * The return is canceled. */ CANCELED = "canceled", } diff --git a/packages/modules/order/integration-tests/__tests__/order-return.ts b/packages/modules/order/integration-tests/__tests__/order-return.ts index 5057e09438..81c13fc8e6 100644 --- a/packages/modules/order/integration-tests/__tests__/order-return.ts +++ b/packages/modules/order/integration-tests/__tests__/order-return.ts @@ -233,12 +233,15 @@ moduleIntegrationTestRunner({ ) // Return - await service.createReturn({ + const orderReturn = await service.createReturn({ order_id: createdOrder.id, reference: Modules.FULFILLMENT, description: "Return all the items", internal_note: "user wants to return all items", - shipping_method: createdOrder.shipping_methods![0].id, + shipping_method: { + name: "Return method", + amount: 35, + }, items: createdOrder.items!.map((item) => { return { id: item.id, @@ -302,6 +305,141 @@ moduleIntegrationTestRunner({ ], }) ) + + // Receive Return + const allItems = createdOrder.items!.map((item) => { + return { + id: item.id, + quantity: item.quantity, + } + }) + const lastItem = allItems.pop()! + const receive = await service.receiveReturn({ + return_id: orderReturn.id, + internal_note: "received some items", + items: allItems, + }) + + const receiveComplete = await service.receiveReturn({ + return_id: orderReturn.id, + internal_note: "received remaining items", + items: [lastItem], + }) + + expect(receive).toEqual( + expect.objectContaining({ + id: orderReturn.id, + status: "partially_received", + received_at: null, + items: expect.arrayContaining([ + expect.objectContaining({ + id: allItems[0].id, + detail: expect.objectContaining({ + return_requested_quantity: 0, + return_received_quantity: 1, + }), + }), + expect.objectContaining({ + id: allItems[1].id, + detail: expect.objectContaining({ + return_requested_quantity: 0, + return_received_quantity: 2, + }), + }), + ]), + }) + ) + + expect(receiveComplete).toEqual( + expect.objectContaining({ + id: orderReturn.id, + status: "received", + received_at: expect.any(Date), + items: expect.arrayContaining([ + expect.objectContaining({ + id: allItems[0].id, + detail: expect.objectContaining({ + return_requested_quantity: 0, + return_received_quantity: 1, + }), + }), + expect.objectContaining({ + id: allItems[1].id, + detail: expect.objectContaining({ + return_requested_quantity: 0, + return_received_quantity: 2, + }), + }), + expect.objectContaining({ + id: lastItem.id, + detail: expect.objectContaining({ + return_requested_quantity: 0, + return_received_quantity: 1, + }), + }), + ]), + }) + ) + + getOrder = await service.retrieve(createdOrder.id, { + select: [ + "id", + "version", + "items.id", + "items.quantity", + "items.detail.id", + "items.detail.version", + "items.detail.quantity", + "items.detail.shipped_quantity", + "items.detail.fulfilled_quantity", + "items.detail.return_requested_quantity", + "items.detail.return_received_quantity", + ], + relations: ["items", "items.detail"], + }) + + serializedOrder = JSON.parse(JSON.stringify(getOrder)) + + expect(serializedOrder).toEqual( + expect.objectContaining({ + version: 6, + items: [ + expect.objectContaining({ + quantity: 1, + detail: expect.objectContaining({ + version: 6, + quantity: 1, + fulfilled_quantity: 1, + shipped_quantity: 1, + return_requested_quantity: 0, + return_received_quantity: 1, + }), + }), + expect.objectContaining({ + quantity: 2, + detail: expect.objectContaining({ + version: 6, + quantity: 2, + fulfilled_quantity: 2, + shipped_quantity: 2, + return_requested_quantity: 0, + return_received_quantity: 2, + }), + }), + expect.objectContaining({ + quantity: 1, + detail: expect.objectContaining({ + version: 6, + quantity: 1, + fulfilled_quantity: 1, + shipped_quantity: 1, + return_requested_quantity: 0, + return_received_quantity: 1, + }), + }), + ], + }) + ) }) }) }, diff --git a/packages/modules/order/src/migrations/Migration20240604100512.ts b/packages/modules/order/src/migrations/Migration20240604100512.ts new file mode 100644 index 0000000000..9edbbe12ef --- /dev/null +++ b/packages/modules/order/src/migrations/Migration20240604100512.ts @@ -0,0 +1,212 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20240604100512 extends Migration { + async up(): Promise { + const sql = ` + ALTER TABLE "order_change" + ADD COLUMN if NOT exists "change_type" TEXT NULL; + + ALTER TABLE "order_change" + ADD COLUMN if NOT exists "deleted_at" timestamptz NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_change_change_type" ON "order_change" ( + change_type + ); + + CREATE INDEX IF NOT EXISTS "IDX_order_change_deleted_at" ON "order_change" ( + deleted_at + ); + + + + ALTER TABLE "order_item" + ADD COLUMN if NOT exists "return_id" TEXT NULL; + + ALTER TABLE "order_item" + ADD COLUMN if NOT exists "claim_id" TEXT NULL; + + ALTER TABLE "order_item" + ADD COLUMN if NOT exists "exchange_id" TEXT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_item_return_id" ON "order_item" ( + return_id + ) + WHERE return_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_item_claim_id" ON "order_item" ( + claim_id + ) + WHERE claim_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_item_exchange_id" ON "order_item" ( + exchange_id + ) + WHERE exchange_id IS NOT NULL AND deleted_at IS NOT NULL; + + + + ALTER TABLE "order_transaction" + ADD COLUMN if NOT exists "return_id" TEXT NULL; + + ALTER TABLE "order_transaction" + ADD COLUMN if NOT exists "claim_id" TEXT NULL; + + ALTER TABLE "order_transaction" + ADD COLUMN if NOT exists "exchange_id" TEXT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_transaction_return_id" ON "order_transaction" ( + return_id + ) + WHERE return_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_transaction_claim_id" ON "order_transaction" ( + claim_id + ) + WHERE claim_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_transaction_exchange_id" ON "order_transaction" ( + exchange_id + ) + WHERE exchange_id IS NOT NULL AND deleted_at IS NOT NULL; + + + + ALTER TABLE "order_shipping" + ADD COLUMN if NOT exists "return_id" TEXT NULL; + + ALTER TABLE "order_shipping" + ADD COLUMN if NOT exists "claim_id" TEXT NULL; + + ALTER TABLE "order_shipping" + ADD COLUMN if NOT exists "exchange_id" TEXT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_shipping_return_id" ON "order_shipping" ( + return_id + ) + WHERE return_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_shipping_claim_id" ON "order_shipping" ( + claim_id + ) + WHERE claim_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_shipping_exchange_id" ON "order_shipping" ( + exchange_id + ) + WHERE exchange_id IS NOT NULL AND deleted_at IS NOT NULL; + + + + + ALTER TABLE "order_change" + ADD COLUMN if NOT exists "return_id" TEXT NULL; + + ALTER TABLE "order_change" + ADD COLUMN if NOT exists "claim_id" TEXT NULL; + + ALTER TABLE "order_change" + ADD COLUMN if NOT exists "exchange_id" TEXT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_change_return_id" ON "order_change" ( + return_id + ) + WHERE return_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_change_claim_id" ON "order_change" ( + claim_id + ) + WHERE claim_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_change_exchange_id" ON "order_change" ( + exchange_id + ) + WHERE exchange_id IS NOT NULL AND deleted_at IS NOT NULL; + + + + ALTER TABLE "order_change_action" + ADD COLUMN if NOT exists "deleted_at" timestamptz NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_change_action_deleted_at" ON "order_change_action" ( + deleted_at + ); + + ALTER TABLE "order_change_action" + ADD COLUMN if NOT exists "return_id" TEXT NULL; + + ALTER TABLE "order_change_action" + ADD COLUMN if NOT exists "claim_id" TEXT NULL; + + ALTER TABLE "order_change_action" + ADD COLUMN if NOT exists "exchange_id" TEXT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_change_action_return_id" ON "order_change_action" ( + return_id + ) + WHERE return_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_change_action_claim_id" ON "order_change_action" ( + claim_id + ) + WHERE claim_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_change_action_exchange_id" ON "order_change_action" ( + exchange_id + ) + WHERE exchange_id IS NOT NULL AND deleted_at IS NOT NULL; + + + + CREATE TABLE IF NOT EXISTS "return" ( + "id" TEXT NOT NULL, + "order_id" TEXT NOT NULL, + "claim_id" TEXT NULL, + "exchange_id" TEXT NULL, + "order_version" INTEGER NOT NULL, + "display_id" SERIAL, + "status" text NOT NULL, + "no_notification" boolean NULL, + "refund_amount" NUMERIC NULL, + "raw_refund_amount" JSONB NULL, + "metadata" jsonb NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + "deleted_at" timestamptz NULL, + "received_at" timestamptz NULL, + "canceled_at" timestamptz NULL, + CONSTRAINT "return_pkey" PRIMARY KEY ("id") + ); + + CREATE TYPE return_status_enum AS ENUM ( + 'requested', + 'received', + 'partially_received', + 'canceled' + ); + ALTER TABLE "return" ALTER COLUMN status DROP DEFAULT; + ALTER TABLE "return" ALTER COLUMN status TYPE return_status_enum USING (status::text::return_status_enum); + ALTER TABLE "return" ALTER COLUMN status SET DEFAULT 'requested'; + + CREATE INDEX IF NOT EXISTS "IDX_return_order_id" ON "return" ( + order_id + ) + WHERE deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_return_claim_id" ON "return" ( + claim_id + ) + WHERE claim_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_return_exchange_id" ON "return" ( + exchange_id + ) + WHERE exchange_id IS NOT NULL AND deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_return_display_id" ON "return" ( + display_id + ) + WHERE deleted_at IS NOT NULL; + ` + this.addSql(sql) + } +} diff --git a/packages/modules/order/src/models/index.ts b/packages/modules/order/src/models/index.ts index e0d0688638..9c282f3838 100644 --- a/packages/modules/order/src/models/index.ts +++ b/packages/modules/order/src/models/index.ts @@ -8,6 +8,7 @@ export { default as OrderChangeAction } from "./order-change-action" export { default as OrderItem } from "./order-item" export { default as OrderShippingMethod } from "./order-shipping-method" export { default as OrderSummary } from "./order-summary" +export { default as Return } from "./return" export { default as ReturnReason } from "./return-reason" export { default as ShippingMethod } from "./shipping-method" export { default as ShippingMethodAdjustment } from "./shipping-method-adjustment" diff --git a/packages/modules/order/src/models/order-change-action.ts b/packages/modules/order/src/models/order-change-action.ts index 64b4584b55..498552d7cd 100644 --- a/packages/modules/order/src/models/order-change-action.ts +++ b/packages/modules/order/src/models/order-change-action.ts @@ -14,6 +14,7 @@ import { PrimaryKey, Property, } from "@mikro-orm/core" +import { Return } from "@models" import Order from "./order" import OrderChange from "./order-change" @@ -22,16 +23,31 @@ type OptionalLineItemProps = DAL.EntityDateColumns const OrderChangeIdIndex = createPsqlIndexStatementHelper({ tableName: "order_change_action", columns: "order_change_id", + where: "deleted_at IS NOT NULL", }) const OrderIdIndex = createPsqlIndexStatementHelper({ tableName: "order_change_action", columns: "order_id", + where: "deleted_at IS NOT NULL", +}) + +const ReturnIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_change_action", + columns: "return_id", + where: "return_id IS NOT NULL AND deleted_at IS NOT NULL", +}) + +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order_change_action", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", }) const ActionOrderingIndex = createPsqlIndexStatementHelper({ tableName: "order_change_action", columns: "ordering", + where: "deleted_at IS NOT NULL", }) @Entity({ tableName: "order_change_action" }) @@ -62,6 +78,21 @@ export default class OrderChangeAction { }) order: Order | null = null + @ManyToOne({ + entity: () => Return, + mapToPk: true, + fieldName: "return_id", + columnType: "text", + nullable: true, + }) + @ReturnIdIndex.MikroORMIndex() + return_id: string | null = null + + @ManyToOne(() => Return, { + persist: false, + }) + return: Return + @Property({ columnType: "integer", nullable: true }) version: number | null = null @@ -133,6 +164,10 @@ export default class OrderChangeAction { }) updated_at: Date + @Property({ columnType: "timestamptz", nullable: true }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "ordchact") diff --git a/packages/modules/order/src/models/order-change.ts b/packages/modules/order/src/models/order-change.ts index 0aa67ca1be..72f20e63f8 100644 --- a/packages/modules/order/src/models/order-change.ts +++ b/packages/modules/order/src/models/order-change.ts @@ -1,6 +1,5 @@ import { DAL } from "@medusajs/types" import { - OrderChangeStatus, createPsqlIndexStatementHelper, generateEntityId, } from "@medusajs/utils" @@ -17,6 +16,8 @@ import { PrimaryKey, Property, } from "@mikro-orm/core" +import { Return } from "@models" +import { OrderChangeStatus, OrderChangeType } from "@types" import Order from "./order" import OrderChangeAction from "./order-change-action" @@ -25,16 +26,37 @@ type OptionalLineItemProps = DAL.EntityDateColumns const OrderIdIndex = createPsqlIndexStatementHelper({ tableName: "order_change", columns: "order_id", + where: "deleted_at IS NOT NULL", +}) + +const ReturnIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_change", + columns: "return_id", + where: "return_id IS NOT NULL AND deleted_at IS NOT NULL", }) const OrderChangeStatusIndex = createPsqlIndexStatementHelper({ tableName: "order_change", columns: "status", + where: "deleted_at IS NOT NULL", +}) + +const OrderChangeTypeIndex = createPsqlIndexStatementHelper({ + tableName: "order_change", + columns: "change_type", + where: "deleted_at IS NOT NULL", +}) + +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order_change", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", }) const VersionIndex = createPsqlIndexStatementHelper({ tableName: "order_change", columns: ["order_id", "version"], + where: "deleted_at IS NOT NULL", }) @Entity({ tableName: "order_change" }) @@ -60,10 +82,29 @@ export default class OrderChange { }) order: Order + @ManyToOne({ + entity: () => Return, + mapToPk: true, + fieldName: "return_id", + columnType: "text", + nullable: true, + }) + @ReturnIdIndex.MikroORMIndex() + return_id: string | null = null + + @ManyToOne(() => Return, { + persist: false, + }) + return: Return + @Property({ columnType: "integer" }) @VersionIndex.MikroORMIndex() version: number + @Enum({ items: () => OrderChangeType, nullable: true }) + @OrderChangeTypeIndex.MikroORMIndex() + change_type: OrderChangeType | null = null + @OneToMany(() => OrderChangeAction, (action) => action.order_change, { cascade: [Cascade.PERSIST, "sotf-remove" as Cascade], }) @@ -142,6 +183,10 @@ export default class OrderChange { }) updated_at: Date + @Property({ columnType: "timestamptz", nullable: true }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "ordch") diff --git a/packages/modules/order/src/models/order-item.ts b/packages/modules/order/src/models/order-item.ts index edb7eefcd2..27b5a12d63 100644 --- a/packages/modules/order/src/models/order-item.ts +++ b/packages/modules/order/src/models/order-item.ts @@ -14,6 +14,7 @@ import { PrimaryKey, Property, } from "@mikro-orm/core" +import { Return } from "@models" import LineItem from "./line-item" import Order from "./order" @@ -25,6 +26,12 @@ const OrderIdIndex = createPsqlIndexStatementHelper({ where: "deleted_at IS NOT NULL", }) +const ReturnIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_item", + columns: "return_id", + where: "return_id IS NOT NULL AND deleted_at IS NOT NULL", +}) + const OrderVersionIndex = createPsqlIndexStatementHelper({ tableName: "order_item", columns: ["version"], @@ -59,15 +66,30 @@ export default class OrderItem { @OrderIdIndex.MikroORMIndex() order_id: string - @Property({ columnType: "integer" }) - @OrderVersionIndex.MikroORMIndex() - version: number - @ManyToOne(() => Order, { persist: false, }) order: Order + @ManyToOne({ + entity: () => Return, + mapToPk: true, + fieldName: "return_id", + columnType: "text", + nullable: true, + }) + @ReturnIdIndex.MikroORMIndex() + return_id: string | null = null + + @ManyToOne(() => Return, { + persist: false, + }) + return: Return + + @Property({ columnType: "integer" }) + @OrderVersionIndex.MikroORMIndex() + version: number + @ManyToOne({ entity: () => LineItem, fieldName: "item_id", diff --git a/packages/modules/order/src/models/order-shipping-method.ts b/packages/modules/order/src/models/order-shipping-method.ts index 95a0c6c8cd..123de91a0f 100644 --- a/packages/modules/order/src/models/order-shipping-method.ts +++ b/packages/modules/order/src/models/order-shipping-method.ts @@ -12,6 +12,7 @@ import { PrimaryKey, Property, } from "@mikro-orm/core" +import { Return } from "@models" import Order from "./order" import ShippingMethod from "./shipping-method" @@ -23,6 +24,12 @@ const OrderIdIndex = createPsqlIndexStatementHelper({ where: "deleted_at IS NOT NULL", }) +const ReturnIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_shipping", + columns: "return_id", + where: "return_id IS NOT NULL AND deleted_at IS NOT NULL", +}) + const OrderVersionIndex = createPsqlIndexStatementHelper({ tableName: "order_shipping", columns: ["version"], @@ -57,15 +64,30 @@ export default class OrderShippingMethod { @OrderIdIndex.MikroORMIndex() order_id: string - @Property({ columnType: "integer" }) - @OrderVersionIndex.MikroORMIndex() - version: number - @ManyToOne(() => Order, { persist: false, }) order: Order + @ManyToOne({ + entity: () => Return, + mapToPk: true, + fieldName: "return_id", + columnType: "text", + nullable: true, + }) + @ReturnIdIndex.MikroORMIndex() + return_id: string | null = null + + @ManyToOne(() => Return, { + persist: false, + }) + return: Return + + @Property({ columnType: "integer" }) + @OrderVersionIndex.MikroORMIndex() + version: number + @ManyToOne({ entity: () => ShippingMethod, fieldName: "shipping_method_id", diff --git a/packages/modules/order/src/models/return.ts b/packages/modules/order/src/models/return.ts new file mode 100644 index 0000000000..dbe61e4763 --- /dev/null +++ b/packages/modules/order/src/models/return.ts @@ -0,0 +1,184 @@ +import { BigNumberRawValue, DAL } from "@medusajs/types" +import { + BigNumber, + MikroOrmBigNumberProperty, + ReturnStatus, + createPsqlIndexStatementHelper, + generateEntityId, +} from "@medusajs/utils" +import { + BeforeCreate, + Cascade, + Collection, + Entity, + Enum, + ManyToOne, + OnInit, + OneToMany, + OptionalProps, + PrimaryKey, + Property, +} from "@mikro-orm/core" +import { OrderItem, OrderShippingMethod } from "@models" +import Order from "./order" + +type OptionalReturnProps = DAL.EntityDateColumns + +const DisplayIdIndex = createPsqlIndexStatementHelper({ + tableName: "return", + columns: "display_id", + where: "deleted_at IS NOT NULL", +}) + +const ReturnDeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "return", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", +}) + +const OrderIdIndex = createPsqlIndexStatementHelper({ + tableName: "return", + columns: ["order_id"], + where: "deleted_at IS NOT NULL", +}) + +const ClaimIdIndex = createPsqlIndexStatementHelper({ + tableName: "return", + columns: ["claim_id"], + where: "claim_id IS NOT NULL AND deleted_at IS NOT NULL", +}) + +const ExchageIdIndex = createPsqlIndexStatementHelper({ + tableName: "return", + columns: ["exchange_id"], + where: "exchange_id IS NOT NULL AND deleted_at IS NOT NULL", +}) + +@Entity({ tableName: "return" }) +export default class Return { + [OptionalProps]?: OptionalReturnProps + + @PrimaryKey({ columnType: "text" }) + id: string + + @ManyToOne({ + entity: () => Order, + mapToPk: true, + fieldName: "order_id", + columnType: "text", + }) + @OrderIdIndex.MikroORMIndex() + order_id: string + + @ManyToOne(() => Order, { + persist: false, + }) + order: Order + + /* + @ManyToOne({ + entity: () => Claim, + mapToPk: true, + fieldName: "claim_id", + columnType: "text", + nullable: true, + }) + @ClaimIdIndex.MikroORMIndex() + claim_id: string | null + + @ManyToOne(() => Claim, { + persist: false, + }) + claim: Claim | null + + @ManyToOne({ + entity: () => Exchange, + mapToPk: true, + fieldName: "exchange_id", + columnType: "text", + nullable: true, + }) + @ExchangeIdIndex.MikroORMIndex() + exchange_id: string | null + + @ManyToOne(() => Exchange, { + persist: false, + }) + exchange: Exchange | null + */ + + @Property({ + columnType: "integer", + }) + order_version: number + + @Property({ autoincrement: true, primary: false }) + @DisplayIdIndex.MikroORMIndex() + display_id: number + + @Enum({ items: () => ReturnStatus, default: ReturnStatus.REQUESTED }) + status: ReturnStatus = ReturnStatus.REQUESTED + + @Property({ columnType: "boolean", nullable: true }) + no_notification: boolean | null = null + + @MikroOrmBigNumberProperty({ + nullable: true, + }) + refund_amount: BigNumber | number + + @Property({ columnType: "jsonb", nullable: true }) + raw_refund_amount: BigNumberRawValue + + @OneToMany(() => OrderItem, (itemDetail) => itemDetail.return, { + cascade: [Cascade.PERSIST], + }) + items = new Collection(this) + + @OneToMany( + () => OrderShippingMethod, + (shippingMethod) => shippingMethod.return, + { + cascade: [Cascade.PERSIST], + } + ) + shipping_methods = new Collection(this) + + @Property({ columnType: "jsonb", nullable: true }) + metadata: Record | null = null + + @Property({ + onCreate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + created_at: Date + + @Property({ + onCreate: () => new Date(), + onUpdate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + updated_at: Date + + @Property({ columnType: "timestamptz", nullable: true }) + @ReturnDeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + + @Property({ columnType: "timestamptz", nullable: true }) + received_at: Date | null = null + + @Property({ columnType: "timestamptz", nullable: true }) + canceled_at: Date | null = null + + @BeforeCreate() + onCreate() { + this.id = generateEntityId(this.id, "return") + } + + @OnInit() + onInit() { + this.id = generateEntityId(this.id, "return") + } +} diff --git a/packages/modules/order/src/models/transaction.ts b/packages/modules/order/src/models/transaction.ts index aaa8e943cb..e79079dbf2 100644 --- a/packages/modules/order/src/models/transaction.ts +++ b/packages/modules/order/src/models/transaction.ts @@ -14,6 +14,7 @@ import { PrimaryKey, Property, } from "@mikro-orm/core" +import { Return } from "@models" import Order from "./order" type OptionalLineItemProps = DAL.EntityDateColumns @@ -21,16 +22,25 @@ type OptionalLineItemProps = DAL.EntityDateColumns const ReferenceIdIndex = createPsqlIndexStatementHelper({ tableName: "order_transaction", columns: "reference_id", + where: "deleted_at IS NOT NULL", }) const OrderIdIndex = createPsqlIndexStatementHelper({ tableName: "order_transaction", columns: "order_id", + where: "deleted_at IS NOT NULL", +}) + +const ReturnIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_transaction", + columns: "return_id", + where: "return_id IS NOT NULL AND deleted_at IS NOT NULL", }) const CurrencyCodeIndex = createPsqlIndexStatementHelper({ tableName: "order_transaction", columns: "currency_code", + where: "deleted_at IS NOT NULL", }) const DeletedAtIndex = createPsqlIndexStatementHelper({ @@ -68,6 +78,21 @@ export default class Transaction { }) order: Order + @ManyToOne({ + entity: () => Return, + mapToPk: true, + fieldName: "return_id", + columnType: "text", + nullable: true, + }) + @ReturnIdIndex.MikroORMIndex() + return_id: string | null = null + + @ManyToOne(() => Return, { + persist: false, + }) + return: Return + @Property({ columnType: "integer", defaultRaw: "1", diff --git a/packages/modules/order/src/repositories/index.ts b/packages/modules/order/src/repositories/index.ts index d0f10fb678..b95f89c6e1 100644 --- a/packages/modules/order/src/repositories/index.ts +++ b/packages/modules/order/src/repositories/index.ts @@ -1,2 +1,3 @@ export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils" -export * from "./order" +export { OrderRepository } from "./order" +export { ReturnRepository } from "./return" diff --git a/packages/modules/order/src/repositories/order.ts b/packages/modules/order/src/repositories/order.ts index 0c9a51e3a6..d10a500c30 100644 --- a/packages/modules/order/src/repositories/order.ts +++ b/packages/modules/order/src/repositories/order.ts @@ -1,121 +1,9 @@ -import { Context, DAL } from "@medusajs/types" import { DALUtils } from "@medusajs/utils" -import { LoadStrategy } from "@mikro-orm/core" -import { EntityManager } from "@mikro-orm/postgresql" import { Order } from "@models" -import { mapRepositoryToOrderModel } from "../utils/transform-order" +import { setFindMethods } from "../utils/base-repository-find" export class OrderRepository extends DALUtils.mikroOrmBaseRepositoryFactory( Order -) { - async find( - options?: DAL.FindOptions, - context?: Context - ): Promise { - const manager = this.getActiveManager(context) - const knex = manager.getKnex() +) {} - const findOptions_ = { ...options } as any - findOptions_.options ??= {} - findOptions_.where ??= {} - - if (!("strategy" in findOptions_.options)) { - if (findOptions_.options.limit != null || findOptions_.options.offset) { - Object.assign(findOptions_.options, { - strategy: LoadStrategy.SELECT_IN, - }) - } else { - Object.assign(findOptions_.options, { - strategy: LoadStrategy.JOINED, - }) - } - } - - const config = mapRepositoryToOrderModel(findOptions_) - - let defaultVersion = knex.raw(`"o0"."version"`) - const strategy = config.options.strategy ?? LoadStrategy.JOINED - if (strategy === LoadStrategy.SELECT_IN) { - const sql = manager - .qb(Order, "_sub0") - .select("version") - .where({ id: knex.raw(`"o0"."order_id"`) }) - .getKnexQuery() - .toString() - - defaultVersion = knex.raw(`(${sql})`) - } - - const version = config.where.version ?? defaultVersion - delete config.where?.version - - config.options.populateWhere ??= {} - - config.options.populateWhere.items ??= {} - config.options.populateWhere.items.version = version - - config.options.populateWhere.summary ??= {} - config.options.populateWhere.summary.version = version - - config.options.populateWhere.shipping_methods ??= {} - config.options.populateWhere.shipping_methods.version = version - - if (!config.options.orderBy) { - config.options.orderBy = { id: "ASC" } - } - - return await manager.find(Order, config.where, config.options) - } - - async findAndCount( - findOptions: DAL.FindOptions = { where: {} }, - context: Context = {} - ): Promise<[Order[], number]> { - const manager = this.getActiveManager(context) - const knex = manager.getKnex() - - const findOptions_ = { ...findOptions } as any - findOptions_.options ??= {} - findOptions_.where ??= {} - - if (!("strategy" in findOptions_.options)) { - Object.assign(findOptions_.options, { - strategy: LoadStrategy.SELECT_IN, - }) - } - - const config = mapRepositoryToOrderModel(findOptions_) - - let defaultVersion = knex.raw(`"o0"."version"`) - const strategy = config.options.strategy ?? LoadStrategy.JOINED - if (strategy === LoadStrategy.SELECT_IN) { - const sql = manager - .qb(Order, "_sub0") - .select("version") - .where({ id: knex.raw(`"o0"."order_id"`) }) - .getKnexQuery() - .toString() - - defaultVersion = knex.raw(`(${sql})`) - } - - const version = config.where.version ?? defaultVersion - delete config.where.version - - config.options.populateWhere ??= {} - config.options.populateWhere.items ??= {} - config.options.populateWhere.items.version = version - - config.options.populateWhere.summary ??= {} - config.options.populateWhere.summary.version = version - - config.options.populateWhere.shipping_methods ??= {} - config.options.populateWhere.shipping_methods.version = version - - if (!config.options.orderBy) { - config.options.orderBy = { id: "ASC" } - } - - return await manager.findAndCount(Order, config.where, config.options) - } -} +setFindMethods(OrderRepository, Order) diff --git a/packages/modules/order/src/repositories/return.ts b/packages/modules/order/src/repositories/return.ts new file mode 100644 index 0000000000..30e8554b74 --- /dev/null +++ b/packages/modules/order/src/repositories/return.ts @@ -0,0 +1,9 @@ +import { DALUtils } from "@medusajs/utils" +import { Return } from "@models" +import { setFindMethods } from "../utils/base-repository-find" + +export class ReturnRepository extends DALUtils.mikroOrmBaseRepositoryFactory( + Return +) {} + +setFindMethods(ReturnRepository, Return) diff --git a/packages/modules/order/src/services/actions/cancel-fulfillment.ts b/packages/modules/order/src/services/actions/cancel-fulfillment.ts new file mode 100644 index 0000000000..29c3e27a40 --- /dev/null +++ b/packages/modules/order/src/services/actions/cancel-fulfillment.ts @@ -0,0 +1,38 @@ +import { Context, OrderTypes } from "@medusajs/types" +import { ChangeActionType } from "../../utils" + +export async function cancelFulfillment( + this: any, + data: OrderTypes.CancelOrderFulfillmentDTO, + sharedContext?: Context +): Promise { + const items = data.items.map((item) => { + return { + action: ChangeActionType.CANCEL_ITEM_FULFILLMENT, + internal_note: item.internal_note, + reference: data.reference, + reference_id: data.reference_id, + claim_id: data.claim_id, + exchange_id: data.exchange_id, + details: { + reference_id: item.id, + quantity: item.quantity, + metadata: item.metadata, + }, + } + }) + + const change = await this.createOrderChange_( + { + order_id: data.order_id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions: items, + }, + sharedContext + ) + + await this.confirmOrderChange(change[0].id, sharedContext) +} diff --git a/packages/modules/order/src/services/actions/create-return.ts b/packages/modules/order/src/services/actions/create-return.ts new file mode 100644 index 0000000000..cf5a77b975 --- /dev/null +++ b/packages/modules/order/src/services/actions/create-return.ts @@ -0,0 +1,113 @@ +import { + Context, + CreateOrderChangeActionDTO, + OrderTypes, +} from "@medusajs/types" +import { + ReturnStatus, + getShippingMethodsTotals, + isString, +} from "@medusajs/utils" +import { OrderChangeType } from "@types" +import { ChangeActionType } from "../../utils" + +export async function createReturn( + this: any, + data: OrderTypes.CreateOrderReturnDTO, + sharedContext?: Context +) { + const order = await this.orderService_.retrieve( + data.order_id, + { + relations: ["items"], + }, + sharedContext + ) + + const [returnRef] = await this.createReturns( + [ + { + order_id: data.order_id, + order_version: order.version, + status: ReturnStatus.REQUESTED, + // refund_amount: data.refund_amount ?? null, + }, + ], + sharedContext + ) + + let shippingMethodId + + if (!isString(data.shipping_method)) { + const methods = await this.createShippingMethods( + [ + { + order_id: data.order_id, + ...data.shipping_method, + }, + ], + sharedContext + ) + shippingMethodId = methods[0].id + } else { + shippingMethodId = data.shipping_method + } + + const method = await this.retrieveShippingMethod( + shippingMethodId, + { + relations: ["tax_lines", "adjustments"], + }, + sharedContext + ) + + const calculatedAmount = getShippingMethodsTotals([method as any], {})[ + method.id + ] + + const actions: CreateOrderChangeActionDTO[] = data.items.map((item) => { + return { + action: ChangeActionType.RETURN_ITEM, + return_id: returnRef.id, + internal_note: item.internal_note, + reference: "return", + reference_id: returnRef.id, + details: { + reference_id: item.id, + return_id: returnRef.id, + quantity: item.quantity, + metadata: item.metadata, + }, + } + }) + + if (shippingMethodId) { + actions.push({ + action: ChangeActionType.SHIPPING_ADD, + reference: "order_shipping_method", + reference_id: shippingMethodId, + return_id: returnRef.id, + amount: calculatedAmount.total, + }) + } + + const change = await this.createOrderChange_( + { + order_id: data.order_id, + return_id: returnRef.id, + change_type: OrderChangeType.RETURN, + reference: "return", + reference_id: returnRef.id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions, + }, + sharedContext + ) + + await this.confirmOrderChange(change[0].id, sharedContext) + + return returnRef +} diff --git a/packages/modules/order/src/services/actions/index.ts b/packages/modules/order/src/services/actions/index.ts new file mode 100644 index 0000000000..d8752998a3 --- /dev/null +++ b/packages/modules/order/src/services/actions/index.ts @@ -0,0 +1,5 @@ +export * from "./cancel-fulfillment" +export * from "./create-return" +export * from "./receive-return" +export * from "./register-fulfillment" +export * from "./register-shipment" diff --git a/packages/modules/order/src/services/actions/receive-return.ts b/packages/modules/order/src/services/actions/receive-return.ts new file mode 100644 index 0000000000..304f11454c --- /dev/null +++ b/packages/modules/order/src/services/actions/receive-return.ts @@ -0,0 +1,73 @@ +import { Context, OrderTypes } from "@medusajs/types" +import { MathBN, ReturnStatus } from "@medusajs/utils" +import { ChangeActionType } from "../../utils" + +export async function receiveReturn( + this: any, + data: OrderTypes.ReceiveOrderReturnDTO, + sharedContext?: Context +) { + const returnEntry = await this.retrieveReturn( + data.return_id, + { + select: ["id", "order_id"], + relations: ["items"], + }, + sharedContext + ) + + const items = data.items.map((item) => { + return { + action: ChangeActionType.RECEIVE_RETURN_ITEM, + internal_note: item.internal_note, + reference: data.reference, + reference_id: data.reference_id, + details: { + reference_id: item.id, + quantity: item.quantity, + metadata: item.metadata, + }, + } + }) + + const change = await this.createOrderChange_( + { + order_id: returnEntry.order_id, + reference: "return", + reference_id: returnEntry.id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions: items, + }, + sharedContext + ) + + await this.confirmOrderChange(change[0].id, sharedContext) + + const hasReceivedAllItems = returnEntry.items.every((item) => { + const retItem = items.find((dtItem) => { + return item.detail.item_id === dtItem.details.reference_id + }) + const quantity = retItem ? retItem.details.quantity : 0 + return MathBN.eq( + MathBN.sub(item.detail.return_requested_quantity, quantity), + 0 + ) + }) + + const retData = hasReceivedAllItems + ? { status: ReturnStatus.RECEIVED, received_at: new Date() } + : { status: ReturnStatus.PARTIALLY_RECEIVED } + + const returnRef = await this.updateReturns( + { + selector: { id: returnEntry.id }, + data: retData, + }, + sharedContext + ) + + return returnRef +} diff --git a/packages/modules/order/src/services/actions/register-fulfillment.ts b/packages/modules/order/src/services/actions/register-fulfillment.ts new file mode 100644 index 0000000000..a3411d1e4f --- /dev/null +++ b/packages/modules/order/src/services/actions/register-fulfillment.ts @@ -0,0 +1,36 @@ +import { Context, OrderTypes } from "@medusajs/types" +import { ChangeActionType } from "../../utils" + +export async function registerFulfillment( + this: any, + data: OrderTypes.RegisterOrderFulfillmentDTO, + sharedContext?: Context +): Promise { + const items = data.items.map((item) => { + return { + action: ChangeActionType.FULFILL_ITEM, + internal_note: item.internal_note, + reference: data.reference, + reference_id: data.reference_id, + details: { + reference_id: item.id, + quantity: item.quantity, + metadata: item.metadata, + }, + } + }) + + const change = await this.createOrderChange_( + { + order_id: data.order_id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions: items, + }, + sharedContext + ) + + await this.confirmOrderChange(change[0].id, sharedContext) +} diff --git a/packages/modules/order/src/services/actions/register-shipment.ts b/packages/modules/order/src/services/actions/register-shipment.ts new file mode 100644 index 0000000000..defcb12434 --- /dev/null +++ b/packages/modules/order/src/services/actions/register-shipment.ts @@ -0,0 +1,50 @@ +import { + Context, + CreateOrderChangeActionDTO, + OrderTypes, +} from "@medusajs/types" +import { ChangeActionType } from "../../utils" + +export async function registerShipment( + this: any, + data: OrderTypes.RegisterOrderShipmentDTO, + sharedContext?: Context +): Promise { + let shippingMethodId + + const actions: CreateOrderChangeActionDTO[] = data.items.map((item) => { + return { + action: ChangeActionType.SHIP_ITEM, + internal_note: item.internal_note, + reference: data.reference, + reference_id: data.reference_id, + details: { + reference_id: item.id, + quantity: item.quantity, + metadata: item.metadata, + }, + } + }) + + if (shippingMethodId) { + actions.push({ + action: ChangeActionType.SHIPPING_ADD, + reference: data.reference, + reference_id: shippingMethodId, + }) + } + + const change = await this.createOrderChange_( + { + order_id: data.order_id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions, + }, + sharedContext + ) + + await this.confirmOrderChange(change[0].id, sharedContext) +} diff --git a/packages/modules/order/src/services/order-change-service.ts b/packages/modules/order/src/services/order-change-service.ts index 40d34737ba..5a08689c09 100644 --- a/packages/modules/order/src/services/order-change-service.ts +++ b/packages/modules/order/src/services/order-change-service.ts @@ -11,10 +11,10 @@ import { MedusaContext, MedusaError, ModulesSdkUtils, - OrderChangeStatus, deduplicate, } from "@medusajs/utils" import { OrderChange } from "@models" +import { OrderChangeStatus } from "@types" type InjectedDependencies = { orderChangeRepository: DAL.RepositoryService diff --git a/packages/modules/order/src/services/order-module-service.ts b/packages/modules/order/src/services/order-module-service.ts index 2b6bd4a0aa..ed8e424812 100644 --- a/packages/modules/order/src/services/order-module-service.ts +++ b/packages/modules/order/src/services/order-module-service.ts @@ -1,7 +1,6 @@ import { BigNumberInput, Context, - CreateOrderChangeActionDTO, DAL, FindConfig, InternalModuleDeclaration, @@ -20,7 +19,6 @@ import { createRawPropertiesFromBigNumber, decorateCartTotals, deduplicate, - getShippingMethodsTotals, InjectManager, InjectTransactionManager, isObject, @@ -29,7 +27,6 @@ import { MedusaContext, MedusaError, ModulesSdkUtils, - OrderChangeStatus, OrderStatus, promiseAll, transformPropertiesToBigNumber, @@ -45,6 +42,7 @@ import { OrderItem, OrderShippingMethod, OrderSummary, + Return, ReturnReason, ShippingMethod, ShippingMethodAdjustment, @@ -52,19 +50,26 @@ import { Transaction, } from "@models" import { + CreateOrderChangeDTO, CreateOrderItemDTO, CreateOrderLineItemDTO, CreateOrderLineItemTaxLineDTO, CreateOrderShippingMethodDTO, CreateOrderShippingMethodTaxLineDTO, + OrderChangeStatus, UpdateOrderItemDTO, UpdateOrderLineItemDTO, UpdateOrderLineItemTaxLineDTO, UpdateOrderShippingMethodTaxLineDTO, } from "@types" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" -import { calculateOrderChange, ChangeActionType } from "../utils" -import { formatOrder } from "../utils/transform-order" +import { + applyChangesToOrder, + ApplyOrderChangeDTO, + calculateOrderChange, + formatOrder, +} from "../utils" +import * as BundledActions from "./actions" import OrderChangeService from "./order-change-service" import OrderService from "./order-service" @@ -85,6 +90,7 @@ type InjectedDependencies = { orderSummaryService: ModulesSdkTypes.InternalModuleService orderShippingMethodService: ModulesSdkTypes.InternalModuleService returnReasonService: ModulesSdkTypes.InternalModuleService + returnService: ModulesSdkTypes.InternalModuleService } const generateMethodForModels = [ @@ -102,6 +108,7 @@ const generateMethodForModels = [ OrderSummary, OrderShippingMethod, ReturnReason, + Return, ] export default class OrderModuleService< @@ -119,7 +126,8 @@ export default class OrderModuleService< TOrderItem extends OrderItem = OrderItem, TOrderSummary extends OrderSummary = OrderSummary, TOrderShippingMethod extends OrderShippingMethod = OrderShippingMethod, - TReturnReason extends ReturnReason = ReturnReason + TReturnReason extends ReturnReason = ReturnReason, + TReturn extends Return = Return > extends ModulesSdkUtils.abstractModuleServiceFactory< InjectedDependencies, @@ -141,6 +149,7 @@ export default class OrderModuleService< ReturnReason: { dto: OrderTypes.OrderReturnReasonDTO } OrderSummary: { dto: OrderTypes.OrderSummaryDTO } Transaction: { dto: OrderTypes.OrderTransactionDTO } + Return: { dto: any } // TODO: Add return dto } >(Order, generateMethodForModels, entityNameToLinkableKeysMap) implements IOrderModuleService @@ -161,6 +170,7 @@ export default class OrderModuleService< protected orderSummaryService_: ModulesSdkTypes.InternalModuleService protected orderShippingMethodService_: ModulesSdkTypes.InternalModuleService protected returnReasonService_: ModulesSdkTypes.InternalModuleService + protected returnService_: ModulesSdkTypes.InternalModuleService constructor( { @@ -180,6 +190,7 @@ export default class OrderModuleService< orderSummaryService, orderShippingMethodService, returnReasonService, + returnService, }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { @@ -202,6 +213,7 @@ export default class OrderModuleService< this.orderSummaryService_ = orderSummaryService this.orderShippingMethodService_ = orderShippingMethodService this.returnReasonService_ = returnReasonService + this.returnService_ = returnService } __joinerConfig(): ModuleJoinerConfig { @@ -271,7 +283,7 @@ export default class OrderModuleService< async retrieve( id: string, config?: FindConfig | undefined, - sharedContext?: Context | undefined + @MedusaContext() sharedContext?: Context | undefined ): Promise { config ??= {} const includeTotals = this.shouldIncludeTotals(config) @@ -284,7 +296,7 @@ export default class OrderModuleService< async list( filters?: any, config?: FindConfig | undefined, - sharedContext?: Context | undefined + @MedusaContext() sharedContext?: Context | undefined ): Promise { config ??= {} const includeTotals = this.shouldIncludeTotals(config) @@ -299,7 +311,7 @@ export default class OrderModuleService< async listAndCount( filters?: any, config?: FindConfig | undefined, - sharedContext?: Context | undefined + @MedusaContext() sharedContext?: Context | undefined ): Promise<[OrderTypes.OrderDTO[], number]> { config ??= {} const includeTotals = this.shouldIncludeTotals(config) @@ -316,6 +328,57 @@ export default class OrderModuleService< ] } + // @ts-ignore + async retrieveReturn( + id: string, + config?: FindConfig | undefined, + @MedusaContext() sharedContext?: Context | undefined + ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const returnOrder = await super.retrieveReturn(id, config, sharedContext) + + return formatOrder(returnOrder, { includeTotals }) as OrderTypes.ReturnDTO + } + + // @ts-ignore + async listReturns( + filters?: any, + config?: FindConfig | undefined, + @MedusaContext() sharedContext?: Context | undefined + ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const returnOrders = await super.listReturns(filters, config, sharedContext) + + return formatOrder(returnOrders, { + includeTotals, + }) as OrderTypes.ReturnDTO[] + } + + // @ts-ignore + async listAndCountReturns( + filters?: any, + config?: FindConfig | undefined, + @MedusaContext() sharedContext?: Context | undefined + ): Promise<[OrderTypes.ReturnDTO[], number]> { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const [returnOrders, count] = await super.listAndCountReturns( + filters, + config, + sharedContext + ) + + return [ + formatOrder(returnOrders, { includeTotals }) as OrderTypes.ReturnDTO[], + count, + ] + } + async create( data: OrderTypes.CreateOrderDTO[], sharedContext?: Context @@ -1460,19 +1523,19 @@ export default class OrderModuleService< } async createOrderChange( - data: OrderTypes.CreateOrderChangeDTO, + data: CreateOrderChangeDTO, sharedContext?: Context ): Promise async createOrderChange( - data: OrderTypes.CreateOrderChangeDTO[], + data: CreateOrderChangeDTO[], sharedContext?: Context ): Promise @InjectManager("baseRepository_") async createOrderChange( - data: OrderTypes.CreateOrderChangeDTO | OrderTypes.CreateOrderChangeDTO[], - sharedContext?: Context + data: CreateOrderChangeDTO | CreateOrderChangeDTO[], + @MedusaContext() sharedContext?: Context ): Promise { const changes = await this.createOrderChange_(data, sharedContext) @@ -1486,8 +1549,8 @@ export default class OrderModuleService< @InjectTransactionManager("baseRepository_") protected async createOrderChange_( - data: OrderTypes.CreateOrderChangeDTO | OrderTypes.CreateOrderChangeDTO[], - sharedContext?: Context + data: CreateOrderChangeDTO | CreateOrderChangeDTO[], + @MedusaContext() sharedContext?: Context ): Promise { const dataArr = Array.isArray(data) ? data : [data] @@ -1554,7 +1617,7 @@ export default class OrderModuleService< | string[] | OrderTypes.CancelOrderChangeDTO | OrderTypes.CancelOrderChangeDTO[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise { const data = Array.isArray(orderChangeIdOrData) ? orderChangeIdOrData @@ -1604,7 +1667,7 @@ export default class OrderModuleService< | string[] | OrderTypes.ConfirmOrderChangeDTO | OrderTypes.ConfirmOrderChangeDTO[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise { const data = Array.isArray(orderChangeIdOrData) ? orderChangeIdOrData @@ -1636,6 +1699,9 @@ export default class OrderModuleService< ...action, version: change.version, order_id: change.order_id, + return_id: change.return_id, + claim_id: change.claim_id, + exchange_id: change.exchange_id, } }) return change.actions @@ -1671,7 +1737,7 @@ export default class OrderModuleService< | string[] | OrderTypes.DeclineOrderChangeDTO | OrderTypes.DeclineOrderChangeDTO[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise { const data = Array.isArray(orderChangeIdOrData) ? orderChangeIdOrData @@ -1697,7 +1763,7 @@ export default class OrderModuleService< @InjectManager("baseRepository_") async applyPendingOrderActions( orderId: string | string[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise { const orderIds = Array.isArray(orderId) ? orderId : [orderId] @@ -1737,11 +1803,17 @@ export default class OrderModuleService< sharedContext ) - await this.applyOrderChanges_(changes, sharedContext) + await this.applyOrderChanges_( + changes as ApplyOrderChangeDTO[], + sharedContext + ) } @InjectManager("baseRepository_") - async revertLastVersion(orderId: string, sharedContext?: Context) { + async revertLastVersion( + orderId: string, + @MedusaContext() sharedContext?: Context + ) { const order = await super.retrieve( orderId, { @@ -1852,6 +1924,15 @@ export default class OrderModuleService< }, sharedContext ) + + // Returns + await this.returnService_.delete( + { + order_id: order.id, + order_version: currentVersion, + }, + sharedContext + ) } private async getAndValidateOrderChange_( @@ -1927,7 +2008,7 @@ export default class OrderModuleService< data: | OrderTypes.CreateOrderChangeActionDTO | OrderTypes.CreateOrderChangeActionDTO[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise< OrderTypes.OrderChangeActionDTO | OrderTypes.OrderChangeActionDTO[] > { @@ -1968,22 +2049,14 @@ export default class OrderModuleService< } private async applyOrderChanges_( - changeActions: any[], + changeActions: ApplyOrderChangeDTO[], sharedContext?: Context ): Promise { - type ApplyOrderChangeDTO = { - id: string - order_id: string - version: number - actions: OrderChangeAction[] - applied: boolean - } - const actionsMap: Record = {} const ordersIds: string[] = [] const usedActions: any[] = [] - for (const action of changeActions as ApplyOrderChangeDTO[]) { + for (const action of changeActions) { if (action.applied) { continue } @@ -2023,68 +2096,12 @@ export default class OrderModuleService< sharedContext ) - const itemsToUpsert: OrderItem[] = [] - const shippingMethodsToInsert: OrderShippingMethod[] = [] - const summariesToUpsert: any[] = [] - const orderToUpdate: any[] = [] - - for (const order of orders) { - const calculated = calculateOrderChange({ - order: order as any, - actions: actionsMap[order.id], - transactions: order.transactions, - }) - - createRawPropertiesFromBigNumber(calculated) - - const version = actionsMap[order.id][0].version! - - for (const item of calculated.order.items) { - const orderItem = item.detail as any - itemsToUpsert.push({ - id: orderItem.version === version ? orderItem.id : undefined, - item_id: item.id, - order_id: order.id, - version, - quantity: item.detail.quantity, - fulfilled_quantity: item.detail.fulfilled_quantity, - shipped_quantity: item.detail.shipped_quantity, - return_requested_quantity: item.detail.return_requested_quantity, - return_received_quantity: item.detail.return_received_quantity, - return_dismissed_quantity: item.detail.return_dismissed_quantity, - written_off_quantity: item.detail.written_off_quantity, - metadata: item.detail.metadata, - } as any) - } - - const orderSummary = order.summary as any - summariesToUpsert.push({ - id: orderSummary.version === version ? orderSummary.id : undefined, - order_id: order.id, - version, - totals: calculated.summary, - }) - - if (version > order.version) { - for (const shippingMethod of order.shipping_methods ?? []) { - const sm = { - ...(shippingMethod as any).detail, - version, - } - delete sm.id - shippingMethodsToInsert.push(sm) - } - - orderToUpdate.push({ - selector: { - id: order.id, - }, - data: { - version, - }, - }) - } - } + const { + itemsToUpsert, + shippingMethodsToInsert, + summariesToUpsert, + orderToUpdate, + } = applyChangesToOrder(orders, actionsMap) await promiseAll([ orderToUpdate.length @@ -2108,186 +2125,6 @@ export default class OrderModuleService< ]) } - @InjectTransactionManager("baseRepository_") - async registerFulfillment( - data: OrderTypes.RegisterOrderFulfillmentDTO, - sharedContext?: Context - ): Promise { - const items = data.items.map((item) => { - return { - action: ChangeActionType.FULFILL_ITEM, - internal_note: item.internal_note, - reference: data.reference, - reference_id: data.reference_id, - details: { - reference_id: item.id, - quantity: item.quantity, - metadata: item.metadata, - }, - } - }) - - const change = await this.createOrderChange_( - { - order_id: data.order_id, - description: data.description, - internal_note: data.internal_note, - created_by: data.created_by, - metadata: data.metadata, - actions: items, - }, - sharedContext - ) - - await this.confirmOrderChange(change[0].id, sharedContext) - } - - @InjectTransactionManager("baseRepository_") - async cancelFulfillment( - data: OrderTypes.CancelOrderFulfillmentDTO, - sharedContext?: Context - ): Promise { - const items = data.items.map((item) => { - return { - action: ChangeActionType.CANCEL_ITEM_FULFILLMENT, - internal_note: item.internal_note, - reference: data.reference, - reference_id: data.reference_id, - details: { - reference_id: item.id, - quantity: item.quantity, - metadata: item.metadata, - }, - } - }) - - const change = await this.createOrderChange_( - { - order_id: data.order_id, - description: data.description, - internal_note: data.internal_note, - created_by: data.created_by, - metadata: data.metadata, - actions: items, - }, - sharedContext - ) - - await this.confirmOrderChange(change[0].id, sharedContext) - } - - @InjectTransactionManager("baseRepository_") - async registerShipment( - data: OrderTypes.RegisterOrderShipmentDTO, - sharedContext?: Context - ): Promise { - let shippingMethodId - - const actions: CreateOrderChangeActionDTO[] = data.items.map((item) => { - return { - action: ChangeActionType.SHIP_ITEM, - internal_note: item.internal_note, - reference: data.reference, - reference_id: data.reference_id, - details: { - reference_id: item.id, - quantity: item.quantity, - metadata: item.metadata, - }, - } - }) - - if (shippingMethodId) { - actions.push({ - action: ChangeActionType.SHIPPING_ADD, - reference: data.reference, - reference_id: shippingMethodId, - }) - } - - const change = await this.createOrderChange_( - { - order_id: data.order_id, - description: data.description, - internal_note: data.internal_note, - created_by: data.created_by, - metadata: data.metadata, - actions, - }, - sharedContext - ) - - await this.confirmOrderChange(change[0].id, sharedContext) - } - - @InjectTransactionManager("baseRepository_") - async createReturn( - data: OrderTypes.CreateOrderReturnDTO, - sharedContext?: Context - ): Promise { - let shippingMethodId - - if (!isString(data.shipping_method)) { - const methods = await this.createShippingMethods( - data.order_id, - [{ order_id: data.order_id, ...data.shipping_method }], - sharedContext - ) - shippingMethodId = methods[0].id - } else { - shippingMethodId = data.shipping_method - } - - const method = await this.shippingMethodService_.retrieve( - shippingMethodId, - { - relations: ["tax_lines", "adjustments"], - }, - sharedContext - ) - - const calculatedAmount = getShippingMethodsTotals([method as any], {})[ - method.id - ] - - const actions: CreateOrderChangeActionDTO[] = data.items.map((item) => { - return { - action: ChangeActionType.RETURN_ITEM, - internal_note: item.internal_note, - reference: data.reference ?? "fulfillment", - reference_id: data.reference_id ?? shippingMethodId, - details: { - reference_id: item.id, - quantity: item.quantity, - metadata: item.metadata, - }, - } - }) - - if (shippingMethodId) { - actions.push({ - action: ChangeActionType.SHIPPING_ADD, - reference: data.reference ?? "fulfillment", - reference_id: data.reference_id ?? shippingMethodId, - amount: calculatedAmount.total, - }) - } - - const change = await this.createOrderChange_( - { - order_id: data.order_id, - description: data.description, - internal_note: data.internal_note, - created_by: data.created_by, - metadata: data.metadata, - actions, - }, - sharedContext - ) - - await this.confirmOrderChange(change[0].id, sharedContext) - } - async addTransactions( transactionData: OrderTypes.CreateOrderTransactionDTO, sharedContext?: Context @@ -2303,7 +2140,7 @@ export default class OrderModuleService< transactionData: | OrderTypes.CreateOrderTransactionDTO | OrderTypes.CreateOrderTransactionDTO[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise< OrderTypes.OrderTransactionDTO | OrderTypes.OrderTransactionDTO[] > { @@ -2346,7 +2183,7 @@ export default class OrderModuleService< // @ts-ignore async deleteTransactions( transactionIds: string | object | string[] | object[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise { const data = Array.isArray(transactionIds) ? transactionIds @@ -2376,7 +2213,7 @@ export default class OrderModuleService< async softDeleteTransactions( transactionIds: string | object | string[] | object[], config?: SoftDeleteReturn, - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise | void> { const transactions = await super.listTransactions( { @@ -2408,7 +2245,7 @@ export default class OrderModuleService< async restoreTransactions( transactionIds: string | object | string[] | object[], config?: RestoreReturn, - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise | void> { const transactions = await super.listTransactions( { @@ -2497,7 +2334,7 @@ export default class OrderModuleService< returnReasonData: | OrderTypes.CreateOrderReturnReasonDTO | OrderTypes.CreateOrderReturnReasonDTO[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise< OrderTypes.OrderReturnReasonDTO | OrderTypes.OrderReturnReasonDTO[] > { @@ -2620,40 +2457,6 @@ export default class OrderModuleService< return await this.returnReasonService_.update(toUpdate, sharedContext) } - @InjectTransactionManager("baseRepository_") - async receiveReturn( - data: OrderTypes.ReceiveOrderReturnDTO, - sharedContext?: Context - ): Promise { - const items = data.items.map((item) => { - return { - action: ChangeActionType.RECEIVE_RETURN_ITEM, - internal_note: item.internal_note, - reference: data.reference, - reference_id: data.reference_id, - details: { - reference_id: item.id, - quantity: item.quantity, - metadata: item.metadata, - }, - } - }) - - const change = await this.createOrderChange_( - { - order_id: data.order_id, - description: data.description, - internal_note: data.internal_note, - created_by: data.created_by, - metadata: data.metadata, - actions: items, - }, - sharedContext - ) - - await this.confirmOrderChange(change[0].id, sharedContext) - } - async archive( orderId: string, sharedContext?: Context @@ -2667,7 +2470,7 @@ export default class OrderModuleService< @InjectTransactionManager("baseRepository_") async archive( orderId: string | string[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise { const orderIds = Array.isArray(orderId) ? orderId : [orderId] const orders = await this.list( @@ -2725,7 +2528,7 @@ export default class OrderModuleService< @InjectTransactionManager("baseRepository_") async completeOrder( orderId: string | string[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise { const orderIds = Array.isArray(orderId) ? orderId : [orderId] const orders = await this.list( @@ -2777,7 +2580,7 @@ export default class OrderModuleService< @InjectTransactionManager("baseRepository_") async cancel( orderId: string | string[], - sharedContext?: Context + @MedusaContext() sharedContext?: Context ): Promise { const orderIds = Array.isArray(orderId) ? orderId : [orderId] const orders = await this.list( @@ -2807,4 +2610,76 @@ export default class OrderModuleService< return Array.isArray(orderId) ? orders : orders[0] } + + // ------------------- Bundled Order Actions + + @InjectTransactionManager("baseRepository_") + async createReturn( + data: OrderTypes.CreateOrderReturnDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + // TODO: type ReturnDTO + const ret = await BundledActions.createReturn.bind(this)( + data, + sharedContext + ) + + return await this.retrieveReturn( + ret.id, + { + relations: ["items"], + }, + sharedContext + ) + } + + @InjectManager("baseRepository_") + async receiveReturn( + data: OrderTypes.ReceiveOrderReturnDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + const ret = await this.receiveReturn_(data, sharedContext) + + return await this.retrieveReturn(ret.id, { + relations: ["items"], + }) + } + + @InjectTransactionManager("baseRepository_") + private async receiveReturn_( + data: OrderTypes.ReceiveOrderReturnDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + return await BundledActions.receiveReturn.bind(this)(data, sharedContext) + } + + @InjectTransactionManager("baseRepository_") + async registerFulfillment( + data: OrderTypes.RegisterOrderFulfillmentDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + return await BundledActions.registerFulfillment.bind(this)( + data, + sharedContext + ) + } + + @InjectTransactionManager("baseRepository_") + async cancelFulfillment( + data: OrderTypes.CancelOrderFulfillmentDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + return await BundledActions.cancelFulfillment.bind(this)( + data, + sharedContext + ) + } + + @InjectTransactionManager("baseRepository_") + async registerShipment( + data: OrderTypes.RegisterOrderShipmentDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + return await BundledActions.registerShipment.bind(this)(data, sharedContext) + } } diff --git a/packages/modules/order/src/types/index.ts b/packages/modules/order/src/types/index.ts index 640e25dc05..2b47f90293 100644 --- a/packages/modules/order/src/types/index.ts +++ b/packages/modules/order/src/types/index.ts @@ -5,6 +5,7 @@ export * from "./line-item" export * from "./line-item-adjustment" export * from "./line-item-tax-line" export * from "./order" +export * from "./order-change" export * from "./order-detail" export * from "./shipping-method" export * from "./shipping-method-adjustment" diff --git a/packages/modules/order/src/types/order-change.ts b/packages/modules/order/src/types/order-change.ts new file mode 100644 index 0000000000..e4009570af --- /dev/null +++ b/packages/modules/order/src/types/order-change.ts @@ -0,0 +1,35 @@ +import { OrderTypes } from "@medusajs/types" + +export enum OrderChangeStatus { + /** + * The order change is confirmed. + */ + CONFIRMED = "confirmed", + /** + * The order change is declined. + */ + DECLINED = "declined", + /** + * The order change is requested. + */ + REQUESTED = "requested", + /** + * The order change is pending. + */ + PENDING = "pending", + /** + * The order change is canceled. + */ + CANCELED = "canceled", +} + +export enum OrderChangeType { + RETURN = "return", + EXCHANGE = "exchange", + CLAIM = "claim", + EDIT = "edit", +} + +export interface CreateOrderChangeDTO extends OrderTypes.CreateOrderChangeDTO { + change_type?: OrderChangeType +} diff --git a/packages/modules/order/src/types/utils/index.ts b/packages/modules/order/src/types/utils/index.ts index 1ec0999be9..4b22823ebc 100644 --- a/packages/modules/order/src/types/utils/index.ts +++ b/packages/modules/order/src/types/utils/index.ts @@ -1,13 +1,25 @@ import { BigNumberInput } from "@medusajs/types" export type VirtualOrder = { + id: string + items: { id: string + order_id: string + return_id?: string + claim_id?: string + exchange_id?: string + unit_price: BigNumberInput quantity: BigNumberInput detail: { id?: string + order_id: string + return_id?: string + claim_id?: string + exchange_id?: string + quantity: BigNumberInput shipped_quantity: BigNumberInput fulfilled_quantity: BigNumberInput @@ -20,12 +32,26 @@ export type VirtualOrder = { }[] shipping_methods: { - id: string + shipping_method_id: string + order_id: string + return_id?: string + claim_id?: string + exchange_id?: string + + detail?: { + id?: string + order_id: string + return_id?: string + claim_id?: string + exchange_id?: string + } + price: BigNumberInput }[] total: BigNumberInput + transactions?: OrderTransaction[] metadata?: Record } @@ -59,6 +85,10 @@ export interface OrderChangeEvent { reference?: string reference_id?: string + return_id?: string + claim_id?: string + exchange_id?: string + group_id?: string evaluationOnly?: boolean diff --git a/packages/modules/order/src/utils/actions/cancel-item-fulfillment.ts b/packages/modules/order/src/utils/actions/cancel-item-fulfillment.ts index ad26aed5f0..2ff091e067 100644 --- a/packages/modules/order/src/utils/actions/cancel-item-fulfillment.ts +++ b/packages/modules/order/src/utils/actions/cancel-item-fulfillment.ts @@ -1,6 +1,7 @@ import { MathBN, MedusaError, isDefined } from "@medusajs/utils" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType( ChangeActionType.CANCEL_ITEM_FULFILLMENT, @@ -16,6 +17,8 @@ OrderChangeProcessing.registerActionType( existing.detail.fulfilled_quantity, action.details.quantity ) + + setActionReference(existing, action) }, revert({ action, currentOrder }) { const existing = currentOrder.items.find( diff --git a/packages/modules/order/src/utils/actions/cancel-return.ts b/packages/modules/order/src/utils/actions/cancel-return.ts index ee97328a4c..21c56c7daf 100644 --- a/packages/modules/order/src/utils/actions/cancel-return.ts +++ b/packages/modules/order/src/utils/actions/cancel-return.ts @@ -1,6 +1,7 @@ import { MathBN, MedusaError, isDefined } from "@medusajs/utils" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN, { operation({ action, currentOrder }) { @@ -15,6 +16,8 @@ OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN, { action.details.quantity ) + setActionReference(existing, action) + return action.details.unit_price * action.details.quantity }, revert({ action, currentOrder }) { diff --git a/packages/modules/order/src/utils/actions/fulfill-item.ts b/packages/modules/order/src/utils/actions/fulfill-item.ts index 07e79c0021..d0dcbf04f0 100644 --- a/packages/modules/order/src/utils/actions/fulfill-item.ts +++ b/packages/modules/order/src/utils/actions/fulfill-item.ts @@ -1,6 +1,7 @@ import { MathBN, MedusaError, isDefined } from "@medusajs/utils" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType(ChangeActionType.FULFILL_ITEM, { operation({ action, currentOrder }) { @@ -14,6 +15,8 @@ OrderChangeProcessing.registerActionType(ChangeActionType.FULFILL_ITEM, { existing.detail.fulfilled_quantity, action.details.quantity ) + + setActionReference(existing, action) }, revert({ action, currentOrder }) { const existing = currentOrder.items.find( diff --git a/packages/modules/order/src/utils/actions/item-add.ts b/packages/modules/order/src/utils/actions/item-add.ts index ffcc5e6f6c..3795b08db3 100644 --- a/packages/modules/order/src/utils/actions/item-add.ts +++ b/packages/modules/order/src/utils/actions/item-add.ts @@ -2,6 +2,7 @@ import { MathBN, MedusaError, isDefined } from "@medusajs/utils" import { VirtualOrder } from "@types" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, { operation({ action, currentOrder }) { @@ -18,9 +19,16 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, { existing.detail.quantity, action.details.quantity ) + + setActionReference(existing, action) } else { currentOrder.items.push({ id: action.reference_id!, + order_id: currentOrder.id, + return_id: action.details.return_id, + claim_id: action.details.claim_id, + exchange_id: action.details.exchange_id, + unit_price: action.details.unit_price, quantity: action.details.quantity, } as VirtualOrder["items"][0]) diff --git a/packages/modules/order/src/utils/actions/item-remove.ts b/packages/modules/order/src/utils/actions/item-remove.ts index f7ac47b90a..7ae373e229 100644 --- a/packages/modules/order/src/utils/actions/item-remove.ts +++ b/packages/modules/order/src/utils/actions/item-remove.ts @@ -2,6 +2,7 @@ import { MathBN, MedusaError, isDefined } from "@medusajs/utils" import { VirtualOrder } from "@types" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_REMOVE, { isDeduction: true, @@ -20,6 +21,8 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_REMOVE, { action.details.quantity ) + setActionReference(existing, action) + if (MathBN.lte(existing.quantity, 0)) { currentOrder.items.splice(existingIndex, 1) } diff --git a/packages/modules/order/src/utils/actions/receive-damaged-return-item.ts b/packages/modules/order/src/utils/actions/receive-damaged-return-item.ts index 3c55f0091b..383d12232b 100644 --- a/packages/modules/order/src/utils/actions/receive-damaged-return-item.ts +++ b/packages/modules/order/src/utils/actions/receive-damaged-return-item.ts @@ -2,6 +2,7 @@ import { MathBN, MedusaError, isDefined } from "@medusajs/utils" import { EVENT_STATUS } from "@types" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType( ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM, @@ -27,6 +28,8 @@ OrderChangeProcessing.registerActionType( toReturn ) + setActionReference(existing, action) + if (previousEvents) { for (const previousEvent of previousEvents) { previousEvent.original_ = JSON.parse(JSON.stringify(previousEvent)) diff --git a/packages/modules/order/src/utils/actions/receive-return-item.ts b/packages/modules/order/src/utils/actions/receive-return-item.ts index c1694dbf80..d4faa96486 100644 --- a/packages/modules/order/src/utils/actions/receive-return-item.ts +++ b/packages/modules/order/src/utils/actions/receive-return-item.ts @@ -7,6 +7,7 @@ import { import { EVENT_STATUS } from "@types" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType(ChangeActionType.RECEIVE_RETURN_ITEM, { isDeduction: true, @@ -30,6 +31,8 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RECEIVE_RETURN_ITEM, { toReturn ) + setActionReference(existing, action) + if (previousEvents) { for (const previousEvent of previousEvents) { previousEvent.original_ = JSON.parse(JSON.stringify(previousEvent)) diff --git a/packages/modules/order/src/utils/actions/return-item.ts b/packages/modules/order/src/utils/actions/return-item.ts index a5d1be4c83..21451ac784 100644 --- a/packages/modules/order/src/utils/actions/return-item.ts +++ b/packages/modules/order/src/utils/actions/return-item.ts @@ -1,6 +1,7 @@ import { MathBN, MedusaError, isDefined } from "@medusajs/utils" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType(ChangeActionType.RETURN_ITEM, { isDeduction: true, @@ -16,6 +17,8 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RETURN_ITEM, { action.details.quantity ) + setActionReference(existing, action) + return MathBN.mult(existing.unit_price, action.details.quantity) }, revert({ action, currentOrder }) { diff --git a/packages/modules/order/src/utils/actions/ship-item.ts b/packages/modules/order/src/utils/actions/ship-item.ts index f58001f115..8a3769ee0f 100644 --- a/packages/modules/order/src/utils/actions/ship-item.ts +++ b/packages/modules/order/src/utils/actions/ship-item.ts @@ -1,6 +1,7 @@ import { MathBN, MedusaError, isDefined } from "@medusajs/utils" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType(ChangeActionType.SHIP_ITEM, { operation({ action, currentOrder }) { @@ -14,6 +15,8 @@ OrderChangeProcessing.registerActionType(ChangeActionType.SHIP_ITEM, { existing.detail.shipped_quantity, action.details.quantity ) + + setActionReference(existing, action) }, revert({ action, currentOrder }) { const existing = currentOrder.items.find( diff --git a/packages/modules/order/src/utils/actions/shipping-add.ts b/packages/modules/order/src/utils/actions/shipping-add.ts index c3f61fba04..d5914209b8 100644 --- a/packages/modules/order/src/utils/actions/shipping-add.ts +++ b/packages/modules/order/src/utils/actions/shipping-add.ts @@ -9,7 +9,12 @@ OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, { : [currentOrder.shipping_methods] shipping.push({ - id: action.reference_id!, + shipping_method_id: action.reference_id!, + order_id: currentOrder.id, + return_id: action.return_id, + claim_id: action.claim_id, + exchange_id: action.exchange_id, + price: action.amount as number, }) @@ -21,7 +26,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, { : [currentOrder.shipping_methods] const existingIndex = shipping.findIndex( - (item) => item.id === action.reference_id + (item) => item.shipping_method_id === action.reference_id ) if (existingIndex > -1) { diff --git a/packages/modules/order/src/utils/actions/write-off-item.ts b/packages/modules/order/src/utils/actions/write-off-item.ts index 3259e6b15d..5b56b72d7f 100644 --- a/packages/modules/order/src/utils/actions/write-off-item.ts +++ b/packages/modules/order/src/utils/actions/write-off-item.ts @@ -1,6 +1,7 @@ import { MathBN, MedusaError, isDefined } from "@medusajs/utils" import { ChangeActionType } from "../action-key" import { OrderChangeProcessing } from "../calculate-order-change" +import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType(ChangeActionType.WRITE_OFF_ITEM, { operation({ action, currentOrder }) { @@ -13,6 +14,8 @@ OrderChangeProcessing.registerActionType(ChangeActionType.WRITE_OFF_ITEM, { existing.detail.written_off_quantity, action.details.quantity ) + + setActionReference(existing, action) }, revert({ action, currentOrder }) { const existing = currentOrder.items.find( diff --git a/packages/modules/order/src/utils/apply-order-changes.ts b/packages/modules/order/src/utils/apply-order-changes.ts new file mode 100644 index 0000000000..804aea8971 --- /dev/null +++ b/packages/modules/order/src/utils/apply-order-changes.ts @@ -0,0 +1,89 @@ +import { OrderChangeActionDTO } from "@medusajs/types" +import { createRawPropertiesFromBigNumber } from "@medusajs/utils" +import { OrderItem, OrderShippingMethod } from "@models" +import { calculateOrderChange } from "./calculate-order-change" + +export interface ApplyOrderChangeDTO extends OrderChangeActionDTO { + id: string + order_id: string + version: number + applied: boolean +} + +export function applyChangesToOrder( + orders: any[], + actionsMap: Record +) { + const itemsToUpsert: OrderItem[] = [] + const shippingMethodsToInsert: OrderShippingMethod[] = [] + const summariesToUpsert: any[] = [] + const orderToUpdate: any[] = [] + + for (const order of orders) { + const calculated = calculateOrderChange({ + order: order as any, + actions: actionsMap[order.id], + transactions: order.transactions ?? [], + }) + + createRawPropertiesFromBigNumber(calculated) + + const version = actionsMap[order.id][0].version ?? 1 + + for (const item of calculated.order.items) { + const orderItem = item.detail as any + itemsToUpsert.push({ + id: orderItem.version === version ? orderItem.id : undefined, + item_id: item.id, + order_id: order.id, + version, + return_id: item.detail.return_id, + claim_id: item.detail.claim_id, + exchange_id: item.detail.exchange_id, + quantity: item.detail.quantity, + fulfilled_quantity: item.detail.fulfilled_quantity, + shipped_quantity: item.detail.shipped_quantity, + return_requested_quantity: item.detail.return_requested_quantity, + return_received_quantity: item.detail.return_received_quantity, + return_dismissed_quantity: item.detail.return_dismissed_quantity, + written_off_quantity: item.detail.written_off_quantity, + metadata: item.detail.metadata, + } as any) + } + + const orderSummary = order.summary as any + summariesToUpsert.push({ + id: orderSummary?.version === version ? orderSummary.id : undefined, + order_id: order.id, + version, + totals: calculated.summary, + }) + + if (version > order.version) { + for (const shippingMethod of calculated.order.shipping_methods ?? []) { + const sm = { + ...((shippingMethod as any).detail ?? shippingMethod), + version, + } + delete sm.id + shippingMethodsToInsert.push(sm) + } + + orderToUpdate.push({ + selector: { + id: order.id, + }, + data: { + version, + }, + }) + } + } + + return { + itemsToUpsert, + shippingMethodsToInsert, + summariesToUpsert, + orderToUpdate, + } +} diff --git a/packages/modules/order/src/utils/base-repository-find.ts b/packages/modules/order/src/utils/base-repository-find.ts new file mode 100644 index 0000000000..fad16abd6d --- /dev/null +++ b/packages/modules/order/src/utils/base-repository-find.ts @@ -0,0 +1,151 @@ +import { Constructor, Context, DAL } from "@medusajs/types" +import { LoadStrategy } from "@mikro-orm/core" +import { Order } from "@models" +import { mapRepositoryToOrderModel } from "." + +export function setFindMethods(klass: Constructor, entity: any) { + klass.prototype.find = async function find( + this: any, + options?: DAL.FindOptions, + context?: Context + ): Promise { + const manager = this.getActiveManager(context) + const knex = manager.getKnex() + + const findOptions_ = { ...options } as any + findOptions_.options ??= {} + findOptions_.where ??= {} + findOptions_.populate ??= [] + + if (!("strategy" in findOptions_.options)) { + if (findOptions_.options.limit != null || findOptions_.options.offset) { + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + } else { + Object.assign(findOptions_.options, { + strategy: LoadStrategy.JOINED, + }) + } + } + + const config = mapRepositoryToOrderModel(findOptions_) + + let orderAlias = "o0" + if (entity !== Order) { + // first relation is always order if entity is not Order + config.options.populate.unshift("order") + orderAlias = "o1" + } + + let defaultVersion = knex.raw(`"${orderAlias}"."version"`) + const strategy = config.options.strategy ?? LoadStrategy.JOINED + if (strategy === LoadStrategy.SELECT_IN) { + const sql = manager + .qb(Order, "_sub0") + .select("version") + .where({ id: knex.raw(`"o0"."order_id"`) }) + .getKnexQuery() + .toString() + + defaultVersion = knex.raw(`(${sql})`) + } + + const version = config.where.version ?? defaultVersion + delete config.where?.version + + config.options.populateWhere ??= {} + + if (entity !== Order) { + config.options.populateWhere.order ??= {} + config.options.populateWhere.order.version = version + + config.options.populateWhere.order.summary ??= {} + config.options.populateWhere.order.summary.version = version + } else { + config.options.populateWhere.summary ??= {} + config.options.populateWhere.summary.version = version + } + + config.options.populateWhere.items ??= {} + config.options.populateWhere.items.version = version + + config.options.populateWhere.shipping_methods ??= {} + config.options.populateWhere.shipping_methods.version = version + + if (!config.options.orderBy) { + config.options.orderBy = { id: "ASC" } + } + + return await manager.find(entity, config.where, config.options) + } + + klass.prototype.findAndCount = async function findAndCount( + this: any, + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise<[T[], number]> { + const manager = this.getActiveManager(context) + const knex = manager.getKnex() + + const findOptions_ = { ...findOptions } as any + findOptions_.options ??= {} + findOptions_.where ??= {} + + if (!("strategy" in findOptions_.options)) { + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + } + + const config = mapRepositoryToOrderModel(findOptions_) + + let orderAlias = "o0" + if (entity !== Order) { + // first relation is always order if entity is not Order + config.options.populate.unshift("order") + orderAlias = "o1" + } + + let defaultVersion = knex.raw(`"${orderAlias}"."version"`) + const strategy = config.options.strategy ?? LoadStrategy.JOINED + if (strategy === LoadStrategy.SELECT_IN) { + const sql = manager + .qb(Order, "_sub0") + .select("version") + .where({ id: knex.raw(`"o0"."order_id"`) }) + .getKnexQuery() + .toString() + + defaultVersion = knex.raw(`(${sql})`) + } + + const version = config.where.version ?? defaultVersion + delete config.where.version + + config.options.populateWhere ??= {} + + if (entity !== Order) { + config.options.populateWhere.order ??= {} + config.options.populateWhere.order.version = version + + config.options.populateWhere.order.summary ??= {} + config.options.populateWhere.order.summary.version = version + } else { + config.options.populateWhere.summary ??= {} + config.options.populateWhere.summary.version = version + } + + config.options.populateWhere.items ??= {} + config.options.populateWhere.items.version = version + + config.options.populateWhere.shipping_methods ??= {} + config.options.populateWhere.shipping_methods.version = version + + if (!config.options.orderBy) { + config.options.orderBy = { id: "ASC" } + } + + return await manager.findAndCount(entity, config.where, config.options) + } +} diff --git a/packages/modules/order/src/utils/index.ts b/packages/modules/order/src/utils/index.ts index 9b768e3957..a542e5f36d 100644 --- a/packages/modules/order/src/utils/index.ts +++ b/packages/modules/order/src/utils/index.ts @@ -1,3 +1,6 @@ export * from "./action-key" export * from "./actions" +export * from "./apply-order-changes" export * from "./calculate-order-change" +export * from "./set-action-reference" +export * from "./transform-order" diff --git a/packages/modules/order/src/utils/set-action-reference.ts b/packages/modules/order/src/utils/set-action-reference.ts new file mode 100644 index 0000000000..345d2b065e --- /dev/null +++ b/packages/modules/order/src/utils/set-action-reference.ts @@ -0,0 +1,6 @@ +export function setActionReference(existing, action) { + existing.detail.order_id ??= action.order_id + existing.detail.return_id ??= action.return_id + existing.detail.claim_id ??= action.claim_id + existing.detail.exchange_id ??= action.exchange_id +}