From 5813216c880490ec75c6ff41b398ac3a8dbc4d73 Mon Sep 17 00:00:00 2001 From: "Carlos R. L. Rodrigues" <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:23:34 -0300 Subject: [PATCH] core(core-flows, medusa): remove request item return (#8146) --- .../http/__tests__/returns/returns.spec.ts | 58 ++++++++++- .../modules/__tests__/order/order.spec.ts | 16 ++-- .../core-flows/src/order/workflows/index.ts | 1 + .../workflows/remove-request-item-return.ts | 95 +++++++++++++++++++ .../remove-return-shipping-method.ts | 4 +- .../src/workflow/order/request-item-return.ts | 12 +++ .../[id]/request-items/[action_id]/route.ts | 44 +++++++++ .../src/api/admin/returns/middlewares.ts | 10 ++ .../__tests__/order-items-shipping.spec.ts | 5 +- .../src/migrations/Migration20240715174100.ts | 4 +- .../src/migrations/Migration20240716081800.ts | 64 +++++++++++++ .../order/src/models/adjustment-line.ts | 3 + .../modules/order/src/models/line-item.ts | 15 +++ .../order/src/models/shipping-method.ts | 4 +- packages/modules/order/src/models/tax-line.ts | 3 + .../src/services/order-module-service.ts | 2 - 16 files changed, 320 insertions(+), 20 deletions(-) create mode 100644 packages/core/core-flows/src/order/workflows/remove-request-item-return.ts create mode 100644 packages/medusa/src/api/admin/returns/[id]/request-items/[action_id]/route.ts create mode 100644 packages/modules/order/src/migrations/Migration20240716081800.ts diff --git a/integration-tests/http/__tests__/returns/returns.spec.ts b/integration-tests/http/__tests__/returns/returns.spec.ts index 49bd93e8a4..6106d1439e 100644 --- a/integration-tests/http/__tests__/returns/returns.spec.ts +++ b/integration-tests/http/__tests__/returns/returns.spec.ts @@ -228,6 +228,57 @@ medusaIntegrationTestRunner({ total: 50, fulfilled_total: 50, return_requested_total: 50, + detail: expect.objectContaining({ + return_requested_quantity: 1, + }), + }), + ]), + }) + ) + + // Remove item return requesta + const returnItemActionId = + result.data.order_preview.items[0].actions[0].id + result = await api.delete( + `/admin/returns/${returnId}/request-items/${returnItemActionId}`, + adminHeaders + ) + + expect(result.data.order_preview).toEqual( + expect.objectContaining({ + id: order.id, + items: expect.arrayContaining([ + expect.objectContaining({ + detail: expect.objectContaining({ + return_requested_quantity: 0, + }), + }), + ]), + }) + ) + + // Add item return request again + result = await api.post( + `/admin/returns/${returnId}/request-items`, + { + items: [ + { + id: item.id, + quantity: 1, + }, + ], + }, + adminHeaders + ) + + expect(result.data.order_preview).toEqual( + expect.objectContaining({ + id: order.id, + items: expect.arrayContaining([ + expect.objectContaining({ + detail: expect.objectContaining({ + return_requested_quantity: 1, + }), }), ]), }) @@ -300,12 +351,13 @@ medusaIntegrationTestRunner({ }) ) - expect(result.data.order_preview.shipping_methods).toHaveLength(2) + // remove shipping method - const action_id = result.data.order_preview.shipping_methods[1].actions[0].id + const shippingActionId = + result.data.order_preview.shipping_methods[1].actions[0].id result = await api.delete( - `/admin/returns/${returnId}/shipping-method/${action_id}`, + `/admin/returns/${returnId}/shipping-method/${shippingActionId}`, adminHeaders ) diff --git a/integration-tests/modules/__tests__/order/order.spec.ts b/integration-tests/modules/__tests__/order/order.spec.ts index 861449245d..a1ac1fb28b 100644 --- a/integration-tests/modules/__tests__/order/order.spec.ts +++ b/integration-tests/modules/__tests__/order/order.spec.ts @@ -205,8 +205,9 @@ medusaIntegrationTestRunner({ metadata: null, created_at: expect.any(String), updated_at: expect.any(String), + deleted_at: null, tax_lines: [], - adjustments: [ + adjustments: expect.arrayContaining([ { id: expect.any(String), description: "VIP discount", @@ -219,6 +220,7 @@ medusaIntegrationTestRunner({ provider_id: expect.any(String), created_at: expect.any(String), updated_at: expect.any(String), + deleted_at: null, item_id: expect.any(String), amount: 5e-18, subtotal: 5e-18, @@ -232,7 +234,7 @@ medusaIntegrationTestRunner({ precision: 20, }, }, - ], + ]), compare_at_unit_price: null, unit_price: 50, quantity: 1, @@ -412,7 +414,7 @@ medusaIntegrationTestRunner({ metadata: null, created_at: expect.any(String), updated_at: expect.any(String), - tax_lines: [ + tax_lines: expect.arrayContaining([ { id: expect.any(String), description: "shipping Tax 1", @@ -425,6 +427,7 @@ medusaIntegrationTestRunner({ provider_id: null, created_at: expect.any(String), updated_at: expect.any(String), + deleted_at: null, shipping_method_id: expect.any(String), rate: 10, total: 0.9, @@ -438,8 +441,8 @@ medusaIntegrationTestRunner({ precision: 20, }, }, - ], - adjustments: [ + ]), + adjustments: expect.arrayContaining([ { id: expect.any(String), description: "VIP discount", @@ -452,6 +455,7 @@ medusaIntegrationTestRunner({ provider_id: null, created_at: expect.any(String), updated_at: expect.any(String), + deleted_at: null, shipping_method_id: expect.any(String), amount: 1, subtotal: 1, @@ -465,7 +469,7 @@ medusaIntegrationTestRunner({ precision: 20, }, }, - ], + ]), amount: 10, subtotal: 10, total: 9.9, diff --git a/packages/core/core-flows/src/order/workflows/index.ts b/packages/core/core-flows/src/order/workflows/index.ts index 458cfa413a..d0d6287be0 100644 --- a/packages/core/core-flows/src/order/workflows/index.ts +++ b/packages/core/core-flows/src/order/workflows/index.ts @@ -26,6 +26,7 @@ export * from "./exchange-request-item-return" export * from "./get-order-detail" export * from "./get-orders-list" export * from "./receive-return" +export * from "./remove-request-item-return" export * from "./remove-return-shipping-method" export * from "./request-item-return" export * from "./update-order-change-actions" diff --git a/packages/core/core-flows/src/order/workflows/remove-request-item-return.ts b/packages/core/core-flows/src/order/workflows/remove-request-item-return.ts new file mode 100644 index 0000000000..37866fd5ed --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/remove-request-item-return.ts @@ -0,0 +1,95 @@ +import { + OrderChangeActionDTO, + OrderChangeDTO, + OrderDTO, + OrderWorkflow, + ReturnDTO, +} from "@medusajs/types" +import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils" +import { + WorkflowData, + createStep, + createWorkflow, +} from "@medusajs/workflows-sdk" +import { useRemoteQueryStep } from "../../common" +import { deleteOrderChangeActionsStep, previewOrderChangeStep } from "../steps" +import { + throwIfIsCancelled, + throwIfOrderChangeIsNotActive, +} from "../utils/order-validation" + +const validationStep = createStep( + "remove-request-item-return-validation", + async function ({ + order, + orderChange, + orderReturn, + input, + }: { + order: OrderDTO + orderReturn: ReturnDTO + orderChange: OrderChangeDTO + input: OrderWorkflow.DeleteRequestItemReturnWorkflowInput + }) { + throwIfIsCancelled(order, "Order") + throwIfIsCancelled(orderReturn, "Return") + throwIfOrderChangeIsNotActive({ orderChange }) + + const associatedAction = orderChange.actions?.find( + (a) => a.id === input.action_id + ) as OrderChangeActionDTO + + if (!associatedAction) { + throw new Error( + `No request return found for return ${input.return_id} in order change ${orderChange.id}` + ) + } else if (associatedAction.action !== ChangeActionType.RETURN_ITEM) { + throw new Error( + `Action ${associatedAction.id} is not requesting item return` + ) + } + } +) + +export const removeRequestItemReturnWorkflowId = "remove-request-item-return" +export const removeRequestItemReturnWorkflow = createWorkflow( + removeRequestItemReturnWorkflowId, + function ( + input: WorkflowData + ): WorkflowData { + const orderReturn: ReturnDTO = useRemoteQueryStep({ + entry_point: "return", + fields: ["id", "status", "order_id"], + variables: { id: input.return_id }, + list: false, + throw_if_key_not_found: true, + }) + + const order: OrderDTO = useRemoteQueryStep({ + entry_point: "orders", + fields: ["id", "status", "items.*"], + variables: { id: orderReturn.order_id }, + list: false, + throw_if_key_not_found: true, + }).config({ name: "order-query" }) + + const orderChange: OrderChangeDTO = useRemoteQueryStep({ + entry_point: "order_change", + fields: ["id", "status", "version", "actions.*"], + variables: { + filters: { + order_id: orderReturn.order_id, + return_id: orderReturn.id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], + }, + }, + list: false, + }).config({ name: "order-change-query" }) + + validationStep({ order, input, orderReturn, orderChange }) + + deleteOrderChangeActionsStep({ ids: [input.action_id] }) + + return previewOrderChangeStep(order.id) + } +) diff --git a/packages/core/core-flows/src/order/workflows/remove-return-shipping-method.ts b/packages/core/core-flows/src/order/workflows/remove-return-shipping-method.ts index eefaab5d47..57b0091bee 100644 --- a/packages/core/core-flows/src/order/workflows/remove-return-shipping-method.ts +++ b/packages/core/core-flows/src/order/workflows/remove-return-shipping-method.ts @@ -34,7 +34,7 @@ const validationStep = createStep( throwIfIsCancelled(orderReturn, "Return") throwIfOrderChangeIsNotActive({ orderChange }) - const associatedAction = orderChange.actions.find( + const associatedAction = orderChange.actions?.find( (a) => a.id === input.action_id ) as OrderChangeActionDTO @@ -81,7 +81,7 @@ export const removeReturnShippingMethodWorkflow = createWorkflow( const dataToRemove = transform( { orderChange, input }, ({ orderChange, input }) => { - const associatedAction = orderChange.actions.find( + const associatedAction = orderChange.actions?.find( (a) => a.id === input.action_id ) as OrderChangeActionDTO diff --git a/packages/core/types/src/workflow/order/request-item-return.ts b/packages/core/types/src/workflow/order/request-item-return.ts index 18748d4e35..19d09d8e68 100644 --- a/packages/core/types/src/workflow/order/request-item-return.ts +++ b/packages/core/types/src/workflow/order/request-item-return.ts @@ -4,13 +4,25 @@ export interface RequestItemReturnWorkflowInput { return_id: string items: CreateReturnItem[] } +export interface DeleteRequestItemReturnWorkflowInput { + return_id: string + action_id: string +} export interface OrderExchangeRequestItemReturnWorkflowInput { exchange_id: string items: CreateReturnItem[] } +export interface DeleteOrderExchangeRequestItemReturnWorkflowInput { + return_id: string + action_id: string +} export interface OrderClaimRequestItemReturnWorkflowInput { claim_id: string items: CreateReturnItem[] } +export interface DeleteOrderClaimRequestItemReturnWorkflowInput { + return_id: string + action_id: string +} diff --git a/packages/medusa/src/api/admin/returns/[id]/request-items/[action_id]/route.ts b/packages/medusa/src/api/admin/returns/[id]/request-items/[action_id]/route.ts new file mode 100644 index 0000000000..ecde753266 --- /dev/null +++ b/packages/medusa/src/api/admin/returns/[id]/request-items/[action_id]/route.ts @@ -0,0 +1,44 @@ +import { removeRequestItemReturnWorkflow } from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../../types/routing" + +export const DELETE = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const { id, action_id } = req.params + + const { result: orderPreview } = await removeRequestItemReturnWorkflow( + req.scope + ).run({ + input: { + return_id: id, + action_id, + }, + }) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return", + variables: { + id, + filters: { + ...req.filterableFields, + }, + }, + fields: req.remoteQueryConfig.fields, + }) + const [orderReturn] = await remoteQuery(queryObject) + + res.json({ + order_preview: orderPreview, + return: orderReturn, + }) +} diff --git a/packages/medusa/src/api/admin/returns/middlewares.ts b/packages/medusa/src/api/admin/returns/middlewares.ts index 11ac9f2435..ff3b3b1591 100644 --- a/packages/medusa/src/api/admin/returns/middlewares.ts +++ b/packages/medusa/src/api/admin/returns/middlewares.ts @@ -54,6 +54,16 @@ export const adminReturnRoutesMiddlewares: MiddlewareRoute[] = [ ), ], }, + { + method: ["DELETE"], + matcher: "/admin/returns/:id/request-items/:action_id", + middlewares: [ + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, { method: ["POST"], matcher: "/admin/returns/:id/shipping-method", diff --git a/packages/modules/order/integration-tests/__tests__/order-items-shipping.spec.ts b/packages/modules/order/integration-tests/__tests__/order-items-shipping.spec.ts index 5de94ee091..87893b60bc 100644 --- a/packages/modules/order/integration-tests/__tests__/order-items-shipping.spec.ts +++ b/packages/modules/order/integration-tests/__tests__/order-items-shipping.spec.ts @@ -2190,11 +2190,12 @@ moduleIntegrationTestRunner({ }) const serialized = JSON.parse(JSON.stringify(order)) + expect(serialized.items).toEqual( expect.arrayContaining([ expect.objectContaining({ id: itemOne.id, - tax_lines: [ + tax_lines: expect.arrayContaining([ expect.objectContaining({ id: taxLine!.id, item_id: itemOne.id, @@ -2206,7 +2207,7 @@ moduleIntegrationTestRunner({ rate: 25, code: "TX-2", }), - ], + ]), }), ]) ) diff --git a/packages/modules/order/src/migrations/Migration20240715174100.ts b/packages/modules/order/src/migrations/Migration20240715174100.ts index 0753fdde00..43f17594cc 100644 --- a/packages/modules/order/src/migrations/Migration20240715174100.ts +++ b/packages/modules/order/src/migrations/Migration20240715174100.ts @@ -1,6 +1,6 @@ import { Migration } from "@mikro-orm/migrations" -export class Migration20240715102100 extends Migration { +export class Migration20240715174100 extends Migration { async up(): Promise { const sql = ` DROP INDEX IF EXISTS "IDX_order_shipping_method_shipping_option_id"; @@ -11,8 +11,6 @@ export class Migration20240715102100 extends Migration { CREATE INDEX IF NOT EXISTS "IDX_order_shipping_method_shipping_option_id" ON "order_shipping_method" ( shipping_option_id ) WHERE deleted_at IS NULL; - - ` this.addSql(sql) diff --git a/packages/modules/order/src/migrations/Migration20240716081800.ts b/packages/modules/order/src/migrations/Migration20240716081800.ts new file mode 100644 index 0000000000..c32ec6fae4 --- /dev/null +++ b/packages/modules/order/src/migrations/Migration20240716081800.ts @@ -0,0 +1,64 @@ +import { Migration } from "@mikro-orm/migrations" + +export class Migration20240716081800 extends Migration { + async up(): Promise { + const sql = ` + DROP INDEX IF EXISTS "IDX_order_line_item_variant_id"; + DROP INDEX IF EXISTS "IDX_order_line_item_product_id"; + + ALTER TABLE "order_line_item" + ADD COLUMN if NOT exists "deleted_at" timestamptz NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_line_item_variant_id" ON "order_line_item" ( + variant_id + ) WHERE deleted_at IS NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_line_item_product_id" ON "order_line_item" ( + product_id + ) WHERE deleted_at IS NULL; + + + + DROP INDEX IF EXISTS "IDX_order_shipping_method_tax_line_shipping_method_id"; + + ALTER TABLE "order_shipping_method_tax_line" + ADD COLUMN if NOT exists "deleted_at" timestamptz NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_shipping_method_tax_line_shipping_method_id" ON "order_shipping_method_tax_line" ( + shipping_method_id + ) WHERE deleted_at IS NULL; + + + DROP INDEX IF EXISTS "IDX_order_shipping_method_adjustment_shipping_method_id"; + + ALTER TABLE "order_shipping_method_adjustment" + ADD COLUMN if NOT exists "deleted_at" timestamptz NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_shipping_method_adjustment_shipping_method_id" ON "order_shipping_method_adjustment" ( + shipping_method_id + ) WHERE deleted_at IS NULL; + + + + DROP INDEX IF EXISTS "IDX_order_line_item_tax_line_item_id"; + + ALTER TABLE "order_line_item_tax_line" + ADD COLUMN if NOT exists "deleted_at" timestamptz NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_line_item_tax_line_item_id" ON "order_line_item_tax_line" ( + item_id + ) WHERE deleted_at IS NULL; + + DROP INDEX IF EXISTS "IDX_order_line_item_adjustment_item_id"; + + ALTER TABLE "order_line_item_adjustment" + ADD COLUMN if NOT exists "deleted_at" timestamptz NULL; + + CREATE INDEX IF NOT EXISTS "IDX_order_line_item_adjustment_item_id" ON "order_line_item_adjustment" ( + item_id + ) WHERE deleted_at IS NULL; + ` + + this.addSql(sql) + } +} diff --git a/packages/modules/order/src/models/adjustment-line.ts b/packages/modules/order/src/models/adjustment-line.ts index 5085688e7c..e87da7d466 100644 --- a/packages/modules/order/src/models/adjustment-line.ts +++ b/packages/modules/order/src/models/adjustment-line.ts @@ -49,4 +49,7 @@ export default abstract class AdjustmentLine { defaultRaw: "now()", }) updated_at: Date + + @Property({ columnType: "timestamptz", nullable: true }) + deleted_at: Date | null = null } diff --git a/packages/modules/order/src/models/line-item.ts b/packages/modules/order/src/models/line-item.ts index 76d0349347..8e0a456cb4 100644 --- a/packages/modules/order/src/models/line-item.ts +++ b/packages/modules/order/src/models/line-item.ts @@ -1,6 +1,7 @@ import { BigNumberRawValue, DAL } from "@medusajs/types" import { BigNumber, + DALUtils, MikroOrmBigNumberProperty, createPsqlIndexStatementHelper, generateEntityId, @@ -10,6 +11,7 @@ import { Cascade, Collection, Entity, + Filter, OnInit, OneToMany, OptionalProps, @@ -22,17 +24,26 @@ import LineItemTaxLine from "./line-item-tax-line" type OptionalLineItemProps = DAL.ModelDateColumns +const DeletedAtIndex = createPsqlIndexStatementHelper({ + tableName: "order_line_item", + columns: "deleted_at", + where: "deleted_at IS NOT NULL", +}) + const ProductIdIndex = createPsqlIndexStatementHelper({ tableName: "order_line_item", columns: "product_id", + where: "deleted_at IS NOT NULL", }) const VariantIdIndex = createPsqlIndexStatementHelper({ tableName: "order_line_item", columns: "variant_id", + where: "deleted_at IS NOT NULL", }) @Entity({ tableName: "order_line_item" }) +@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions) export default class LineItem { [OptionalProps]?: OptionalLineItemProps @@ -145,6 +156,10 @@ export default class LineItem { }) updated_at: Date + @Property({ columnType: "timestamptz", nullable: true }) + @DeletedAtIndex.MikroORMIndex() + deleted_at: Date | null = null + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "ordli") diff --git a/packages/modules/order/src/models/shipping-method.ts b/packages/modules/order/src/models/shipping-method.ts index 2d4d55e1fa..339aef974f 100644 --- a/packages/modules/order/src/models/shipping-method.ts +++ b/packages/modules/order/src/models/shipping-method.ts @@ -71,7 +71,7 @@ export default class ShippingMethod { () => ShippingMethodTaxLine, (taxLine) => taxLine.shipping_method, { - cascade: [Cascade.PERSIST, "soft-remove"] as any, + cascade: [Cascade.PERSIST, "soft-remove" as Cascade], } ) tax_lines = new Collection>(this) @@ -80,7 +80,7 @@ export default class ShippingMethod { () => ShippingMethodAdjustment, (adjustment) => adjustment.shipping_method, { - cascade: [Cascade.PERSIST, "soft-remove"] as any, + cascade: [Cascade.PERSIST, "soft-remove" as Cascade], } ) adjustments = new Collection>(this) diff --git a/packages/modules/order/src/models/tax-line.ts b/packages/modules/order/src/models/tax-line.ts index 95180f5332..74933ad31b 100644 --- a/packages/modules/order/src/models/tax-line.ts +++ b/packages/modules/order/src/models/tax-line.ts @@ -45,4 +45,7 @@ export default abstract class TaxLine { defaultRaw: "now()", }) updated_at: Date + + @Property({ columnType: "timestamptz", nullable: true }) + deleted_at: Date | null = null } diff --git a/packages/modules/order/src/services/order-module-service.ts b/packages/modules/order/src/services/order-module-service.ts index 83d240526e..3814936539 100644 --- a/packages/modules/order/src/services/order-module-service.ts +++ b/packages/modules/order/src/services/order-module-service.ts @@ -2124,8 +2124,6 @@ export default class OrderModuleService< }) } - // TODO - add new shipping methods - const calcOrder = calculated.order decorateCartTotals(calcOrder as DecorateCartLikeInputDTO)