From cfa983001b1e76c27cc1fbf0e25073901afffcda Mon Sep 17 00:00:00 2001 From: "Carlos R. L. Rodrigues" <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:08:16 -0300 Subject: [PATCH] feat(order): create claim and exchange (#7734) --- package.json | 3 +- packages/core/types/package.json | 1 - .../types/src/modules-sdk/module-service.ts | 0 packages/core/types/src/order/common.ts | 36 +- packages/core/types/src/order/mutations.ts | 46 ++- packages/core/types/src/order/service.ts | 66 +++- packages/core/types/src/shared-context.ts | 5 +- .../src/workflow/order/create-return-order.ts | 5 +- .../src/common/pick-value-from-object.ts | 33 +- .../__tests__/order-claim.ts | 242 ++++++++++++ .../integration-tests/__tests__/order-edit.ts | 17 +- .../__tests__/order-exchange.ts | 230 +++++++++++ .../__tests__/order-return.ts | 92 +++-- .../src/migrations/Migration20240604100512.ts | 86 +++-- .../order/src/models/claim-item-image.ts | 6 +- .../modules/order/src/models/claim-item.ts | 34 +- packages/modules/order/src/models/claim.ts | 17 +- .../modules/order/src/models/exchange-item.ts | 116 ++++++ packages/modules/order/src/models/exchange.ts | 23 +- packages/modules/order/src/models/index.ts | 6 +- .../order/src/models/order-change-action.ts | 52 +++ .../modules/order/src/models/order-change.ts | 50 +++ .../modules/order/src/models/order-item.ts | 72 ---- .../order/src/models/order-shipping-method.ts | 2 + .../modules/order/src/models/return-item.ts | 143 +++++++ packages/modules/order/src/models/return.ts | 8 +- .../modules/order/src/models/transaction.ts | 6 + .../modules/order/src/repositories/claim.ts | 9 + .../order/src/repositories/exchange.ts | 9 + .../modules/order/src/repositories/index.ts | 1 + .../__tests__/util/actions/exchanges.ts | 2 +- .../src/services/actions/create-claim.ts | 365 ++++++++++++++++++ .../src/services/actions/create-exchange.ts | 316 +++++++++++++++ .../src/services/actions/create-return.ts | 157 +++++--- .../order/src/services/actions/index.ts | 2 + .../src/services/actions/receive-return.ts | 135 ++++--- .../src/services/order-module-service.ts | 345 ++++++++++++++--- .../modules/order/src/utils/actions/index.ts | 1 + .../order/src/utils/actions/item-add.ts | 14 +- .../order/src/utils/actions/item-remove.ts | 8 +- .../order/src/utils/apply-order-changes.ts | 31 +- .../order/src/utils/base-repository-find.ts | 68 ++-- .../order/src/utils/calculate-order-change.ts | 8 +- .../order/src/utils/transform-order.ts | 138 ++++++- yarn.lock | 2 - 45 files changed, 2571 insertions(+), 437 deletions(-) delete mode 100644 packages/core/types/src/modules-sdk/module-service.ts create mode 100644 packages/modules/order/integration-tests/__tests__/order-claim.ts create mode 100644 packages/modules/order/integration-tests/__tests__/order-exchange.ts create mode 100644 packages/modules/order/src/models/exchange-item.ts create mode 100644 packages/modules/order/src/models/return-item.ts create mode 100644 packages/modules/order/src/repositories/claim.ts create mode 100644 packages/modules/order/src/repositories/exchange.ts create mode 100644 packages/modules/order/src/services/actions/create-claim.ts create mode 100644 packages/modules/order/src/services/actions/create-exchange.ts diff --git a/package.json b/package.json index f7de0605ba..4284892f4a 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,7 @@ "resolve-cwd": "^3.0.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "turbo": "^1.6.3", - "typeorm": "^0.3.16" + "turbo": "^1.6.3" }, "lint-staged": { "*.{js,jsx,ts,tsx}": "yarn run lint", diff --git a/packages/core/types/package.json b/packages/core/types/package.json index c1959fb408..2b435ede45 100644 --- a/packages/core/types/package.json +++ b/packages/core/types/package.json @@ -26,7 +26,6 @@ "cross-env": "^5.2.1", "ioredis": "^5.2.5", "rimraf": "^5.0.1", - "typeorm": "^0.3.16", "typescript": "^5.1.6", "vite": "^5.2.11", "winston": "^3.8.2" diff --git a/packages/core/types/src/modules-sdk/module-service.ts b/packages/core/types/src/modules-sdk/module-service.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/types/src/order/common.ts b/packages/core/types/src/order/common.ts index a1155e8cb3..edd5326b6e 100644 --- a/packages/core/types/src/order/common.ts +++ b/packages/core/types/src/order/common.ts @@ -2,7 +2,7 @@ import { BaseFilterable } from "../dal" import { OperatorMap } from "../dal/utils" import { FulfillmentDTO } from "../fulfillment" import { PaymentCollectionDTO } from "../payment" -import { BigNumberRawValue, BigNumberValue } from "../totals" +import { BigNumberInput, BigNumberRawValue, BigNumberValue } from "../totals" export type ChangeActionType = | "CANCEL" @@ -1113,6 +1113,25 @@ type ReturnStatus = "requested" | "received" | "partially_received" | "canceled" export interface ReturnDTO extends Omit { status: ReturnStatus + refund_amount?: BigNumberValue +} + +export interface OrderClaimDTO + extends Omit { + claim_items: any[] + additional_items: any[] + return?: ReturnDTO + no_notification?: boolean + refund_amount?: BigNumberValue +} + +export interface OrderExchangeDTO + extends Omit { + return_items: any[] + additional_items: any[] + no_notification?: boolean + difference_due?: BigNumberValue + return?: ReturnDTO } export type PaymentStatus = @@ -1531,3 +1550,18 @@ export interface FilterableOrderReturnReasonProps { label?: string description?: string } + +export interface OrderChangeReturn { + items: { + item_id: string + order_id: string + fulfilled_quantity: BigNumberInput + shipped_quantity: BigNumberInput + return_requested_quantity: BigNumberInput + return_received_quantity: BigNumberInput + return_dismissed_quantity: BigNumberInput + written_off_quantity: BigNumberInput + [key: string]: any + }[] + shippingMethods: any[] +} diff --git a/packages/core/types/src/order/mutations.ts b/packages/core/types/src/order/mutations.ts index 5546cbc212..7c9fa663de 100644 --- a/packages/core/types/src/order/mutations.ts +++ b/packages/core/types/src/order/mutations.ts @@ -379,6 +379,7 @@ interface BaseOrderBundledItemActionsDTO { id: string quantity: BigNumberInput internal_note?: string + note?: string metadata?: Record | null } interface BaseOrderBundledActionsDTO { @@ -406,11 +407,52 @@ export interface CancelOrderFulfillmentDTO extends BaseOrderBundledActionsDTO { export interface RegisterOrderShipmentDTO extends BaseOrderBundledActionsDTO { items: BaseOrderBundledItemActionsDTO[] + no_notification?: boolean } export interface CreateOrderReturnDTO extends BaseOrderBundledActionsDTO { - items: BaseOrderBundledItemActionsDTO[] - shipping_method: Omit | string + items: { + id: string + quantity: BigNumberInput + internal_note?: string + note?: string + reason_id?: string + metadata?: Record + }[] + shipping_method?: Omit | string + refund_amount?: BigNumberInput + no_notification?: boolean +} + +export type OrderClaimType = "refund" | "replace" +export type ClaimReason = + | "missing_item" + | "wrong_item" + | "production_failure" + | "other" +export interface CreateOrderClaimDTO extends BaseOrderBundledActionsDTO { + type: OrderClaimType + claim_items: (BaseOrderBundledItemActionsDTO & { + reason: ClaimReason + images?: { + url: string + metadata?: Record + }[] + })[] + additional_items?: BaseOrderBundledItemActionsDTO[] + shipping_methods?: Omit[] | string[] + return_shipping?: Omit | string + refund_amount?: BigNumberInput + no_notification?: boolean +} + +export interface CreateOrderExchangeDTO extends BaseOrderBundledActionsDTO { + additional_items?: BaseOrderBundledItemActionsDTO[] + shipping_methods?: Omit[] | string[] + return_shipping: Omit | string + difference_due?: BigNumberInput + allow_backorder?: boolean + no_notification?: boolean } export interface CancelOrderReturnDTO { diff --git a/packages/core/types/src/order/service.ts b/packages/core/types/src/order/service.ts index eeb99334a4..27f15dca2b 100644 --- a/packages/core/types/src/order/service.ts +++ b/packages/core/types/src/order/service.ts @@ -16,7 +16,10 @@ import { OrderAddressDTO, OrderChangeActionDTO, OrderChangeDTO, + OrderChangeReturn, + OrderClaimDTO, OrderDTO, + OrderExchangeDTO, OrderItemDTO, OrderLineItemAdjustmentDTO, OrderLineItemDTO, @@ -36,7 +39,9 @@ import { CreateOrderAdjustmentDTO, CreateOrderChangeActionDTO, CreateOrderChangeDTO, + CreateOrderClaimDTO, CreateOrderDTO, + CreateOrderExchangeDTO, CreateOrderLineItemDTO, CreateOrderLineItemForOrderDTO, CreateOrderLineItemTaxLineDTO, @@ -149,6 +154,42 @@ export interface IOrderModuleService extends IModuleService { sharedContext?: Context ): Promise<[ReturnDTO[], number]> + retrieveOrderClaim( + claimnId: string, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listOrderClaims( + filters?: FilterableOrderProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listAndCountOrderClaims( + filters?: FilterableOrderProps, + config?: FindConfig, + sharedContext?: Context + ): Promise<[OrderClaimDTO[], number]> + + retrieveOrderExchange( + claimnId: string, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listOrderExchanges( + filters?: FilterableOrderProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listAndCountOrderExchanges( + filters?: FilterableOrderProps, + config?: FindConfig, + sharedContext?: Context + ): Promise<[OrderExchangeDTO[], number]> + /** * This method creates {return type}(s) * @@ -1154,7 +1195,7 @@ export interface IOrderModuleService extends IModuleService { * * @param {string} orderId - The order's ID. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} Resolves when {summary} + * @returns {Promise} Resolves when {summary} * * @example * ```typescript @@ -1165,7 +1206,7 @@ export interface IOrderModuleService extends IModuleService { confirmOrderChange( orderChangeId: string, sharedContext?: Context - ): Promise + ): Promise /** * This method Represents the completion of an asynchronous operation @@ -1310,7 +1351,7 @@ export interface IOrderModuleService extends IModuleService { * * @param {string | string[]} orderId - The order's ID. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {(orderId: string | string[], sharedContext?: Context) => any} {summary} + * @returns {(orderId: string | string[], sharedContext?: Context) => Promise} {summary} * * @example * ```typescript @@ -1318,7 +1359,10 @@ export interface IOrderModuleService extends IModuleService { * ``` * */ - applyPendingOrderActions(orderId: string | string[], sharedContext?: Context) + applyPendingOrderActions( + orderId: string | string[], + sharedContext?: Context + ): Promise addOrderAction( data: CreateOrderChangeActionDTO, @@ -1532,7 +1576,7 @@ export interface IOrderModuleService extends IModuleService { createReturn( returnData: CreateOrderReturnDTO, sharedContext?: Context - ): Promise // TODO: ReturnDTO + ): Promise /* cancelReturn( @@ -1544,5 +1588,15 @@ export interface IOrderModuleService extends IModuleService { receiveReturn( returnData: ReceiveOrderReturnDTO, sharedContext?: Context - ): Promise // TODO: ReturnDTO + ): Promise + + createClaim( + claimData: CreateOrderClaimDTO, + sharedContext?: Context + ): Promise + + createExchange( + exchangeData: CreateOrderExchangeDTO, + sharedContext?: Context + ): Promise } diff --git a/packages/core/types/src/shared-context.ts b/packages/core/types/src/shared-context.ts index 75e30e636c..035908b34e 100644 --- a/packages/core/types/src/shared-context.ts +++ b/packages/core/types/src/shared-context.ts @@ -1,4 +1,3 @@ -import { EntityManager } from "typeorm" import { EventBusTypes } from "./bundles" import { Message } from "./event-bus" @@ -12,11 +11,11 @@ export type SharedContext = { /** * An instance of a transaction manager. */ - transactionManager?: EntityManager + transactionManager?: any /** * An instance of an entity manager. */ - manager?: EntityManager + manager?: any } export interface MessageAggregatorFormat { diff --git a/packages/core/types/src/workflow/order/create-return-order.ts b/packages/core/types/src/workflow/order/create-return-order.ts index 8ce1a3b01e..a96304d769 100644 --- a/packages/core/types/src/workflow/order/create-return-order.ts +++ b/packages/core/types/src/workflow/order/create-return-order.ts @@ -1,9 +1,10 @@ import { BigNumberInput } from "../../totals" -interface CreateOrderReturnItem { +interface CreateReturnItem { id: string quantity: BigNumberInput internal_note?: string + note?: string reason_id?: string metadata?: Record } @@ -11,7 +12,7 @@ interface CreateOrderReturnItem { export interface CreateOrderReturnWorkflowInput { order_id: string created_by?: string // The id of the authenticated user - items: CreateOrderReturnItem[] + items: CreateReturnItem[] return_shipping: { option_id: string price?: number diff --git a/packages/core/utils/src/common/pick-value-from-object.ts b/packages/core/utils/src/common/pick-value-from-object.ts index 7153e23b0d..0a089a13d7 100644 --- a/packages/core/utils/src/common/pick-value-from-object.ts +++ b/packages/core/utils/src/common/pick-value-from-object.ts @@ -1,3 +1,4 @@ +import { isDefined } from "./is-defined" import { isObject } from "./is-object" export function pickValueFromObject( @@ -5,32 +6,28 @@ export function pickValueFromObject( object: Record ): any { const segments = path.split(".") - let result: any = undefined + let result: any = object - for (const segment of segments) { - const segmentsLeft = [...segments].splice(1, segments.length - 1) - const segmentOutput = object[segment] + for (let i = 0; i < segments.length; i++) { + const segment = segments[i] + result = result[segment] - if (segmentsLeft.length === 0) { - result = segmentOutput - break + if (!isDefined(result)) { + return } - if (isObject(segmentOutput)) { - result = pickValueFromObject(segmentsLeft.join("."), segmentOutput) - break + if (i === segments.length - 1) { + return result } - if (Array.isArray(segmentOutput)) { - result = segmentOutput - .map((segmentOutput_) => - pickValueFromObject(segmentsLeft.join("."), segmentOutput_) - ) - .flat() - break + if (Array.isArray(result)) { + const subPath = segments.slice(i + 1).join(".") + return result.map((item) => pickValueFromObject(subPath, item)).flat() } - result = segmentOutput + if (!isObject(result)) { + return result + } } return result diff --git a/packages/modules/order/integration-tests/__tests__/order-claim.ts b/packages/modules/order/integration-tests/__tests__/order-claim.ts new file mode 100644 index 0000000000..5c7e70ea35 --- /dev/null +++ b/packages/modules/order/integration-tests/__tests__/order-claim.ts @@ -0,0 +1,242 @@ +import { Modules } from "@medusajs/modules-sdk" +import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types" +import { ClaimType } from "@medusajs/utils" +import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" + +jest.setTimeout(100000) + +moduleIntegrationTestRunner({ + moduleName: Modules.ORDER, + testSuite: ({ service }: SuiteOptions) => { + describe("Order Module Service - Claim flows", () => { + const input = { + email: "foo@bar.com", + items: [ + { + title: "Item 1", + subtitle: "Subtitle 1", + thumbnail: "thumbnail1.jpg", + quantity: 1, + product_id: "product1", + product_title: "Product 1", + product_description: "Description 1", + product_subtitle: "Product Subtitle 1", + product_type: "Type 1", + product_collection: "Collection 1", + product_handle: "handle1", + variant_id: "variant1", + variant_sku: "SKU1", + variant_barcode: "Barcode1", + variant_title: "Variant 1", + variant_option_values: { + color: "Red", + size: "Large", + }, + requires_shipping: true, + is_discountable: true, + is_tax_inclusive: true, + compare_at_unit_price: 10, + unit_price: 8, + tax_lines: [ + { + description: "Tax 1", + tax_rate_id: "tax_usa", + code: "code", + rate: 0.1, + provider_id: "taxify_master", + }, + ], + adjustments: [ + { + code: "VIP_10", + amount: 10, + description: "VIP discount", + promotion_id: "prom_123", + provider_id: "coupon_kings", + }, + ], + }, + { + title: "Item 2", + quantity: 2, + unit_price: 5, + }, + { + title: "Item 3", + quantity: 1, + unit_price: 30, + }, + ], + sales_channel_id: "test", + shipping_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + phone: "12345", + }, + billing_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + }, + shipping_methods: [ + { + name: "Test shipping method", + amount: 10, + }, + ], + transactions: [ + { + amount: 58, + currency_code: "USD", + reference: "payment", + reference_id: "pay_123", + }, + ], + currency_code: "usd", + customer_id: "joe", + } as CreateOrderDTO + + it("should claim an item and add two new items to the order", async function () { + const createdOrder = await service.create(input) + + // Fullfilment + await service.registerFulfillment({ + order_id: createdOrder.id, + items: createdOrder.items!.map((item) => { + return { + id: item.id, + quantity: item.quantity, + } + }), + }) + + // Shipment + await service.registerShipment({ + order_id: createdOrder.id, + reference: Modules.FULFILLMENT, + items: createdOrder.items!.map((item) => { + return { + id: item.id, + quantity: item.quantity, + } + }), + }) + + // Claim + const orderClaim = await service.createClaim({ + order_id: createdOrder.id, + type: ClaimType.REPLACE, + description: "Claim all the items", + internal_note: "user wants to return all items", + shipping_methods: [ + { + name: "Claim method", + amount: 35, + provider_id: "dhl", + }, + ], + claim_items: [ + { + id: createdOrder.items![1].id, + quantity: 1, + reason: "production_failure", + }, + ], + additional_items: [ + { + id: createdOrder.items![2].id, + quantity: 1, + }, + { + unit_price: 20, + quantity: 1, + title: "New item", + }, + ], + return_amount: 5, + return_shipping: { + name: "return shipping method", + amount: 10, + provider_id: "test_provider_id", + }, + }) + + expect(orderClaim).toEqual( + expect.objectContaining({ + id: orderClaim.id, + order_id: createdOrder.id, + return: expect.objectContaining({ + order_id: createdOrder.id, + claim_id: orderClaim.id, + status: "requested", + items: expect.arrayContaining([ + expect.objectContaining({ + item_id: createdOrder.items![1].id, + quantity: 1, + }), + ]), + }), + type: "replace", + additional_items: expect.arrayContaining([ + expect.objectContaining({ + claim_id: orderClaim.id, + is_additional_item: true, + quantity: 1, + item: expect.objectContaining({ + title: "New item", + }), + detail: expect.objectContaining({ + quantity: 1, + }), + }), + expect.objectContaining({ + claim_id: orderClaim.id, + item_id: createdOrder.items![2].id, + is_additional_item: true, + quantity: 1, + item: expect.objectContaining({ + title: "Item 3", + }), + detail: expect.objectContaining({ + quantity: 2, + }), + }), + ]), + claim_items: [ + expect.objectContaining({ + reason: "production_failure", + claim_id: orderClaim.id, + is_additional_item: false, + quantity: 1, + item: expect.objectContaining({ + title: "Item 2", + }), + detail: expect.objectContaining({ + quantity: 2, + }), + }), + ], + shipping_methods: [ + expect.objectContaining({ + name: "return shipping method", + amount: 10, + }), + expect.objectContaining({ + name: "Claim method", + amount: 35, + }), + ], + refund_amount: null, + }) + ) + }) + }) + }, +}) diff --git a/packages/modules/order/integration-tests/__tests__/order-edit.ts b/packages/modules/order/integration-tests/__tests__/order-edit.ts index 024b42188b..f4710bd1ba 100644 --- a/packages/modules/order/integration-tests/__tests__/order-edit.ts +++ b/packages/modules/order/integration-tests/__tests__/order-edit.ts @@ -142,6 +142,7 @@ moduleIntegrationTestRunner({ createdOrder.items![0].unit_price * createdOrder.items![0].quantity, details: { + reference_id: createdOrder.items![0].id, quantity: 1, }, }, @@ -155,6 +156,7 @@ moduleIntegrationTestRunner({ createdOrder.items![1].unit_price * createdOrder.items![1].quantity, details: { + reference_id: createdOrder.items![1].id, quantity: 3, }, }, @@ -351,6 +353,7 @@ moduleIntegrationTestRunner({ createdOrder.items![0].unit_price * createdOrder.items![0].quantity, details: { + reference_id: createdOrder.items![0].id, quantity: 1, }, }, @@ -362,6 +365,7 @@ moduleIntegrationTestRunner({ createdOrder.items![1].unit_price * createdOrder.items![1].quantity, details: { + reference_id: createdOrder.items![1].id, quantity: 3, }, }, @@ -412,9 +416,7 @@ moduleIntegrationTestRunner({ await expect( service.confirmOrderChange(orderChange.id) - ).rejects.toThrowError( - `Order Change cannot be modified: ${orderChange.id}` - ) + ).rejects.toThrow(`Order Change cannot be modified: ${orderChange.id}`) const modified = await service.retrieve(createdOrder.id, { select: [ @@ -570,6 +572,7 @@ moduleIntegrationTestRunner({ createdOrder.items![0].unit_price * createdOrder.items![0].quantity, details: { + reference_id: createdOrder.items![0].id, quantity: 1, }, }, @@ -581,9 +584,9 @@ moduleIntegrationTestRunner({ canceled_by: "cx_agent_123", }) - await expect( - service.cancelOrderChange(orderChange.id) - ).rejects.toThrowError("Order Change cannot be modified") + await expect(service.cancelOrderChange(orderChange.id)).rejects.toThrow( + "Order Change cannot be modified" + ) await service.declineOrderChange({ id: orderChange2.id, @@ -593,7 +596,7 @@ moduleIntegrationTestRunner({ await expect( service.declineOrderChange(orderChange2.id) - ).rejects.toThrowError("Order Change cannot be modified") + ).rejects.toThrow("Order Change cannot be modified") const [change1, change2] = await service.listOrderChanges( { diff --git a/packages/modules/order/integration-tests/__tests__/order-exchange.ts b/packages/modules/order/integration-tests/__tests__/order-exchange.ts new file mode 100644 index 0000000000..d89fc0a612 --- /dev/null +++ b/packages/modules/order/integration-tests/__tests__/order-exchange.ts @@ -0,0 +1,230 @@ +import { Modules } from "@medusajs/modules-sdk" +import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types" +import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" + +jest.setTimeout(100000) + +moduleIntegrationTestRunner({ + moduleName: Modules.ORDER, + testSuite: ({ service }: SuiteOptions) => { + describe("Order Module Service - Exchange flows", () => { + const input = { + email: "foo@bar.com", + items: [ + { + title: "Item 1", + subtitle: "Subtitle 1", + thumbnail: "thumbnail1.jpg", + quantity: 1, + product_id: "product1", + product_title: "Product 1", + product_description: "Description 1", + product_subtitle: "Product Subtitle 1", + product_type: "Type 1", + product_collection: "Collection 1", + product_handle: "handle1", + variant_id: "variant1", + variant_sku: "SKU1", + variant_barcode: "Barcode1", + variant_title: "Variant 1", + variant_option_values: { + color: "Red", + size: "Large", + }, + requires_shipping: true, + is_discountable: true, + is_tax_inclusive: true, + compare_at_unit_price: 10, + unit_price: 8, + tax_lines: [ + { + description: "Tax 1", + tax_rate_id: "tax_usa", + code: "code", + rate: 0.1, + provider_id: "taxify_master", + }, + ], + adjustments: [ + { + code: "VIP_10", + amount: 10, + description: "VIP discount", + promotion_id: "prom_123", + provider_id: "coupon_kings", + }, + ], + }, + { + title: "Item 2", + quantity: 2, + unit_price: 5, + }, + { + title: "Item 3", + quantity: 1, + unit_price: 30, + }, + ], + sales_channel_id: "test", + shipping_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + phone: "12345", + }, + billing_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + }, + shipping_methods: [ + { + name: "Test shipping method", + amount: 10, + }, + ], + transactions: [ + { + amount: 58, + currency_code: "USD", + reference: "payment", + reference_id: "pay_123", + }, + ], + currency_code: "usd", + customer_id: "joe", + } as CreateOrderDTO + + it("should exchange an item and add two new items to the order", async function () { + const createdOrder = await service.create(input) + + // Fullfilment + await service.registerFulfillment({ + order_id: createdOrder.id, + items: createdOrder.items!.map((item) => { + return { + id: item.id, + quantity: item.quantity, + } + }), + }) + + // Shipment + await service.registerShipment({ + order_id: createdOrder.id, + reference: Modules.FULFILLMENT, + items: createdOrder.items!.map((item) => { + return { + id: item.id, + quantity: item.quantity, + } + }), + }) + + // Exchange + const reason = await service.createReturnReasons({ + value: "wrong-size", + label: "Wrong Size", + }) + + const orderExchange = await service.createExchange({ + order_id: createdOrder.id, + description: "Exchange all the items", + internal_note: "user wants to return all items", + difference_due: 14, + shipping_methods: [ + { + name: "Exchange method", + amount: 35, + provider_id: "dhl", + }, + ], + return_items: [ + { + id: createdOrder.items![0].id, + reason_id: reason.id, + quantity: 1, + note: "I don't need this", + }, + ], + additional_items: [ + { + id: createdOrder.items![2].id, + quantity: 1, + }, + { + unit_price: 20, + quantity: 1, + title: "New item", + }, + ], + return_amount: 5, + return_shipping: { + name: "return shipping method", + amount: 10, + provider_id: "test_provider_id", + }, + }) + + expect(orderExchange).toEqual( + expect.objectContaining({ + id: orderExchange.id, + order_id: createdOrder.id, + return: expect.objectContaining({ + order_id: createdOrder.id, + exchange_id: orderExchange.id, + status: "requested", + items: expect.arrayContaining([ + expect.objectContaining({ + item_id: createdOrder.items![0].id, + quantity: 1, + }), + ]), + }), + additional_items: expect.arrayContaining([ + expect.objectContaining({ + exchange_id: orderExchange.id, + quantity: 1, + item: expect.objectContaining({ + title: "New item", + }), + detail: expect.objectContaining({ + quantity: 1, + }), + }), + expect.objectContaining({ + exchange_id: orderExchange.id, + item_id: createdOrder.items![2].id, + quantity: 1, + item: expect.objectContaining({ + title: "Item 3", + }), + detail: expect.objectContaining({ + quantity: 2, + }), + }), + ]), + shipping_methods: [ + expect.objectContaining({ + name: "return shipping method", + amount: 10, + }), + expect.objectContaining({ + name: "Exchange method", + amount: 35, + }), + ], + difference_due: 14, + }) + ) + }) + }) + }, +}) diff --git a/packages/modules/order/integration-tests/__tests__/order-return.ts b/packages/modules/order/integration-tests/__tests__/order-return.ts index 81c13fc8e6..6ae43a13be 100644 --- a/packages/modules/order/integration-tests/__tests__/order-return.ts +++ b/packages/modules/order/integration-tests/__tests__/order-return.ts @@ -2,7 +2,7 @@ import { Modules } from "@medusajs/modules-sdk" import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types" import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils" -jest.setTimeout(100000) +jest.setTimeout(1000000) moduleIntegrationTestRunner({ moduleName: Modules.ORDER, @@ -135,12 +135,10 @@ moduleIntegrationTestRunner({ expect(serializedOrder).toEqual( expect.objectContaining({ - version: 2, items: [ expect.objectContaining({ quantity: 1, detail: expect.objectContaining({ - version: 2, quantity: 1, fulfilled_quantity: 1, shipped_quantity: 0, @@ -149,7 +147,6 @@ moduleIntegrationTestRunner({ expect.objectContaining({ quantity: 2, detail: expect.objectContaining({ - version: 2, quantity: 2, fulfilled_quantity: 2, shipped_quantity: 0, @@ -158,7 +155,6 @@ moduleIntegrationTestRunner({ expect.objectContaining({ quantity: 1, detail: expect.objectContaining({ - version: 2, quantity: 1, fulfilled_quantity: 1, shipped_quantity: 0, @@ -199,12 +195,10 @@ moduleIntegrationTestRunner({ expect(serializedOrder).toEqual( expect.objectContaining({ - version: 3, items: [ expect.objectContaining({ quantity: 1, detail: expect.objectContaining({ - version: 3, quantity: 1, fulfilled_quantity: 1, shipped_quantity: 1, @@ -213,7 +207,6 @@ moduleIntegrationTestRunner({ expect.objectContaining({ quantity: 2, detail: expect.objectContaining({ - version: 3, quantity: 2, fulfilled_quantity: 2, shipped_quantity: 2, @@ -222,7 +215,6 @@ moduleIntegrationTestRunner({ expect.objectContaining({ quantity: 1, detail: expect.objectContaining({ - version: 3, quantity: 1, fulfilled_quantity: 1, shipped_quantity: 1, @@ -233,6 +225,15 @@ moduleIntegrationTestRunner({ ) // Return + const reason = await service.createReturnReasons({ + value: "wrong-size", + label: "Wrong Size", + }) + const reason2 = await service.createReturnReasons({ + value: "disliked", + label: "Disliled", + }) + const orderReturn = await service.createReturn({ order_id: createdOrder.id, reference: Modules.FULFILLMENT, @@ -245,11 +246,29 @@ moduleIntegrationTestRunner({ items: createdOrder.items!.map((item) => { return { id: item.id, - quantity: item.quantity, + quantity: 1, + reason_id: reason.id, } }), }) + const secondReturn = await service.createReturn({ + order_id: createdOrder.id, + reference: Modules.FULFILLMENT, + description: "Return remaining item", + shipping_method: { + name: "Return method", + amount: 5, + }, + items: [ + { + id: createdOrder.items![1].id, + quantity: 1, + reason_id: reason2.id, + }, + ], + }) + getOrder = await service.retrieve(createdOrder.id, { select: [ "id", @@ -270,12 +289,10 @@ moduleIntegrationTestRunner({ expect(serializedOrder).toEqual( expect.objectContaining({ - version: 4, items: [ expect.objectContaining({ quantity: 1, detail: expect.objectContaining({ - version: 4, quantity: 1, fulfilled_quantity: 1, shipped_quantity: 1, @@ -285,7 +302,6 @@ moduleIntegrationTestRunner({ expect.objectContaining({ quantity: 2, detail: expect.objectContaining({ - version: 4, quantity: 2, fulfilled_quantity: 2, shipped_quantity: 2, @@ -295,7 +311,6 @@ moduleIntegrationTestRunner({ expect.objectContaining({ quantity: 1, detail: expect.objectContaining({ - version: 4, quantity: 1, fulfilled_quantity: 1, shipped_quantity: 1, @@ -310,10 +325,11 @@ moduleIntegrationTestRunner({ const allItems = createdOrder.items!.map((item) => { return { id: item.id, - quantity: item.quantity, + quantity: 1, } }) const lastItem = allItems.pop()! + const receive = await service.receiveReturn({ return_id: orderReturn.id, internal_note: "received some items", @@ -326,6 +342,30 @@ moduleIntegrationTestRunner({ items: [lastItem], }) + const receiveSecond = await service.receiveReturn({ + return_id: secondReturn.id, + internal_note: "received some items", + items: [ + { + id: createdOrder.items![1].id, + quantity: 1, + }, + ], + }) + + expect(receiveSecond).toEqual( + expect.objectContaining({ + status: "received", + received_at: expect.any(Date), + items: expect.arrayContaining([ + expect.objectContaining({ + item_id: createdOrder.items![1].id, + received_quantity: 1, + }), + ]), + }) + ) + expect(receive).toEqual( expect.objectContaining({ id: orderReturn.id, @@ -333,17 +373,17 @@ moduleIntegrationTestRunner({ received_at: null, items: expect.arrayContaining([ expect.objectContaining({ - id: allItems[0].id, + item_id: allItems[0].id, detail: expect.objectContaining({ return_requested_quantity: 0, return_received_quantity: 1, }), }), expect.objectContaining({ - id: allItems[1].id, + item_id: allItems[1].id, detail: expect.objectContaining({ - return_requested_quantity: 0, - return_received_quantity: 2, + return_requested_quantity: 1, + return_received_quantity: 1, }), }), ]), @@ -357,21 +397,21 @@ moduleIntegrationTestRunner({ received_at: expect.any(Date), items: expect.arrayContaining([ expect.objectContaining({ - id: allItems[0].id, + item_id: allItems[0].id, detail: expect.objectContaining({ return_requested_quantity: 0, return_received_quantity: 1, }), }), expect.objectContaining({ - id: allItems[1].id, + item_id: allItems[1].id, detail: expect.objectContaining({ - return_requested_quantity: 0, - return_received_quantity: 2, + return_requested_quantity: 1, + return_received_quantity: 1, }), }), expect.objectContaining({ - id: lastItem.id, + item_id: lastItem.id, detail: expect.objectContaining({ return_requested_quantity: 0, return_received_quantity: 1, @@ -402,12 +442,10 @@ moduleIntegrationTestRunner({ 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, @@ -418,7 +456,6 @@ moduleIntegrationTestRunner({ expect.objectContaining({ quantity: 2, detail: expect.objectContaining({ - version: 6, quantity: 2, fulfilled_quantity: 2, shipped_quantity: 2, @@ -429,7 +466,6 @@ moduleIntegrationTestRunner({ expect.objectContaining({ quantity: 1, detail: expect.objectContaining({ - version: 6, quantity: 1, fulfilled_quantity: 1, shipped_quantity: 1, diff --git a/packages/modules/order/src/migrations/Migration20240604100512.ts b/packages/modules/order/src/migrations/Migration20240604100512.ts index 3d4505980c..5f32bf68bb 100644 --- a/packages/modules/order/src/migrations/Migration20240604100512.ts +++ b/packages/modules/order/src/migrations/Migration20240604100512.ts @@ -19,31 +19,6 @@ export class Migration20240604100512 extends Migration { - 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; @@ -205,6 +180,36 @@ export class Migration20240604100512 extends Migration { WHERE deleted_at IS NOT NULL; + CREATE TABLE IF NOT EXISTS "return_item" ( + "id" TEXT NOT NULL, + "return_id" TEXT NOT NULL, + "reason_id" TEXT NULL, + "item_id" TEXT NOT NULL, + "quantity" NUMERIC NOT NULL, + "raw_quantity" JSONB NOT NULL, + "received_quantity" NUMERIC NOT NULL DEFAULT 0, + "raw_received_quantity" JSONB NOT NULL, + "note" TEXT NULL, + "metadata" JSONB NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + "deleted_at" timestamptz NULL, + CONSTRAINT "return_item_pkey" PRIMARY KEY ("id") + ); + + CREATE INDEX IF NOT EXISTS "IDX_return_item_deleted_at" ON "return_item" ("deleted_at") + WHERE deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_return_item_return_id" ON "return_item" ("return_id") + WHERE deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_return_item_item_id" ON "return_item" ("item_id") + WHERE deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_return_item_reason_id" ON "return_item" ("reason_id") + WHERE deleted_at IS NOT NULL; + + CREATE TABLE IF NOT EXISTS "order_exchange" ( "id" TEXT NOT NULL, @@ -213,8 +218,6 @@ export class Migration20240604100512 extends Migration { "order_version" INTEGER NOT NULL, "display_id" SERIAL, "no_notification" BOOLEAN NULL, - "refund_amount" NUMERIC NULL, - "raw_refund_amount" JSONB NULL, "allow_backorder" BOOLEAN NOT NULL DEFAULT FALSE, "difference_due" NUMERIC NULL, "raw_difference_due" JSONB NULL, @@ -239,6 +242,30 @@ export class Migration20240604100512 extends Migration { WHERE return_id IS NOT NULL AND deleted_at IS NOT NULL; + CREATE TABLE IF NOT EXISTS "order_exchange_item" ( + "id" TEXT NOT NULL, + "exchange_id" TEXT NOT NULL, + "item_id" TEXT NOT NULL, + "quantity" NUMERIC NOT NULL, + "raw_quantity" JSONB NOT NULL, + "note" TEXT NULL, + "metadata" JSONB NULL, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + "deleted_at" timestamptz NULL, + CONSTRAINT "order_exchange_item_pkey" PRIMARY KEY ("id") + ); + + CREATE INDEX IF NOT EXISTS "IDX_order_exchange_item_deleted_at" ON "order_exchange_item" ("deleted_at") + WHERE deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_exchange_item_exchange_id" ON "order_exchange_item" ("exchange_id") + WHERE deleted_at IS NOT NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_exchange_item_item_id" ON "order_exchange_item" ("item_id") + WHERE deleted_at IS NOT NULL; + + CREATE TYPE order_claim_type_enum AS ENUM ( 'refund', @@ -288,7 +315,10 @@ export class Migration20240604100512 extends Migration { "id" TEXT NOT NULL, "claim_id" TEXT NOT NULL, "item_id" TEXT NOT NULL, - "reason" claim_reason_enum NOT NULL, + "is_additional_item" BOOLEAN NOT NULL DEFAULT FALSE, + "reason" claim_reason_enum NULL, + "quantity" NUMERIC NOT NULL, + "raw_quantity" JSONB NOT NULL, "note" TEXT NULL, "metadata" JSONB NULL, "created_at" timestamptz NOT NULL DEFAULT now(), diff --git a/packages/modules/order/src/models/claim-item-image.ts b/packages/modules/order/src/models/claim-item-image.ts index 2047627f50..b99237eb60 100644 --- a/packages/modules/order/src/models/claim-item-image.ts +++ b/packages/modules/order/src/models/claim-item-image.ts @@ -76,11 +76,13 @@ export default class ClaimItemImage { @BeforeCreate() onCreate() { - this.id = generateEntityId(this.id, "ordclaimimg") + this.id = generateEntityId(this.id, "climg") + this.claim_item_id = this.item?.id } @OnInit() onInit() { - this.id = generateEntityId(this.id, "ordclaimimg") + this.id = generateEntityId(this.id, "climg") + this.claim_item_id = this.item?.id } } diff --git a/packages/modules/order/src/models/claim-item.ts b/packages/modules/order/src/models/claim-item.ts index 1e09c563bc..77082364ce 100644 --- a/packages/modules/order/src/models/claim-item.ts +++ b/packages/modules/order/src/models/claim-item.ts @@ -1,6 +1,7 @@ -import { DAL } from "@medusajs/types" +import { BigNumberRawValue, DAL } from "@medusajs/types" import { ClaimReason, + MikroOrmBigNumberProperty, createPsqlIndexStatementHelper, generateEntityId, } from "@medusajs/utils" @@ -26,11 +27,19 @@ type OptionalLineItemProps = DAL.EntityDateColumns const ClaimIdIndex = createPsqlIndexStatementHelper({ tableName: "order_claim_item", columns: "claim_id", + where: "deleted_at IS NOT NULL", }) const ItemIdIndex = createPsqlIndexStatementHelper({ tableName: "order_claim_item", columns: "item_id", + where: "deleted_at IS NOT NULL", +}) + +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order_claim_item_image", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", }) @Entity({ tableName: "order_claim_item" }) @@ -45,8 +54,14 @@ export default class OrderClaimItem { }) images = new Collection(this) - @Enum({ items: () => ClaimReason }) - reason: ClaimReason + @Enum({ items: () => ClaimReason, nullable: true }) + reason: ClaimReason | null = null + + @MikroOrmBigNumberProperty() + quantity: Number | number + + @Property({ columnType: "jsonb" }) + raw_quantity: BigNumberRawValue @ManyToOne(() => Claim, { columnType: "text", @@ -76,6 +91,9 @@ export default class OrderClaimItem { }) item: LineItem + @Property({ columnType: "boolean", default: false }) + is_additional_item: boolean = false + @Property({ columnType: "text", nullable: true }) note: string @@ -97,13 +115,19 @@ export default class OrderClaimItem { }) updated_at: Date + @Property({ columnType: "timestamptz", nullable: true }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + @BeforeCreate() onCreate() { - this.id = generateEntityId(this.id, "ordclaimitem") + this.id = generateEntityId(this.id, "claitem") + this.claim_id = this.claim?.id } @OnInit() onInit() { - this.id = generateEntityId(this.id, "ordclaimitem") + this.id = generateEntityId(this.id, "claitem") + this.claim_id = this.claim?.id } } diff --git a/packages/modules/order/src/models/claim.ts b/packages/modules/order/src/models/claim.ts index 3bdc122b78..d477c81488 100644 --- a/packages/modules/order/src/models/claim.ts +++ b/packages/modules/order/src/models/claim.ts @@ -22,9 +22,9 @@ import { } from "@mikro-orm/core" import ClaimItem from "./claim-item" import Order from "./order" -import OrderItem from "./order-item" import OrderShippingMethod from "./order-shipping-method" import Return from "./return" +import Transaction from "./transaction" type OptionalOrderClaimProps = DAL.EntityDateColumns @@ -110,12 +110,12 @@ export default class OrderClaim { @Property({ columnType: "jsonb", nullable: true }) raw_refund_amount: BigNumberRawValue - @OneToMany(() => OrderItem, (itemDetail) => itemDetail.claim, { + @OneToMany(() => ClaimItem, (item) => item.claim, { cascade: [Cascade.PERSIST], }) - items = new Collection(this) + additional_items = new Collection(this) - @OneToMany(() => ClaimItem, (itemDetail) => itemDetail.claim, { + @OneToMany(() => ClaimItem, (item) => item.claim, { cascade: [Cascade.PERSIST], }) claim_items = new Collection(this) @@ -129,6 +129,11 @@ export default class OrderClaim { ) shipping_methods = new Collection(this) + @OneToMany(() => Transaction, (transaction) => transaction.claim, { + cascade: [Cascade.PERSIST], + }) + transactions = new Collection(this) + @Property({ columnType: "jsonb", nullable: true }) metadata: Record | null = null @@ -156,11 +161,11 @@ export default class OrderClaim { @BeforeCreate() onCreate() { - this.id = generateEntityId(this.id, "ordclaim") + this.id = generateEntityId(this.id, "claim") } @OnInit() onInit() { - this.id = generateEntityId(this.id, "ordclaim") + this.id = generateEntityId(this.id, "claim") } } diff --git a/packages/modules/order/src/models/exchange-item.ts b/packages/modules/order/src/models/exchange-item.ts new file mode 100644 index 0000000000..683c0febdb --- /dev/null +++ b/packages/modules/order/src/models/exchange-item.ts @@ -0,0 +1,116 @@ +import { BigNumberRawValue, DAL } from "@medusajs/types" +import { + MikroOrmBigNumberProperty, + createPsqlIndexStatementHelper, + generateEntityId, +} from "@medusajs/utils" +import { + BeforeCreate, + Entity, + ManyToOne, + OnInit, + OptionalProps, + PrimaryKey, + Property, +} from "@mikro-orm/core" +import Exchange from "./exchange" +import LineItem from "./line-item" + +type OptionalLineItemProps = DAL.EntityDateColumns + +const ExchangeIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_exchange_item", + columns: "exchange_id", + where: "deleted_at IS NOT NULL", +}) + +const ItemIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_exchange_item", + columns: "item_id", + where: "deleted_at IS NOT NULL", +}) + +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order_claim_item_image", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", +}) + +@Entity({ tableName: "order_exchange_item" }) +export default class OrderExchangeItem { + [OptionalProps]?: OptionalLineItemProps + + @PrimaryKey({ columnType: "text" }) + id: string + + @MikroOrmBigNumberProperty() + quantity: Number | number + + @Property({ columnType: "jsonb" }) + raw_quantity: BigNumberRawValue + + @ManyToOne(() => Exchange, { + columnType: "text", + fieldName: "exchange_id", + mapToPk: true, + onDelete: "cascade", + }) + @ExchangeIdIndex.MikroORMIndex() + exchange_id: string + + @ManyToOne(() => Exchange, { + persist: false, + }) + exchange: Exchange + + @ManyToOne({ + entity: () => LineItem, + fieldName: "item_id", + mapToPk: true, + columnType: "text", + }) + @ItemIdIndex.MikroORMIndex() + item_id: string + + @ManyToOne(() => LineItem, { + persist: false, + }) + item: LineItem + + @Property({ columnType: "text", nullable: true }) + note: string + + @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 }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + + @BeforeCreate() + onCreate() { + this.id = generateEntityId(this.id, "oexcitem") + this.exchange_id = this.exchange?.id + } + + @OnInit() + onInit() { + this.id = generateEntityId(this.id, "oexcitem") + this.exchange_id = this.exchange?.id + } +} diff --git a/packages/modules/order/src/models/exchange.ts b/packages/modules/order/src/models/exchange.ts index ba0499af26..08183de619 100644 --- a/packages/modules/order/src/models/exchange.ts +++ b/packages/modules/order/src/models/exchange.ts @@ -18,8 +18,8 @@ import { PrimaryKey, Property, } from "@mikro-orm/core" +import { ExchangeItem, Transaction } from "@models" import Order from "./order" -import OrderItem from "./order-item" import OrderShippingMethod from "./order-shipping-method" import Return from "./return" @@ -96,14 +96,6 @@ export default class OrderExchange { @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 - @MikroOrmBigNumberProperty({ nullable: true, }) @@ -115,10 +107,10 @@ export default class OrderExchange { @Property({ columnType: "boolean", default: false }) allow_backorder: boolean = false - @OneToMany(() => OrderItem, (itemDetail) => itemDetail.exchange, { + @OneToMany(() => ExchangeItem, (item) => item.exchange, { cascade: [Cascade.PERSIST], }) - items = new Collection(this) + additional_items = new Collection(this) @OneToMany( () => OrderShippingMethod, @@ -129,6 +121,11 @@ export default class OrderExchange { ) shipping_methods = new Collection(this) + @OneToMany(() => Transaction, (transaction) => transaction.exchange, { + cascade: [Cascade.PERSIST], + }) + transactions = new Collection(this) + @Property({ columnType: "jsonb", nullable: true }) metadata: Record | null = null @@ -156,11 +153,11 @@ export default class OrderExchange { @BeforeCreate() onCreate() { - this.id = generateEntityId(this.id, "ordexchange") + this.id = generateEntityId(this.id, "oexc") } @OnInit() onInit() { - this.id = generateEntityId(this.id, "ordexchange") + this.id = generateEntityId(this.id, "oexc") } } diff --git a/packages/modules/order/src/models/index.ts b/packages/modules/order/src/models/index.ts index c9a09a74bf..08866770ee 100644 --- a/packages/modules/order/src/models/index.ts +++ b/packages/modules/order/src/models/index.ts @@ -1,8 +1,9 @@ export { default as Address } from "./address" -export { default as Claim } from "./claim" +export { default as OrderClaim } from "./claim" export { default as ClaimItem } from "./claim-item" export { default as ClaimItemImage } from "./claim-item-image" -export { default as Exchange } from "./exchange" +export { default as OrderExchange } from "./exchange" +export { default as ExchangeItem } from "./exchange-item" export { default as LineItem } from "./line-item" export { default as LineItemAdjustment } from "./line-item-adjustment" export { default as LineItemTaxLine } from "./line-item-tax-line" @@ -13,6 +14,7 @@ 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 ReturnItem } from "./return-item" 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 53dda0ad16..0aa7239cf1 100644 --- a/packages/modules/order/src/models/order-change-action.ts +++ b/packages/modules/order/src/models/order-change-action.ts @@ -14,6 +14,8 @@ import { PrimaryKey, Property, } from "@mikro-orm/core" +import OrderClaim from "./claim" +import OrderExchange from "./exchange" import Order from "./order" import OrderChange from "./order-change" import Return from "./return" @@ -38,6 +40,18 @@ const ReturnIdIndex = createPsqlIndexStatementHelper({ where: "return_id IS NOT NULL AND deleted_at IS NOT NULL", }) +const OrderClaimIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_change_action", + columns: "claim_id", + where: "claim_id IS NOT NULL AND deleted_at IS NOT NULL", +}) + +const OrderExchangeIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_change_action", + columns: "exchange_id", + where: "exchange_id IS NOT NULL AND deleted_at IS NOT NULL", +}) + const DeletedAtIndex = createPsqlIndexStatementHelper({ tableName: "order_change_action", columns: "deleted_at", @@ -93,6 +107,36 @@ export default class OrderChangeAction { }) return: Return + @ManyToOne({ + entity: () => OrderClaim, + mapToPk: true, + fieldName: "claim_id", + columnType: "text", + nullable: true, + }) + @OrderClaimIdIndex.MikroORMIndex() + claim_id: string | null = null + + @ManyToOne(() => OrderClaim, { + persist: false, + }) + claim: OrderClaim + + @ManyToOne({ + entity: () => OrderExchange, + mapToPk: true, + fieldName: "exchange_id", + columnType: "text", + nullable: true, + }) + @OrderExchangeIdIndex.MikroORMIndex() + exchange_id: string | null = null + + @ManyToOne(() => OrderExchange, { + persist: false, + }) + exchange: OrderExchange + @Property({ columnType: "integer", nullable: true }) version: number | null = null @@ -172,6 +216,10 @@ export default class OrderChangeAction { onCreate() { this.id = generateEntityId(this.id, "ordchact") this.order_id ??= this.order?.id ?? this.order_change?.order_id ?? null + this.return_id ??= this.return?.id ?? this.order_change?.return_id ?? null + this.claim_id ??= this.claim?.id ?? this.order_change?.claim_id ?? null + this.exchange_id ??= + this.exchange?.id ?? this.order_change?.exchange_id ?? null this.order_change_id ??= this.order_change?.id ?? null this.version ??= this.order_change?.version ?? null } @@ -180,6 +228,10 @@ export default class OrderChangeAction { onInit() { this.id = generateEntityId(this.id, "ordchact") this.order_id ??= this.order?.id ?? this.order_change?.order_id ?? null + this.return_id ??= this.return?.id ?? this.order_change?.return_id ?? null + this.claim_id ??= this.claim?.id ?? this.order_change?.claim_id ?? null + this.exchange_id ??= + this.exchange?.id ?? this.order_change?.exchange_id ?? null this.order_change_id ??= this.order_change?.id ?? null this.version ??= this.order_change?.version ?? null } diff --git a/packages/modules/order/src/models/order-change.ts b/packages/modules/order/src/models/order-change.ts index 8c68a48929..ea6efe8330 100644 --- a/packages/modules/order/src/models/order-change.ts +++ b/packages/modules/order/src/models/order-change.ts @@ -17,6 +17,8 @@ import { Property, } from "@mikro-orm/core" import { OrderChangeStatus, OrderChangeType } from "@types" +import OrderClaim from "./claim" +import OrderExchange from "./exchange" import Order from "./order" import OrderChangeAction from "./order-change-action" import Return from "./return" @@ -35,6 +37,18 @@ const ReturnIdIndex = createPsqlIndexStatementHelper({ where: "return_id IS NOT NULL AND deleted_at IS NOT NULL", }) +const OrderClaimIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_change", + columns: "claim_id", + where: "claim_id IS NOT NULL AND deleted_at IS NOT NULL", +}) + +const OrderExchangeIdIndex = createPsqlIndexStatementHelper({ + tableName: "order_change", + columns: "exchange_id", + where: "exchange_id IS NOT NULL AND deleted_at IS NOT NULL", +}) + const OrderChangeStatusIndex = createPsqlIndexStatementHelper({ tableName: "order_change", columns: "status", @@ -97,6 +111,36 @@ export default class OrderChange { }) return: Return + @ManyToOne({ + entity: () => OrderClaim, + mapToPk: true, + fieldName: "claim_id", + columnType: "text", + nullable: true, + }) + @OrderClaimIdIndex.MikroORMIndex() + claim_id: string | null = null + + @ManyToOne(() => OrderClaim, { + persist: false, + }) + claim: OrderClaim + + @ManyToOne({ + entity: () => OrderExchange, + mapToPk: true, + fieldName: "exchange_id", + columnType: "text", + nullable: true, + }) + @OrderExchangeIdIndex.MikroORMIndex() + exchange_id: string | null = null + + @ManyToOne(() => OrderExchange, { + persist: false, + }) + exchange: OrderExchange + @Property({ columnType: "integer" }) @VersionIndex.MikroORMIndex() version: number @@ -191,11 +235,17 @@ export default class OrderChange { onCreate() { this.id = generateEntityId(this.id, "ordch") this.order_id ??= this.order?.id + this.return_id ??= this.return?.id + this.claim_id ??= this.claim?.id + this.exchange_id ??= this.exchange?.id } @OnInit() onInit() { this.id = generateEntityId(this.id, "ordch") this.order_id ??= this.order?.id + this.return_id ??= this.return?.id + this.claim_id ??= this.claim?.id + this.exchange_id ??= this.exchange?.id } } diff --git a/packages/modules/order/src/models/order-item.ts b/packages/modules/order/src/models/order-item.ts index d9c8dbb8c6..59b52d5182 100644 --- a/packages/modules/order/src/models/order-item.ts +++ b/packages/modules/order/src/models/order-item.ts @@ -14,11 +14,8 @@ import { PrimaryKey, Property, } from "@mikro-orm/core" -import Claim from "./claim" -import Exchange from "./exchange" import LineItem from "./line-item" import Order from "./order" -import Return from "./return" type OptionalLineItemProps = DAL.EntityDateColumns @@ -28,24 +25,6 @@ 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 ExchangeIdIndex = createPsqlIndexStatementHelper({ - tableName: "order_item", - columns: ["exchange_id"], - where: "exchange_id IS NOT NULL AND deleted_at IS NOT NULL", -}) - -const ClaimIdIndex = createPsqlIndexStatementHelper({ - tableName: "order_item", - columns: ["claim_id"], - where: "claim_id IS NOT NULL AND deleted_at IS NOT NULL", -}) - const OrderVersionIndex = createPsqlIndexStatementHelper({ tableName: "order_item", columns: ["version"], @@ -85,51 +64,6 @@ export default class OrderItem { }) 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 - - @ManyToOne({ - entity: () => Exchange, - mapToPk: true, - fieldName: "exchange_id", - columnType: "text", - nullable: true, - }) - @ExchangeIdIndex.MikroORMIndex() - exchange_id: string | null - - @ManyToOne(() => Exchange, { - persist: false, - }) - exchange: Exchange - - @ManyToOne({ - entity: () => Claim, - mapToPk: true, - fieldName: "claim_id", - columnType: "text", - nullable: true, - }) - @ClaimIdIndex.MikroORMIndex() - claim_id: string | null - - @ManyToOne(() => Claim, { - persist: false, - }) - claim: Claim - @Property({ columnType: "integer" }) @OrderVersionIndex.MikroORMIndex() version: number @@ -216,9 +150,6 @@ export default class OrderItem { onCreate() { this.id = generateEntityId(this.id, "orditem") this.order_id ??= this.order?.id - this.return_id ??= this.return?.id - this.exchange_id ??= this.exchange?.id - this.claim_id ??= this.claim?.id this.item_id ??= this.item?.id this.version ??= this.order?.version } @@ -227,9 +158,6 @@ export default class OrderItem { onInit() { this.id = generateEntityId(this.id, "orditem") this.order_id ??= this.order?.id - this.return_id ??= this.return?.id - this.exchange_id ??= this.exchange?.id - this.claim_id ??= this.claim?.id this.item_id ??= this.item?.id this.version ??= this.order?.version } diff --git a/packages/modules/order/src/models/order-shipping-method.ts b/packages/modules/order/src/models/order-shipping-method.ts index f22fd99d70..5e4edec3f5 100644 --- a/packages/modules/order/src/models/order-shipping-method.ts +++ b/packages/modules/order/src/models/order-shipping-method.ts @@ -170,6 +170,7 @@ export default class OrderShippingMethod { this.id = generateEntityId(this.id, "ordspmv") this.order_id ??= this.order?.id this.return_id ??= this.return?.id + this.claim_id ??= this.claim?.id this.exchange_id ??= this.exchange?.id this.shipping_method_id ??= this.shipping_method?.id this.version ??= this.order?.version @@ -180,6 +181,7 @@ export default class OrderShippingMethod { this.id = generateEntityId(this.id, "ordspmv") this.order_id ??= this.order?.id this.return_id ??= this.return?.id + this.claim_id ??= this.claim?.id this.exchange_id ??= this.exchange?.id this.shipping_method_id ??= this.shipping_method?.id this.version ??= this.order?.version diff --git a/packages/modules/order/src/models/return-item.ts b/packages/modules/order/src/models/return-item.ts new file mode 100644 index 0000000000..f6b53c9d4d --- /dev/null +++ b/packages/modules/order/src/models/return-item.ts @@ -0,0 +1,143 @@ +import { BigNumberRawValue, DAL } from "@medusajs/types" +import { + MikroOrmBigNumberProperty, + createPsqlIndexStatementHelper, + generateEntityId, +} from "@medusajs/utils" +import { + BeforeCreate, + Entity, + ManyToOne, + OnInit, + OptionalProps, + PrimaryKey, + Property, +} from "@mikro-orm/core" +import LineItem from "./line-item" +import Return from "./return" +import ReturnReason from "./return-reason" + +type OptionalLineItemProps = DAL.EntityDateColumns + +const ReturnIdIndex = createPsqlIndexStatementHelper({ + tableName: "return_item", + columns: "return_id", + where: "deleted_at IS NOT NULL", +}) + +const ReturnReasonIdIndex = createPsqlIndexStatementHelper({ + tableName: "return_item", + columns: "reason_id", + where: "deleted_at IS NOT NULL", +}) + +const ItemIdIndex = createPsqlIndexStatementHelper({ + tableName: "return_item", + columns: "item_id", + where: "deleted_at IS NOT NULL", +}) + +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order_claim_item_image", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", +}) + +@Entity({ tableName: "return_item" }) +export default class ReturnItem { + [OptionalProps]?: OptionalLineItemProps + + @PrimaryKey({ columnType: "text" }) + id: string + + @ManyToOne(() => ReturnReason, { + columnType: "text", + fieldName: "reason_id", + mapToPk: true, + nullable: true, + }) + @ReturnReasonIdIndex.MikroORMIndex() + reason_id: string | null = null + + @ManyToOne(() => ReturnReason, { + persist: false, + }) + reason: ReturnReason + + @MikroOrmBigNumberProperty() + quantity: Number | number + + @Property({ columnType: "jsonb" }) + raw_quantity: BigNumberRawValue + + @MikroOrmBigNumberProperty() + received_quantity: Number | number = 0 + + @Property({ columnType: "jsonb" }) + raw_received_quantity: BigNumberRawValue + + @ManyToOne(() => Return, { + columnType: "text", + fieldName: "return_id", + mapToPk: true, + onDelete: "cascade", + }) + @ReturnIdIndex.MikroORMIndex() + return_id: string + + @ManyToOne(() => Return, { + persist: false, + }) + return: Return + + @ManyToOne({ + entity: () => LineItem, + fieldName: "item_id", + mapToPk: true, + columnType: "text", + }) + @ItemIdIndex.MikroORMIndex() + item_id: string + + @ManyToOne(() => LineItem, { + persist: false, + }) + item: LineItem + + @Property({ columnType: "text", nullable: true }) + note: string + + @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 }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + + @BeforeCreate() + onCreate() { + this.id = generateEntityId(this.id, "retitem") + this.return_id = this.return?.id + } + + @OnInit() + onInit() { + this.id = generateEntityId(this.id, "retitem") + this.return_id = this.return?.id + } +} diff --git a/packages/modules/order/src/models/return.ts b/packages/modules/order/src/models/return.ts index 12a1da507f..4154364d8b 100644 --- a/packages/modules/order/src/models/return.ts +++ b/packages/modules/order/src/models/return.ts @@ -20,6 +20,7 @@ import { PrimaryKey, Property, } from "@mikro-orm/core" +import { ReturnItem, Transaction } from "@models" import Claim from "./claim" import Exchange from "./exchange" import Order from "./order" @@ -126,7 +127,7 @@ export default class Return { @Property({ columnType: "jsonb", nullable: true }) raw_refund_amount: BigNumberRawValue - @OneToMany(() => OrderItem, (itemDetail) => itemDetail.return, { + @OneToMany(() => ReturnItem, (itemDetail) => itemDetail.return, { cascade: [Cascade.PERSIST], }) items = new Collection(this) @@ -140,6 +141,11 @@ export default class Return { ) shipping_methods = new Collection(this) + @OneToMany(() => Transaction, (transaction) => transaction.return, { + cascade: [Cascade.PERSIST], + }) + transactions = new Collection(this) + @Property({ columnType: "jsonb", nullable: true }) metadata: Record | null = null diff --git a/packages/modules/order/src/models/transaction.ts b/packages/modules/order/src/models/transaction.ts index 63191ac1d1..de00e4ff8e 100644 --- a/packages/modules/order/src/models/transaction.ts +++ b/packages/modules/order/src/models/transaction.ts @@ -189,11 +189,17 @@ export default class Transaction { onCreate() { this.id = generateEntityId(this.id, "ordtrx") this.order_id ??= this.order?.id + this.return_id ??= this.return?.id + this.claim_id ??= this.claim?.id + this.exchange_id ??= this.exchange?.id } @OnInit() onInit() { this.id = generateEntityId(this.id, "ordtrx") this.order_id ??= this.order?.id + this.return_id ??= this.return?.id + this.claim_id ??= this.claim?.id + this.exchange_id ??= this.exchange?.id } } diff --git a/packages/modules/order/src/repositories/claim.ts b/packages/modules/order/src/repositories/claim.ts new file mode 100644 index 0000000000..8eef6098e4 --- /dev/null +++ b/packages/modules/order/src/repositories/claim.ts @@ -0,0 +1,9 @@ +import { DALUtils } from "@medusajs/utils" +import { OrderClaim } from "@models" +import { setFindMethods } from "../utils/base-repository-find" + +export class OrderClaimRepository extends DALUtils.mikroOrmBaseRepositoryFactory( + OrderClaim +) {} + +setFindMethods(OrderClaimRepository, OrderClaim) diff --git a/packages/modules/order/src/repositories/exchange.ts b/packages/modules/order/src/repositories/exchange.ts new file mode 100644 index 0000000000..63188310fc --- /dev/null +++ b/packages/modules/order/src/repositories/exchange.ts @@ -0,0 +1,9 @@ +import { DALUtils } from "@medusajs/utils" +import { OrderExchange } from "@models" +import { setFindMethods } from "../utils/base-repository-find" + +export class OrderExchangeRepository extends DALUtils.mikroOrmBaseRepositoryFactory( + OrderExchange +) {} + +setFindMethods(OrderExchangeRepository, OrderExchange) diff --git a/packages/modules/order/src/repositories/index.ts b/packages/modules/order/src/repositories/index.ts index b95f89c6e1..a140f601b9 100644 --- a/packages/modules/order/src/repositories/index.ts +++ b/packages/modules/order/src/repositories/index.ts @@ -1,3 +1,4 @@ export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils" +export { OrderClaimRepository } from "./claim" export { OrderRepository } from "./order" export { ReturnRepository } from "./return" 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 afb6418fa3..2d7dc17507 100644 --- a/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts +++ b/packages/modules/order/src/services/__tests__/util/actions/exchanges.ts @@ -71,8 +71,8 @@ describe("Order Exchange - Actions", function () { }, { action: ChangeActionType.ITEM_ADD, - reference_id: "item_555", details: { + reference_id: "item_555", unit_price: 50, quantity: 1, }, diff --git a/packages/modules/order/src/services/actions/create-claim.ts b/packages/modules/order/src/services/actions/create-claim.ts new file mode 100644 index 0000000000..f338dd7b3e --- /dev/null +++ b/packages/modules/order/src/services/actions/create-claim.ts @@ -0,0 +1,365 @@ +import { + Context, + CreateOrderChangeActionDTO, + OrderTypes, +} from "@medusajs/types" +import { + ClaimType, + ReturnStatus, + getShippingMethodsTotals, + isString, + promiseAll, +} from "@medusajs/utils" +import { ClaimItem, OrderClaim, Return, ReturnItem } from "@models" +import { OrderChangeType } from "@types" +import { ChangeActionType } from "../../utils" + +function createClaimAndReturnEntities(em, data, order) { + const claimReference = em.create(OrderClaim, { + order_id: data.order_id, + order_version: order.version, + type: data.type as ClaimType, + no_notification: data.no_notification, + refund_amount: (data.refund_amount as unknown) ?? null, + }) + + const returnReference = + data.type === ClaimType.REPLACE + ? em.create(Return, { + order_id: data.order_id, + order_version: order.version, + status: ReturnStatus.REQUESTED, + claim_id: claimReference.id, + refund_amount: (data.refund_amount as unknown) ?? null, + }) + : undefined + + claimReference.return_id = returnReference?.id + + return { claimReference, returnReference } +} + +function createReturnItem(em, item, claimReference, returnReference, actions) { + actions.push({ + action: ChangeActionType.RETURN_ITEM, + reference: "return", + reference_id: returnReference.id, + details: { + reference_id: item.id, + return_id: returnReference.id, + claim_id: claimReference.id, + quantity: item.quantity, + metadata: item.metadata, + }, + }) + + return em.create(ReturnItem, { + item_id: item.id, + return_id: returnReference.id, + quantity: item.quantity, + note: item.note, + metadata: item.metadata, + }) +} + +function createClaimAndReturnItems( + em, + data, + claimReference, + returnReference, + actions +) { + const returnItems: ReturnItem[] = [] + const claimItems = data.claim_items?.map((item) => { + actions.push({ + action: ChangeActionType.WRITE_OFF_ITEM, + reference: "claim", + reference_id: claimReference.id, + details: { + reference_id: item.id, + claim_id: claimReference.id, + quantity: item.quantity, + metadata: item.metadata, + }, + }) + + returnItems.push( + returnReference + ? createReturnItem(em, item, claimReference, returnReference, actions) + : undefined + ) + + return em.create(ClaimItem, { + item_id: item.id, + reason: item.reason, + quantity: item.quantity, + note: item.note, + metadata: item.metadata, + }) + }) + + return [claimItems, returnItems] +} + +async function processAdditionalItems( + em, + service, + data, + order, + claimReference, + actions, + sharedContext +) { + const itemsToAdd: any[] = [] + const additionalNewItems: ClaimItem[] = [] + const additionalItems: ClaimItem[] = [] + data.additional_items?.forEach((item) => { + const hasItem = item.id + ? order.items.find((o) => o.item.id === item.id) + : false + + if (hasItem) { + actions.push({ + action: ChangeActionType.ITEM_ADD, + claim_id: claimReference.id, + internal_note: item.internal_note, + reference: "claim", + reference_id: claimReference.id, + details: { + reference_id: item.id, + claim_id: claimReference.id, + quantity: item.quantity, + metadata: item.metadata, + }, + }) + + additionalItems.push( + em.create(ClaimItem, { + item_id: item.id, + quantity: item.quantity, + note: item.note, + metadata: item.metadata, + is_additional_item: true, + }) + ) + } else { + itemsToAdd.push(item) + + additionalNewItems.push( + em.create(ClaimItem, { + quantity: item.quantity, + note: item.note, + metadata: item.metadata, + is_additional_item: true, + }) + ) + } + }) + + const createItems = await service.lineItemService_.create( + itemsToAdd, + sharedContext + ) + + createItems.forEach((item, index) => { + const addedItem = itemsToAdd[index] + additionalNewItems[index].item_id = item.id + actions.push({ + action: ChangeActionType.ITEM_ADD, + claim_id: claimReference.id, + internal_note: addedItem.internal_note, + reference: "claim", + reference_id: claimReference.id, + details: { + reference_id: item.id, + claim_id: claimReference.id, + quantity: addedItem.quantity, + metadata: addedItem.metadata, + }, + }) + }) + + return additionalNewItems.concat(additionalItems) +} + +async function processShippingMethods( + service, + data, + claimReference, + actions, + sharedContext +) { + for (const shippingMethod of data.shipping_methods ?? []) { + let shippingMethodId + + if (!isString(shippingMethod)) { + const methods = await service.createShippingMethods( + [ + { + order_id: data.order_id, + claim_id: claimReference.id, + ...shippingMethod, + }, + ], + sharedContext + ) + shippingMethodId = methods[0].id + } else { + shippingMethodId = shippingMethod + } + + const method = await service.retrieveShippingMethod( + shippingMethodId, + { relations: ["tax_lines", "adjustments"] }, + sharedContext + ) + + const calculatedAmount = getShippingMethodsTotals([method as any], {})[ + method.id + ] + + actions.push({ + action: ChangeActionType.SHIPPING_ADD, + reference: "order_shipping_method", + reference_id: shippingMethodId, + claim_id: claimReference.id, + amount: calculatedAmount.total, + }) + } +} + +async function processReturnShipping( + service, + data, + claimReference, + returnReference, + actions, + sharedContext +) { + if (!returnReference) { + return + } + + if (data.return_shipping) { + let returnShippingMethodId + + if (!isString(data.return_shipping)) { + const methods = await service.createShippingMethods( + [ + { + order_id: data.order_id, + claim_id: claimReference.id, + return_id: returnReference.id, + ...data.return_shipping, + }, + ], + sharedContext + ) + returnShippingMethodId = methods[0].id + } else { + returnShippingMethodId = data.return_shipping + } + + const method = await service.retrieveShippingMethod( + returnShippingMethodId, + { relations: ["tax_lines", "adjustments"] }, + sharedContext + ) + + const calculatedAmount = getShippingMethodsTotals([method as any], {})[ + method.id + ] + + actions.push({ + action: ChangeActionType.SHIPPING_ADD, + reference: "order_shipping_method", + reference_id: returnShippingMethodId, + return_id: returnReference.id, + claim_id: claimReference.id, + amount: calculatedAmount.total, + }) + } +} + +export async function createClaim( + this: any, + data: OrderTypes.CreateOrderClaimDTO, + sharedContext?: Context +) { + const order = await this.orderService_.retrieve( + data.order_id, + { relations: ["items"] }, + sharedContext + ) + const actions: CreateOrderChangeActionDTO[] = [] + const em = sharedContext!.transactionManager as any + const { claimReference, returnReference } = createClaimAndReturnEntities( + em, + data, + order + ) + + const [claimItems, returnItems] = createClaimAndReturnItems( + em, + data, + claimReference, + returnReference, + actions + ) + + claimReference.claim_items = claimItems + + if (returnReference) { + returnReference.items = returnItems + } + + claimReference.additional_items = await processAdditionalItems( + em, + this, + data, + order, + claimReference, + actions, + sharedContext + ) + await processShippingMethods( + this, + data, + claimReference, + actions, + sharedContext + ) + await processReturnShipping( + this, + data, + claimReference, + returnReference, + actions, + sharedContext + ) + + const change = await this.createOrderChange_( + { + order_id: data.order_id, + claim_id: claimReference.id, + return_id: returnReference.id, + change_type: OrderChangeType.CLAIM, + reference: "claim", + reference_id: claimReference.id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions, + }, + sharedContext + ) + + await promiseAll([ + this.createReturns([returnReference], sharedContext), + this.createOrderClaims([claimReference], sharedContext), + this.confirmOrderChange(change[0].id, sharedContext), + ]) + + return claimReference +} diff --git a/packages/modules/order/src/services/actions/create-exchange.ts b/packages/modules/order/src/services/actions/create-exchange.ts new file mode 100644 index 0000000000..1b6d111f09 --- /dev/null +++ b/packages/modules/order/src/services/actions/create-exchange.ts @@ -0,0 +1,316 @@ +import { + Context, + CreateOrderChangeActionDTO, + OrderTypes, +} from "@medusajs/types" +import { + ReturnStatus, + getShippingMethodsTotals, + isString, + promiseAll, +} from "@medusajs/utils" +import { ExchangeItem, OrderExchange, Return, ReturnItem } from "@models" +import { OrderChangeType } from "@types" +import { ChangeActionType } from "../../utils" + +function createExchangeAndReturnEntities(em, data, order) { + const exchangeReference = em.create(OrderExchange, { + order_id: data.order_id, + order_version: order.version, + no_notification: data.no_notification, + allow_backorder: data.allow_backorder, + difference_due: data.difference_due, + }) + + const returnReference = em.create(Return, { + order_id: data.order_id, + order_version: order.version, + status: ReturnStatus.REQUESTED, + exchange_id: exchangeReference.id, + refund_amount: (data.refund_amount as unknown) ?? null, + }) + + exchangeReference.return_id = returnReference.id + + return { exchangeReference, returnReference } +} + +function createReturnItems( + em, + data, + exchangeReference, + returnReference, + actions +) { + return data.return_items?.map((item) => { + actions.push({ + action: ChangeActionType.RETURN_ITEM, + reference: "return", + reference_id: returnReference.id, + details: { + reference_id: item.id, + return_id: returnReference.id, + exchange_id: exchangeReference.id, + quantity: item.quantity, + metadata: item.metadata, + }, + }) + + return em.create(ReturnItem, { + item_id: item.id, + return_id: returnReference.id, + reason: item.reason, + quantity: item.quantity, + note: item.note, + metadata: item.metadata, + }) + }) +} + +async function processAdditionalItems( + em, + service, + data, + order, + exchangeReference, + actions, + sharedContext +) { + const itemsToAdd: any[] = [] + const additionalNewItems: ExchangeItem[] = [] + const additionalItems: ExchangeItem[] = [] + data.additional_items?.forEach((item) => { + const hasItem = item.id + ? order.items.find((o) => o.item.id === item.id) + : false + + if (hasItem) { + actions.push({ + action: ChangeActionType.ITEM_ADD, + exchange_id: exchangeReference.id, + internal_note: item.internal_note, + reference: "exchange", + reference_id: exchangeReference.id, + details: { + reference_id: item.id, + exchange_id: exchangeReference.id, + quantity: item.quantity, + metadata: item.metadata, + }, + }) + + additionalItems.push( + em.create(ExchangeItem, { + item_id: item.id, + quantity: item.quantity, + note: item.note, + metadata: item.metadata, + is_additional_item: true, + }) + ) + } else { + itemsToAdd.push(item) + + additionalNewItems.push( + em.create(ExchangeItem, { + quantity: item.quantity, + note: item.note, + metadata: item.metadata, + is_additional_item: true, + }) + ) + } + }) + + const createItems = await service.lineItemService_.create( + itemsToAdd, + sharedContext + ) + + createItems.forEach((item, index) => { + const addedItem = itemsToAdd[index] + additionalNewItems[index].item_id = item.id + actions.push({ + action: ChangeActionType.ITEM_ADD, + exchange_id: exchangeReference.id, + internal_note: addedItem.internal_note, + reference: "exchange", + reference_id: exchangeReference.id, + details: { + reference_id: item.id, + exchange_id: exchangeReference.id, + quantity: addedItem.quantity, + metadata: addedItem.metadata, + }, + }) + }) + + return additionalNewItems.concat(additionalItems) +} + +async function processShippingMethods( + service, + data, + exchangeReference, + actions, + sharedContext +) { + for (const shippingMethod of data.shipping_methods ?? []) { + let shippingMethodId + + if (!isString(shippingMethod)) { + const methods = await service.createShippingMethods( + [ + { + order_id: data.order_id, + exchange_id: exchangeReference.id, + ...shippingMethod, + }, + ], + sharedContext + ) + shippingMethodId = methods[0].id + } else { + shippingMethodId = shippingMethod + } + + const method = await service.retrieveShippingMethod( + shippingMethodId, + { relations: ["tax_lines", "adjustments"] }, + sharedContext + ) + + const calculatedAmount = getShippingMethodsTotals([method as any], {})[ + method.id + ] + + actions.push({ + action: ChangeActionType.SHIPPING_ADD, + reference: "order_shipping_method", + reference_id: shippingMethodId, + exchange_id: exchangeReference.id, + amount: calculatedAmount.total, + }) + } +} + +async function processReturnShipping( + service, + data, + exchangeReference, + returnReference, + actions, + sharedContext +) { + let returnShippingMethodId + + if (!isString(data.return_shipping)) { + const methods = await service.createShippingMethods( + [ + { + order_id: data.order_id, + exchange_id: exchangeReference.id, + return_id: returnReference.id, + ...data.return_shipping, + }, + ], + sharedContext + ) + returnShippingMethodId = methods[0].id + } else { + returnShippingMethodId = data.return_shipping + } + + const method = await service.retrieveShippingMethod( + returnShippingMethodId, + { relations: ["tax_lines", "adjustments"] }, + sharedContext + ) + + const calculatedAmount = getShippingMethodsTotals([method as any], {})[ + method.id + ] + + actions.push({ + action: ChangeActionType.SHIPPING_ADD, + reference: "order_shipping_method", + reference_id: returnShippingMethodId, + return_id: returnReference.id, + exchange_id: exchangeReference.id, + amount: calculatedAmount.total, + }) +} + +export async function createExchange( + this: any, + data: OrderTypes.CreateOrderExchangeDTO, + sharedContext?: Context +) { + const order = await this.orderService_.retrieve( + data.order_id, + { relations: ["items"] }, + sharedContext + ) + const actions: CreateOrderChangeActionDTO[] = [] + const em = sharedContext!.transactionManager as any + const { exchangeReference, returnReference } = + createExchangeAndReturnEntities(em, data, order) + + returnReference.items = createReturnItems( + em, + data, + exchangeReference, + returnReference, + actions + ) + + exchangeReference.additional_items = await processAdditionalItems( + em, + this, + data, + order, + exchangeReference, + actions, + sharedContext + ) + await processShippingMethods( + this, + data, + exchangeReference, + actions, + sharedContext + ) + await processReturnShipping( + this, + data, + exchangeReference, + returnReference, + actions, + sharedContext + ) + + const change = await this.createOrderChange_( + { + order_id: data.order_id, + exchange_id: exchangeReference.id, + return_id: returnReference.id, + change_type: OrderChangeType.CLAIM, + reference: "exchange", + reference_id: exchangeReference.id, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions, + }, + sharedContext + ) + + await promiseAll([ + this.createReturns([returnReference], sharedContext), + this.createOrderExchanges([exchangeReference], sharedContext), + this.confirmOrderChange(change[0].id, sharedContext), + ]) + + return exchangeReference +} diff --git a/packages/modules/order/src/services/actions/create-return.ts b/packages/modules/order/src/services/actions/create-return.ts index 4e1f13c824..87547ed92b 100644 --- a/packages/modules/order/src/services/actions/create-return.ts +++ b/packages/modules/order/src/services/actions/create-return.ts @@ -7,67 +7,25 @@ import { ReturnStatus, getShippingMethodsTotals, isString, + promiseAll, } from "@medusajs/utils" +import { Return, ReturnItem } from "@models" 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 - ) +function createReturnReference(em, data, order) { + return em.create(Return, { + order_id: data.order_id, + order_version: order.version, + status: ReturnStatus.REQUESTED, + no_notification: data.no_notification, + refund_amount: (data.refund_amount as unknown) ?? null, + }) +} - const [returnRef] = await this.createReturns( - [ - { - order_id: data.order_id, - order_version: order.version, - status: ReturnStatus.REQUESTED, - // TODO: add refund amount / calculate? - // 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 { +function createReturnItems(em, data, returnRef, actions) { + return data.items.map((item) => { + actions.push({ action: ChangeActionType.RETURN_ITEM, return_id: returnRef.id, internal_note: item.internal_note, @@ -79,8 +37,53 @@ export async function createReturn( quantity: item.quantity, metadata: item.metadata, }, - } + }) + + return em.create(ReturnItem, { + reason_id: item.reason_id, + return_id: returnRef.id, + item_id: item.id, + quantity: item.quantity, + note: item.note, + metadata: item.metadata, + }) }) +} + +async function processShippingMethod( + service, + data, + returnRef, + actions, + sharedContext +) { + let shippingMethodId + + if (!isString(data.shipping_method)) { + const methods = await service.createShippingMethods( + [ + { + order_id: data.order_id, + return_id: returnRef.id, + ...data.shipping_method, + }, + ], + sharedContext + ) + shippingMethodId = methods[0].id + } else { + shippingMethodId = data.shipping_method + } + + const method = await service.retrieveShippingMethod( + shippingMethodId, + { relations: ["tax_lines", "adjustments"] }, + sharedContext + ) + + const calculatedAmount = getShippingMethodsTotals([method as any], {})[ + method.id + ] if (shippingMethodId) { actions.push({ @@ -91,8 +94,16 @@ export async function createReturn( amount: calculatedAmount.total, }) } +} - const change = await this.createOrderChange_( +async function createOrderChange( + service, + data, + returnRef, + actions, + sharedContext +) { + return await service.createOrderChange_( { order_id: data.order_id, return_id: returnRef.id, @@ -107,8 +118,36 @@ export async function createReturn( }, sharedContext ) +} - await this.confirmOrderChange(change[0].id, sharedContext) +export async function createReturn( + this: any, + data: OrderTypes.CreateOrderReturnDTO, + sharedContext?: Context +) { + const order = await this.orderService_.retrieve( + data.order_id, + { relations: ["items"] }, + sharedContext + ) + + const em = sharedContext!.transactionManager as any + const returnRef = createReturnReference(em, data, order) + const actions: CreateOrderChangeActionDTO[] = [] + returnRef.items = createReturnItems(em, data, returnRef, actions) + await processShippingMethod(this, data, returnRef, actions, sharedContext) + const change = await createOrderChange( + this, + data, + returnRef, + actions, + sharedContext + ) + + await promiseAll([ + this.createReturns([returnRef], sharedContext), + 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 index d8752998a3..640ed6a13a 100644 --- a/packages/modules/order/src/services/actions/index.ts +++ b/packages/modules/order/src/services/actions/index.ts @@ -1,4 +1,6 @@ export * from "./cancel-fulfillment" +export * from "./create-claim" +export * from "./create-exchange" export * from "./create-return" export * from "./receive-return" export * from "./register-fulfillment" diff --git a/packages/modules/order/src/services/actions/receive-return.ts b/packages/modules/order/src/services/actions/receive-return.ts index a17539d289..1f0dd5bfd5 100644 --- a/packages/modules/order/src/services/actions/receive-return.ts +++ b/packages/modules/order/src/services/actions/receive-return.ts @@ -1,8 +1,78 @@ import { Context, OrderTypes } from "@medusajs/types" -import { MathBN, ReturnStatus } from "@medusajs/utils" +import { MathBN, ReturnStatus, promiseAll } from "@medusajs/utils" import { OrderChangeType } from "@types" import { ChangeActionType } from "../../utils" +function createReturnItems(data) { + return data.items.map((item) => ({ + 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, + }, + })) +} + +async function createOrderChange( + service, + data, + returnEntry, + items, + sharedContext +) { + return await service.createOrderChange_( + { + order_id: returnEntry.order_id, + return_id: returnEntry.id, + reference: "return", + reference_id: returnEntry.id, + change_type: OrderChangeType.RETURN, + description: data.description, + internal_note: data.internal_note, + created_by: data.created_by, + metadata: data.metadata, + actions: items, + }, + sharedContext + ) +} + +function updateReturnItems(returnEntry, items) { + return returnEntry.items + .map((item) => { + const data = items.find((i) => i.details.reference_id === item.item_id) + if (!data) return + + const receivedQuantity = MathBN.add( + item.received_quantity || 0, + data.details.quantity + ) + item.received_quantity = receivedQuantity + + return { + id: item.id, + received_quantity: receivedQuantity, + } + }) + .filter(Boolean) +} + +function checkAllItemsReceived(returnEntry) { + return returnEntry.items.every((item) => + MathBN.eq(item.received_quantity, item.quantity) + ) +} + +function getReturnUpdateData(hasReceivedAllItems) { + return hasReceivedAllItems + ? { status: ReturnStatus.RECEIVED, received_at: new Date() } + : { status: ReturnStatus.PARTIALLY_RECEIVED } +} + export async function receiveReturn( this: any, data: OrderTypes.ReceiveOrderReturnDTO, @@ -17,59 +87,28 @@ export async function receiveReturn( 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, - change_type: OrderChangeType.RETURN, - description: data.description, - internal_note: data.internal_note, - created_by: data.created_by, - metadata: data.metadata, - actions: items, - }, + const items = createReturnItems(data) + const change = await createOrderChange( + this, + data, + returnEntry, + 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 retItemsToUpdate = updateReturnItems(returnEntry, items) + const hasReceivedAllItems = checkAllItemsReceived(returnEntry) + const retData = getReturnUpdateData(hasReceivedAllItems) - 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 - ) + const [returnRef] = await promiseAll([ + this.updateReturns( + { selector: { id: returnEntry.id }, data: retData }, + sharedContext + ), + this.updateReturnItems(retItemsToUpdate, sharedContext), + ]) return returnRef } diff --git a/packages/modules/order/src/services/order-module-service.ts b/packages/modules/order/src/services/order-module-service.ts index eac8ea4b7d..d20c20045c 100644 --- a/packages/modules/order/src/services/order-module-service.ts +++ b/packages/modules/order/src/services/order-module-service.ts @@ -3,8 +3,8 @@ import { Context, DAL, FindConfig, - InternalModuleDeclaration, IOrderModuleService, + InternalModuleDeclaration, ModuleJoinerConfig, ModulesSdkTypes, OrderDTO, @@ -16,18 +16,18 @@ import { } from "@medusajs/types" import { BigNumber, - createRawPropertiesFromBigNumber, - decorateCartTotals, - deduplicate, InjectManager, InjectTransactionManager, - isObject, - isString, MathBN, MedusaContext, MedusaError, ModulesSdkUtils, OrderStatus, + createRawPropertiesFromBigNumber, + decorateCartTotals, + deduplicate, + isObject, + isString, promiseAll, transformPropertiesToBigNumber, } from "@medusajs/utils" @@ -39,10 +39,13 @@ import { Order, OrderChange, OrderChangeAction, + OrderClaim, + OrderExchange, OrderItem, OrderShippingMethod, OrderSummary, Return, + ReturnItem, ReturnReason, ShippingMethod, ShippingMethodAdjustment, @@ -64,8 +67,8 @@ import { } from "@types" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { - applyChangesToOrder, ApplyOrderChangeDTO, + applyChangesToOrder, calculateOrderChange, formatOrder, } from "../utils" @@ -91,6 +94,9 @@ type InjectedDependencies = { orderShippingMethodService: ModulesSdkTypes.IMedusaInternalService returnReasonService: ModulesSdkTypes.IMedusaInternalService returnService: ModulesSdkTypes.IMedusaInternalService + returnItemService: ModulesSdkTypes.IMedusaInternalService + orderClaimService: ModulesSdkTypes.IMedusaInternalService + orderExchangeService: ModulesSdkTypes.IMedusaInternalService } const generateMethodForModels = { @@ -109,6 +115,9 @@ const generateMethodForModels = { OrderShippingMethod, ReturnReason, Return, + ReturnItem, + OrderClaim, + OrderExchange, } export default class OrderModuleService< @@ -127,7 +136,10 @@ export default class OrderModuleService< TOrderSummary extends OrderSummary = OrderSummary, TOrderShippingMethod extends OrderShippingMethod = OrderShippingMethod, TReturnReason extends ReturnReason = ReturnReason, - TReturn extends Return = Return + TReturn extends Return = Return, + TReturnItem extends ReturnItem = ReturnItem, + TClaim extends OrderClaim = OrderClaim, + TExchange extends OrderExchange = OrderExchange > extends ModulesSdkUtils.MedusaService< OrderTypes.OrderDTO, @@ -149,6 +161,9 @@ export default class OrderModuleService< OrderSummary: { dto: OrderTypes.OrderSummaryDTO } Transaction: { dto: OrderTypes.OrderTransactionDTO } Return: { dto: any } // TODO: Add return dto + ReturnItem: { dto: any } // TODO: Add return item dto + OrderClaim: { dto: any } // TODO: Add claim dto + OrderExchange: { dto: any } // TODO: Add exchange dto } >(Order, generateMethodForModels, entityNameToLinkableKeysMap) implements IOrderModuleService @@ -170,6 +185,9 @@ export default class OrderModuleService< protected orderShippingMethodService_: ModulesSdkTypes.IMedusaInternalService protected returnReasonService_: ModulesSdkTypes.IMedusaInternalService protected returnService_: ModulesSdkTypes.IMedusaInternalService + protected returnItemService_: ModulesSdkTypes.IMedusaInternalService + protected orderClaimService_: ModulesSdkTypes.IMedusaInternalService + protected orderExchangeService_: ModulesSdkTypes.IMedusaInternalService constructor( { @@ -190,6 +208,9 @@ export default class OrderModuleService< orderShippingMethodService, returnReasonService, returnService, + returnItemService, + orderClaimService, + orderExchangeService, }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { @@ -213,6 +234,9 @@ export default class OrderModuleService< this.orderShippingMethodService_ = orderShippingMethodService this.returnReasonService_ = returnReasonService this.returnService_ = returnService + this.returnItemService_ = returnItemService + this.orderClaimService_ = orderClaimService + this.orderExchangeService_ = orderExchangeService } __joinerConfig(): ModuleJoinerConfig { @@ -289,7 +313,10 @@ export default class OrderModuleService< const order = await super.retrieve(id, config, sharedContext) - return formatOrder(order, { includeTotals }) as OrderTypes.OrderDTO + return formatOrder(order, { + entity: Order, + includeTotals, + }) as OrderTypes.OrderDTO } async list( @@ -303,6 +330,7 @@ export default class OrderModuleService< const orders = await super.list(filters, config, sharedContext) return formatOrder(orders, { + entity: Order, includeTotals, }) as OrderTypes.OrderDTO[] } @@ -322,7 +350,10 @@ export default class OrderModuleService< ) return [ - formatOrder(orders, { includeTotals }) as OrderTypes.OrderDTO[], + formatOrder(orders, { + entity: Order, + includeTotals, + }) as OrderTypes.OrderDTO[], count, ] } @@ -338,7 +369,10 @@ export default class OrderModuleService< const returnOrder = await super.retrieveReturn(id, config, sharedContext) - return formatOrder(returnOrder, { includeTotals }) as OrderTypes.ReturnDTO + return formatOrder(returnOrder, { + entity: Return, + includeTotals, + }) as OrderTypes.ReturnDTO } // @ts-ignore @@ -353,6 +387,7 @@ export default class OrderModuleService< const returnOrders = await super.listReturns(filters, config, sharedContext) return formatOrder(returnOrders, { + entity: Return, includeTotals, }) as OrderTypes.ReturnDTO[] } @@ -373,7 +408,142 @@ export default class OrderModuleService< ) return [ - formatOrder(returnOrders, { includeTotals }) as OrderTypes.ReturnDTO[], + formatOrder(returnOrders, { + entity: Return, + includeTotals, + }) as OrderTypes.ReturnDTO[], + count, + ] + } + + // @ts-ignore + async retrieveOrderClaim( + id: string, + config?: FindConfig | undefined, + @MedusaContext() sharedContext?: Context | undefined + ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const returnOrder = await super.retrieveOrderClaim( + id, + config, + sharedContext + ) + + return formatOrder(returnOrder, { + entity: OrderClaim, + includeTotals, + }) as OrderTypes.OrderClaimDTO + } + + // @ts-ignore + async listOrderClaims( + filters?: any, + config?: FindConfig | undefined, + @MedusaContext() sharedContext?: Context | undefined + ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const returnOrders = await super.listOrderClaims( + filters, + config, + sharedContext + ) + + return formatOrder(returnOrders, { + entity: OrderClaim, + includeTotals, + }) as OrderTypes.OrderClaimDTO[] + } + + // @ts-ignore + async listAndCountOrderClaims( + filters?: any, + config?: FindConfig | undefined, + @MedusaContext() sharedContext?: Context | undefined + ): Promise<[OrderTypes.OrderClaimDTO[], number]> { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const [returnOrders, count] = await super.listAndCountOrderClaims( + filters, + config, + sharedContext + ) + + return [ + formatOrder(returnOrders, { + entity: OrderClaim, + includeTotals, + }) as OrderTypes.OrderClaimDTO[], + count, + ] + } + + // @ts-ignore + async retrieveOrderExchange( + id: string, + config?: FindConfig | undefined, + @MedusaContext() sharedContext?: Context | undefined + ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const returnOrder = await super.retrieveOrderExchange( + id, + config, + sharedContext + ) + + return formatOrder(returnOrder, { + entity: OrderExchange, + includeTotals, + }) as OrderTypes.OrderExchangeDTO + } + + // @ts-ignore + async listOrderExchanges( + filters?: any, + config?: FindConfig | undefined, + @MedusaContext() sharedContext?: Context | undefined + ): Promise { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const returnOrders = await super.listOrderExchanges( + filters, + config, + sharedContext + ) + + return formatOrder(returnOrders, { + entity: OrderExchange, + includeTotals, + }) as OrderTypes.OrderExchangeDTO[] + } + + // @ts-ignore + async listAndCountOrderExchanges( + filters?: any, + config?: FindConfig | undefined, + @MedusaContext() sharedContext?: Context | undefined + ): Promise<[OrderTypes.OrderExchangeDTO[], number]> { + config ??= {} + const includeTotals = this.shouldIncludeTotals(config) + + const [returnOrders, count] = await super.listAndCountOrderExchanges( + filters, + config, + sharedContext + ) + + return [ + formatOrder(returnOrders, { + entity: OrderExchange, + includeTotals, + }) as OrderTypes.OrderExchangeDTO[], count, ] } @@ -1648,26 +1818,16 @@ export default class OrderModuleService< await this.orderChangeService_.update(updates as any, sharedContext) } - async confirmOrderChange( - orderChangeId: string, - sharedContext?: Context - ): Promise - - async confirmOrderChange( - orderChangeId: string[], - sharedContext?: Context - ): Promise - + async confirmOrderChange(orderChangeId: string, sharedContext?: Context) + async confirmOrderChange(orderChangeId: string[], sharedContext?: Context) async confirmOrderChange( data: OrderTypes.ConfirmOrderChangeDTO, sharedContext?: Context - ): Promise - + ) async confirmOrderChange( data: OrderTypes.ConfirmOrderChangeDTO[], sharedContext?: Context - ): Promise - + ) @InjectTransactionManager("baseRepository_") async confirmOrderChange( orderChangeIdOrData: @@ -1676,7 +1836,7 @@ export default class OrderModuleService< | OrderTypes.ConfirmOrderChangeDTO | OrderTypes.ConfirmOrderChangeDTO[], @MedusaContext() sharedContext?: Context - ): Promise { + ): Promise { const data = Array.isArray(orderChangeIdOrData) ? orderChangeIdOrData : [orderChangeIdOrData] @@ -1715,29 +1875,19 @@ export default class OrderModuleService< return change.actions }) - await this.applyOrderChanges_(orderChanges.flat(), sharedContext) + return await this.applyOrderChanges_(orderChanges.flat(), sharedContext) } - async declineOrderChange( - orderChangeId: string, - sharedContext?: Context - ): Promise - - async declineOrderChange( - orderChangeId: string[], - sharedContext?: Context - ): Promise - + async declineOrderChange(orderChangeId: string, sharedContext?: Context) + async declineOrderChange(orderChangeId: string[], sharedContext?: Context) async declineOrderChange( data: OrderTypes.DeclineOrderChangeDTO, sharedContext?: Context - ): Promise - + ) async declineOrderChange( data: OrderTypes.DeclineOrderChangeDTO[], sharedContext?: Context - ): Promise - + ) @InjectTransactionManager("baseRepository_") async declineOrderChange( orderChangeIdOrData: @@ -1772,7 +1922,7 @@ export default class OrderModuleService< async applyPendingOrderActions( orderId: string | string[], @MedusaContext() sharedContext?: Context - ): Promise { + ): Promise { const orderIds = Array.isArray(orderId) ? orderId : [orderId] const orders = await this.list( @@ -1811,7 +1961,7 @@ export default class OrderModuleService< sharedContext ) - await this.applyOrderChanges_( + return await this.applyOrderChanges_( changes as ApplyOrderChangeDTO[], sharedContext ) @@ -2059,7 +2209,7 @@ export default class OrderModuleService< private async applyOrderChanges_( changeActions: ApplyOrderChangeDTO[], sharedContext?: Context - ): Promise { + ): Promise { const actionsMap: Record = {} const ordersIds: string[] = [] const usedActions: any[] = [] @@ -2085,10 +2235,13 @@ export default class OrderModuleService< } if (!ordersIds.length) { - return + return { + items: [], + shippingMethods: [], + } } - const orders = await this.list( + let orders = await super.list( { id: deduplicate(ordersIds) }, { select: [ @@ -2096,13 +2249,22 @@ export default class OrderModuleService< "version", "items.detail", "transactions", + "shipping_methods", "summary", "total", ], - relations: ["transactions", "items", "items.detail"], + relations: [ + "transactions", + "items", + "items.detail", + "shipping_methods", + ], }, sharedContext ) + orders = formatOrder(orders, { + entity: Order, + }) as OrderDTO[] const { itemsToUpsert, @@ -2131,6 +2293,11 @@ export default class OrderModuleService< ) : null, ]) + + return { + items: itemsToUpsert as any, + shippingMethods: shippingMethodsToInsert as any, + } } async addTransactions( @@ -2663,6 +2830,88 @@ export default class OrderModuleService< return await BundledActions.receiveReturn.bind(this)(data, sharedContext) } + @InjectManager("baseRepository_") + async createClaim( + data: OrderTypes.CreateOrderClaimDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + const ret = await this.createClaim_(data, sharedContext) + + const claim = await this.retrieveOrderClaim( + ret.id, + { + relations: [ + "additional_items", + "additional_items.item", + "claim_items", + "claim_items.item", + "return", + "return.items", + "shipping_methods", + "shipping_methods.tax_lines", + "shipping_methods.adjustments", + "transactions", + ], + }, + sharedContext + ) + + return await this.baseRepository_.serialize( + claim, + { + populate: true, + } + ) + } + + @InjectTransactionManager("baseRepository_") + async createExchange_( + data: OrderTypes.CreateOrderExchangeDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + return await BundledActions.createExchange.bind(this)(data, sharedContext) + } + + @InjectManager("baseRepository_") + async createExchange( + data: OrderTypes.CreateOrderExchangeDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + const ret = await this.createExchange_(data, sharedContext) + + const claim = await this.retrieveOrderExchange( + ret.id, + { + relations: [ + "additional_items", + "additional_items.item", + "return", + "return.items", + "shipping_methods", + "shipping_methods.tax_lines", + "shipping_methods.adjustments", + "transactions", + ], + }, + sharedContext + ) + + return await this.baseRepository_.serialize( + claim, + { + populate: true, + } + ) + } + + @InjectTransactionManager("baseRepository_") + async createClaim_( + data: OrderTypes.CreateOrderClaimDTO, + @MedusaContext() sharedContext?: Context + ): Promise { + return await BundledActions.createClaim.bind(this)(data, sharedContext) + } + @InjectTransactionManager("baseRepository_") async registerFulfillment( data: OrderTypes.RegisterOrderFulfillmentDTO, diff --git a/packages/modules/order/src/utils/actions/index.ts b/packages/modules/order/src/utils/actions/index.ts index 082c41dfac..1e8594775c 100644 --- a/packages/modules/order/src/utils/actions/index.ts +++ b/packages/modules/order/src/utils/actions/index.ts @@ -9,3 +9,4 @@ export * from "./receive-return-item" export * from "./return-item" export * from "./ship-item" export * from "./shipping-add" +export * from "./write-off-item" diff --git a/packages/modules/order/src/utils/actions/item-add.ts b/packages/modules/order/src/utils/actions/item-add.ts index 3795b08db3..17ee886bba 100644 --- a/packages/modules/order/src/utils/actions/item-add.ts +++ b/packages/modules/order/src/utils/actions/item-add.ts @@ -7,7 +7,7 @@ import { setActionReference } from "../set-action-reference" OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, { operation({ action, currentOrder }) { const existing = currentOrder.items.find( - (item) => item.id === action.reference_id + (item) => item.id === action.details.reference_id ) if (existing) { @@ -23,7 +23,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, { setActionReference(existing, action) } else { currentOrder.items.push({ - id: action.reference_id!, + id: action.details.reference_id!, order_id: currentOrder.id, return_id: action.details.return_id, claim_id: action.details.claim_id, @@ -38,7 +38,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, { }, revert({ action, currentOrder }) { const existingIndex = currentOrder.items.findIndex( - (item) => item.id === action.reference_id + (item) => item.id === action.details.reference_id ) if (existingIndex > -1) { @@ -55,13 +55,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_ADD, { } }, validate({ action }) { - const refId = action.reference_id - if (!isDefined(action.reference_id)) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - "Reference ID is required." - ) - } + const refId = action.details?.reference_id if (!isDefined(action.amount) && !isDefined(action.details?.unit_price)) { throw new MedusaError( diff --git a/packages/modules/order/src/utils/actions/item-remove.ts b/packages/modules/order/src/utils/actions/item-remove.ts index 7ae373e229..ef59731c22 100644 --- a/packages/modules/order/src/utils/actions/item-remove.ts +++ b/packages/modules/order/src/utils/actions/item-remove.ts @@ -8,7 +8,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_REMOVE, { isDeduction: true, operation({ action, currentOrder }) { const existingIndex = currentOrder.items.findIndex( - (item) => item.id === action.reference_id + (item) => item.id === action.details.reference_id ) const existing = currentOrder.items[existingIndex] @@ -31,7 +31,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_REMOVE, { }, revert({ action, currentOrder }) { const existing = currentOrder.items.find( - (item) => item.id === action.reference_id + (item) => item.id === action.details.reference_id ) if (existing) { @@ -42,14 +42,14 @@ OrderChangeProcessing.registerActionType(ChangeActionType.ITEM_REMOVE, { ) } else { currentOrder.items.push({ - id: action.reference_id!, + id: action.details.reference_id!, unit_price: action.details.unit_price, quantity: action.details.quantity, } as VirtualOrder["items"][0]) } }, validate({ action, currentOrder }) { - const refId = action.reference_id + const refId = action.details?.reference_id if (!isDefined(refId)) { throw new MedusaError( MedusaError.Types.INVALID_DATA, diff --git a/packages/modules/order/src/utils/apply-order-changes.ts b/packages/modules/order/src/utils/apply-order-changes.ts index 804aea8971..a34c897d14 100644 --- a/packages/modules/order/src/utils/apply-order-changes.ts +++ b/packages/modules/order/src/utils/apply-order-changes.ts @@ -31,24 +31,23 @@ export function applyChangesToOrder( const version = actionsMap[order.id][0].version ?? 1 for (const item of calculated.order.items) { - const orderItem = item.detail as any + const orderItem = (item.detail as any) ?? item + const itemId = item.detail ? orderItem.item_id : item.id + itemsToUpsert.push({ id: orderItem.version === version ? orderItem.id : undefined, - item_id: item.id, + item_id: itemId, 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) + quantity: orderItem.quantity, + fulfilled_quantity: orderItem.fulfilled_quantity, + shipped_quantity: orderItem.shipped_quantity, + return_requested_quantity: orderItem.return_requested_quantity, + return_received_quantity: orderItem.return_received_quantity, + return_dismissed_quantity: orderItem.return_dismissed_quantity, + written_off_quantity: orderItem.written_off_quantity, + metadata: orderItem.metadata, + } as OrderItem) } const orderSummary = order.summary as any @@ -61,6 +60,10 @@ export function applyChangesToOrder( if (version > order.version) { for (const shippingMethod of calculated.order.shipping_methods ?? []) { + if (!shippingMethod) { + continue + } + const sm = { ...((shippingMethod as any).detail ?? shippingMethod), version, diff --git a/packages/modules/order/src/utils/base-repository-find.ts b/packages/modules/order/src/utils/base-repository-find.ts index fad16abd6d..06b1b3b664 100644 --- a/packages/modules/order/src/utils/base-repository-find.ts +++ b/packages/modules/order/src/utils/base-repository-find.ts @@ -29,13 +29,16 @@ export function setFindMethods(klass: Constructor, entity: any) { } } - const config = mapRepositoryToOrderModel(findOptions_) + const isRelatedEntity = entity !== Order + const config = mapRepositoryToOrderModel(findOptions_, isRelatedEntity) let orderAlias = "o0" - if (entity !== Order) { - // first relation is always order if entity is not Order + if (isRelatedEntity) { + // first relation is always order if the entity is not Order config.options.populate.unshift("order") orderAlias = "o1" + + config.options.populate.unshift("order.items") } let defaultVersion = knex.raw(`"${orderAlias}"."version"`) @@ -44,7 +47,7 @@ export function setFindMethods(klass: Constructor, entity: any) { const sql = manager .qb(Order, "_sub0") .select("version") - .where({ id: knex.raw(`"o0"."order_id"`) }) + .where({ id: knex.raw(`"${orderAlias}"."order_id"`) }) .getKnexQuery() .toString() @@ -55,23 +58,22 @@ export function setFindMethods(klass: Constructor, entity: any) { delete config.where?.version config.options.populateWhere ??= {} + const popWhere = 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 + if (isRelatedEntity) { + popWhere.order ??= {} } - config.options.populateWhere.items ??= {} - config.options.populateWhere.items.version = version + const orderWhere = isRelatedEntity ? popWhere.order : popWhere - config.options.populateWhere.shipping_methods ??= {} - config.options.populateWhere.shipping_methods.version = version + orderWhere.summary ??= {} + orderWhere.summary.version = version + + orderWhere.items ??= {} + orderWhere.items.version = version + + popWhere.shipping_methods ??= {} + popWhere.shipping_methods.version = version if (!config.options.orderBy) { config.options.orderBy = { id: "ASC" } @@ -98,10 +100,11 @@ export function setFindMethods(klass: Constructor, entity: any) { }) } - const config = mapRepositoryToOrderModel(findOptions_) + const isRelatedEntity = entity !== Order + const config = mapRepositoryToOrderModel(findOptions_, isRelatedEntity) let orderAlias = "o0" - if (entity !== Order) { + if (isRelatedEntity) { // first relation is always order if entity is not Order config.options.populate.unshift("order") orderAlias = "o1" @@ -113,7 +116,7 @@ export function setFindMethods(klass: Constructor, entity: any) { const sql = manager .qb(Order, "_sub0") .select("version") - .where({ id: knex.raw(`"o0"."order_id"`) }) + .where({ id: knex.raw(`"${orderAlias}"."order_id"`) }) .getKnexQuery() .toString() @@ -124,23 +127,22 @@ export function setFindMethods(klass: Constructor, entity: any) { delete config.where.version config.options.populateWhere ??= {} + const popWhere = 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 + if (isRelatedEntity) { + popWhere.order ??= {} } - config.options.populateWhere.items ??= {} - config.options.populateWhere.items.version = version + const orderWhere = isRelatedEntity ? popWhere.order : popWhere - config.options.populateWhere.shipping_methods ??= {} - config.options.populateWhere.shipping_methods.version = version + orderWhere.summary ??= {} + orderWhere.summary.version = version + + orderWhere.items ??= {} + orderWhere.items.version = version + + popWhere.shipping_methods ??= {} + popWhere.shipping_methods.version = version if (!config.options.orderBy) { config.options.orderBy = { id: "ASC" } diff --git a/packages/modules/order/src/utils/calculate-order-change.ts b/packages/modules/order/src/utils/calculate-order-change.ts index 91d4980227..e650968262 100644 --- a/packages/modules/order/src/utils/calculate-order-change.ts +++ b/packages/modules/order/src/utils/calculate-order-change.ts @@ -3,6 +3,7 @@ import { BigNumber, MathBN, isDefined, + isPresent, transformPropertiesToBigNumber, } from "@medusajs/utils" import { @@ -183,9 +184,14 @@ export class OrderChangeProcessing { action: InternalOrderChangeEvent, isReplay = false ): BigNumberInput | void { + const definedType = OrderChangeProcessing.typeDefinition[action.action] + if (!isPresent(definedType)) { + throw new Error(`Action type ${action.action} is not defined`) + } + const type = { ...OrderChangeProcessing.defaultConfig, - ...OrderChangeProcessing.typeDefinition[action.action], + ...definedType, } this.actionsProcessed[action.action] ??= [] diff --git a/packages/modules/order/src/utils/transform-order.ts b/packages/modules/order/src/utils/transform-order.ts index ea1851b5b1..9ead4af4e3 100644 --- a/packages/modules/order/src/utils/transform-order.ts +++ b/packages/modules/order/src/utils/transform-order.ts @@ -5,18 +5,32 @@ import { deduplicate, isDefined, } from "@medusajs/utils" +import { Order, OrderClaim, OrderExchange, Return } from "@models" export function formatOrder( order, options: { + entity: any includeTotals?: boolean } -): OrderTypes.OrderDTO | OrderTypes.OrderDTO[] { +): Partial | Partial[] { const isArray = Array.isArray(order) const orders = [...(isArray ? order : [order])] orders.map((order) => { - order.items = order.items?.map((orderItem) => { + let mainOrder = order + + const isRelatedEntity = options?.entity !== Order + + if (isRelatedEntity) { + if (!order.order) { + return order + } + + mainOrder = order.order + } + + mainOrder.items = mainOrder.items?.map((orderItem) => { const detail = { ...orderItem } delete detail.order delete detail.item @@ -29,30 +43,118 @@ export function formatOrder( } }) - order.shipping_methods = order.shipping_methods?.map((shippingMethod) => { - const sm = { ...shippingMethod.shipping_method } - - delete shippingMethod.shipping_method - return { - ...sm, - order_id: shippingMethod.order_id, - detail: { - ...shippingMethod, - }, + if (isRelatedEntity) { + if (order.return) { + formatOrderReturn(order.return, mainOrder) } - }) - order.summary = order.summary?.[0]?.totals + if (options.entity === OrderClaim) { + formatClaim(order) + } else if (options.entity === OrderExchange) { + formatExchange(order) + } else if (options.entity === Return) { + formatReturn(order) + } + } - return options?.includeTotals - ? createRawPropertiesFromBigNumber(decorateCartTotals(order)) - : order + if (order.shipping_methods) { + order.shipping_methods = order.shipping_methods?.map((shippingMethod) => { + const sm = { ...shippingMethod.shipping_method } + + delete shippingMethod.shipping_method + return { + ...sm, + order_id: shippingMethod.order_id, + detail: { + ...shippingMethod, + }, + } + }) + } + + if (mainOrder.summary) { + mainOrder.summary = mainOrder.summary?.[0]?.totals + } + + return createRawPropertiesFromBigNumber( + options?.includeTotals ? decorateCartTotals(order) : order + ) }) return isArray ? orders : orders[0] } -export function mapRepositoryToOrderModel(config) { +function formatOrderReturn(orderReturn, mainOrder) { + orderReturn.items = orderReturn.items.filter( + (item) => !item.is_additional_item + ) + orderReturn.items.forEach((orderItem) => { + const item = mainOrder.items?.find((item) => item.id === orderItem.item_id) + + orderItem.detail = item?.detail + }) +} + +function formatClaim(claim) { + if (claim.additional_items) { + claim.additional_items = claim.additional_items.filter( + (item) => item.is_additional_item + ) + + claim.additional_items.forEach((orderItem) => { + const item = claim.order.items?.find( + (item) => item.id === orderItem.item_id + ) + + orderItem.detail = item?.detail + }) + } + + if (!claim.claim_items) { + return + } + + claim.claim_items = claim.claim_items.filter( + (item) => !item.is_additional_item + ) + claim.claim_items.forEach((orderItem) => { + const item = claim.order.items?.find( + (item) => item.id === orderItem.item_id + ) + + orderItem.detail = item?.detail + }) +} + +function formatExchange(exchange) { + if (!exchange.additional_items) { + return + } + + exchange.additional_items.forEach((orderItem) => { + const item = exchange.order.items?.find( + (item) => item.id === orderItem.item_id + ) + + orderItem.detail = item?.detail + }) +} + +function formatReturn(returnOrder) { + if (!returnOrder.items) { + return + } + + returnOrder.items.forEach((orderItem) => { + const item = returnOrder.order.items?.find( + (item) => item.id === orderItem.item_id + ) + + orderItem.detail = item?.detail + }) +} + +export function mapRepositoryToOrderModel(config, isRelatedEntity = false) { const conf = { ...config } function replace(obj, type): string[] | undefined { diff --git a/yarn.lock b/yarn.lock index 22d803599c..e3630c709b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5293,7 +5293,6 @@ __metadata: cross-env: ^5.2.1 ioredis: ^5.2.5 rimraf: ^5.0.1 - typeorm: ^0.3.16 typescript: ^5.1.6 vite: ^5.2.11 winston: ^3.8.2 @@ -25939,7 +25938,6 @@ __metadata: ts-jest: ^29.1.1 ts-node: ^10.9.1 turbo: ^1.6.3 - typeorm: ^0.3.16 languageName: unknown linkType: soft