diff --git a/integration-tests/http/__tests__/returns/returns.spec.ts b/integration-tests/http/__tests__/returns/returns.spec.ts index c74fd65cd8..8a6ef50401 100644 --- a/integration-tests/http/__tests__/returns/returns.spec.ts +++ b/integration-tests/http/__tests__/returns/returns.spec.ts @@ -632,6 +632,83 @@ medusaIntegrationTestRunner({ internal_note: "Test internal note", }) ) + + const item = order.items[0] + result = await api.post( + `/admin/returns/${returnId}/receive-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({ + quantity: 2, + return_requested_quantity: 1, + return_received_quantity: 1, + return_dismissed_quantity: 0, + written_off_quantity: 0, + }), + }), + ]), + }) + ) + + result = await api.post( + `/admin/returns/${returnId}/dismiss-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({ + quantity: 2, + return_requested_quantity: 0, + return_received_quantity: 1, + return_dismissed_quantity: 1, + written_off_quantity: 1, + }), + }), + ]), + }) + ) + + result = await api.post( + `/admin/returns/${returnId}/receive/confirm`, + {}, + adminHeaders + ) + + expect(result.data.return).toEqual( + expect.objectContaining({ + items: [ + expect.objectContaining({ + received_quantity: 2, + }), + ], + }) + ) }) }) }) diff --git a/packages/core/core-flows/src/order/steps/create-return-items.ts b/packages/core/core-flows/src/order/steps/create-return-items.ts index c55f8e608a..6ed1fd745e 100644 --- a/packages/core/core-flows/src/order/steps/create-return-items.ts +++ b/packages/core/core-flows/src/order/steps/create-return-items.ts @@ -11,7 +11,7 @@ type CreateReturnItemsInput = { returnId: string } -export const createReturnItems = createStep( +export const createReturnItemsStep = createStep( "create-return-items", async (input: CreateReturnItemsInput, { container }) => { const orderModuleService = container.resolve( diff --git a/packages/core/core-flows/src/order/steps/index.ts b/packages/core/core-flows/src/order/steps/index.ts index 1ee4143fff..874c454d67 100644 --- a/packages/core/core-flows/src/order/steps/index.ts +++ b/packages/core/core-flows/src/order/steps/index.ts @@ -26,5 +26,6 @@ export * from "./register-shipment" export * from "./set-tax-lines-for-items" export * from "./update-order-change-actions" export * from "./update-order-exchanges" +export * from "./update-return-items" export * from "./update-shipping-methods" export * from "./update-tax-lines" diff --git a/packages/core/core-flows/src/order/steps/update-return-items.ts b/packages/core/core-flows/src/order/steps/update-return-items.ts new file mode 100644 index 0000000000..0f1ccf1927 --- /dev/null +++ b/packages/core/core-flows/src/order/steps/update-return-items.ts @@ -0,0 +1,39 @@ +import { + ModuleRegistrationName, + getSelectsAndRelationsFromObjectArray, +} from "@medusajs/utils" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +interface UpdateReturnItemBySelector { + id: string + [key: string]: any +} + +export const updateReturnItemsStepId = "update-return-items" +export const updateReturnItemsStep = createStep( + updateReturnItemsStepId, + async (data: UpdateReturnItemBySelector[], { container }) => { + const service = container.resolve(ModuleRegistrationName.ORDER) as any + + const { selects, relations } = getSelectsAndRelationsFromObjectArray(data, { + objectFields: ["metadata"], + }) + const dataBeforeUpdate = await service.listReturnItems( + { id: data.map((d) => d.id) }, + { relations, select: selects } + ) + + const updated = await service.updateReturnItems(data) + + return new StepResponse(updated, dataBeforeUpdate) + }, + async (dataBeforeUpdate, { container }) => { + if (!dataBeforeUpdate?.length) { + return + } + + const service = container.resolve(ModuleRegistrationName.ORDER) as any + + await service.updateReturnItems(dataBeforeUpdate) + } +) diff --git a/packages/core/core-flows/src/order/steps/update-shipping-methods.ts b/packages/core/core-flows/src/order/steps/update-shipping-methods.ts index 6f628c8f90..c3b1c6805d 100644 --- a/packages/core/core-flows/src/order/steps/update-shipping-methods.ts +++ b/packages/core/core-flows/src/order/steps/update-shipping-methods.ts @@ -19,7 +19,7 @@ export const updateOrderShippingMethodsStep = createStep( const { selects, relations } = getSelectsAndRelationsFromObjectArray(data, { objectFields: ["metadata"], }) - const dataBeforeUpdate = await service.listOrderClaims( + const dataBeforeUpdate = await service.listShippingMethods( { id: data.map((d) => d.id) }, { relations, select: selects } ) diff --git a/packages/core/core-flows/src/order/utils/order-validation.ts b/packages/core/core-flows/src/order/utils/order-validation.ts index b544b84913..c1c74dd1c0 100644 --- a/packages/core/core-flows/src/order/utils/order-validation.ts +++ b/packages/core/core-flows/src/order/utils/order-validation.ts @@ -84,7 +84,7 @@ export function throwIfItemsDoesNotExistsInReturn({ orderReturn: Pick inputItems: OrderWorkflow.CreateOrderFulfillmentWorkflowInput["items"] }) { - const orderReturnItemIds = orderReturn.items?.map((i) => i.id) ?? [] + const orderReturnItemIds = orderReturn.items?.map((i: any) => i.item_id) ?? [] const inputItemIds = inputItems.map((i) => i.id) const diff = arrayDifference(inputItemIds, orderReturnItemIds) diff --git a/packages/core/core-flows/src/order/workflows/index.ts b/packages/core/core-flows/src/order/workflows/index.ts index 7025a51b7b..aab0a619b6 100644 --- a/packages/core/core-flows/src/order/workflows/index.ts +++ b/packages/core/core-flows/src/order/workflows/index.ts @@ -25,13 +25,17 @@ export * from "./return/begin-return" export * from "./return/cancel-receive-return" export * from "./return/cancel-request-return" export * from "./return/cancel-return" +export * from "./return/confirm-receive-return-request" export * from "./return/confirm-return-request" export * from "./return/create-complete-return" export * from "./return/create-return-shipping-method" +export * from "./return/dismiss-item-return-request" export * from "./return/receive-complete-return" -export * from "./return/remove-request-item-return" +export * from "./return/receive-item-return-request" +export * from "./return/remove-item-return-action" export * from "./return/remove-return-shipping-method" export * from "./return/request-item-return" +export * from "./return/update-receive-item-return-request" export * from "./return/update-request-item-return" export * from "./return/update-return-shipping-method" export * from "./update-order-change-actions" diff --git a/packages/core/core-flows/src/order/workflows/return/confirm-receive-return-request.ts b/packages/core/core-flows/src/order/workflows/return/confirm-receive-return-request.ts new file mode 100644 index 0000000000..723df5091c --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/return/confirm-receive-return-request.ts @@ -0,0 +1,126 @@ +import { + BigNumberInput, + OrderChangeDTO, + OrderDTO, + ReturnDTO, +} from "@medusajs/types" +import { ChangeActionType, MathBN, OrderChangeStatus } from "@medusajs/utils" +import { + WorkflowData, + createStep, + createWorkflow, + transform, +} from "@medusajs/workflows-sdk" +import { useRemoteQueryStep } from "../../../common" +import { previewOrderChangeStep, updateReturnItemsStep } from "../../steps" +import { confirmOrderChanges } from "../../steps/confirm-order-changes" +import { + throwIfIsCancelled, + throwIfOrderChangeIsNotActive, +} from "../../utils/order-validation" + +type WorkflowInput = { + return_id: string +} + +const validationStep = createStep( + "validate-confirm-return-receive", + async function ({ + order, + orderChange, + orderReturn, + }: { + order: OrderDTO + orderReturn: ReturnDTO + orderChange: OrderChangeDTO + }) { + throwIfIsCancelled(order, "Order") + throwIfIsCancelled(orderReturn, "Return") + throwIfOrderChangeIsNotActive({ orderChange }) + } +) + +export const confirmReturnReceiveWorkflowId = "confirm-return-receive" +export const confirmReturnReceiveWorkflow = createWorkflow( + confirmReturnReceiveWorkflowId, + function (input: WorkflowInput): WorkflowData { + const orderReturn: ReturnDTO = useRemoteQueryStep({ + entry_point: "return", + fields: ["id", "status", "order_id", "canceled_at", "items.*"], + variables: { id: input.return_id }, + list: false, + throw_if_key_not_found: true, + }) + + const order: OrderDTO = useRemoteQueryStep({ + entry_point: "orders", + fields: ["id", "version", "canceled_at"], + 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", + "actions.id", + "actions.action", + "actions.details", + "actions.reference", + "actions.reference_id", + "actions.internal_note", + ], + variables: { + filters: { + order_id: orderReturn.order_id, + return_id: orderReturn.id, + status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED], + }, + }, + list: false, + }).config({ name: "order-change-query" }) + + const updateReturnItem = transform({ orderChange, orderReturn }, (data) => { + const retItems = data.orderReturn.items! + const received = data.orderChange.actions.filter((act) => + [ + ChangeActionType.RECEIVE_RETURN_ITEM, + ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM, + ].includes(act.action as ChangeActionType) + ) + + const itemMap = retItems.reduce((acc, item: any) => { + acc[item.item_id] = item.id + return acc + }, {}) + + const itemUpdates = {} + received.forEach((act) => { + const itemId = act.details!.reference_id as string + if (itemUpdates[itemId]) { + itemUpdates[itemId].received_quantity = MathBN.add( + itemUpdates[itemId].received_quantity, + act.details!.quantity as BigNumberInput + ) + return + } + + itemUpdates[itemId] = { + id: itemMap[itemId], + received_quantity: act.details!.quantity, + } + }) + + return Object.values(itemUpdates) as any + }) + + validationStep({ order, orderReturn, orderChange }) + + updateReturnItemsStep(updateReturnItem) + + confirmOrderChanges({ changes: [orderChange], orderId: order.id }) + + return previewOrderChangeStep(order.id) + } +) diff --git a/packages/core/core-flows/src/order/workflows/return/confirm-return-request.ts b/packages/core/core-flows/src/order/workflows/return/confirm-return-request.ts index 4485d3cc77..88763d9923 100644 --- a/packages/core/core-flows/src/order/workflows/return/confirm-return-request.ts +++ b/packages/core/core-flows/src/order/workflows/return/confirm-return-request.ts @@ -9,7 +9,7 @@ import { import { useRemoteQueryStep } from "../../../common" import { previewOrderChangeStep } from "../../steps" import { confirmOrderChanges } from "../../steps/confirm-order-changes" -import { createReturnItems } from "../../steps/create-return-items" +import { createReturnItemsStep } from "../../steps/create-return-items" import { throwIfIsCancelled, throwIfOrderChangeIsNotActive, @@ -20,7 +20,7 @@ type WorkflowInput = { } const validationStep = createStep( - "validate-create-return-shipping-method", + "validate-confirm-return-request", async function ({ order, orderChange, @@ -85,7 +85,10 @@ export const confirmReturnRequestWorkflow = createWorkflow( validationStep({ order, orderReturn, orderChange }) - createReturnItems({ returnId: orderReturn.id, changes: returnItemActions }) + createReturnItemsStep({ + returnId: orderReturn.id, + changes: returnItemActions, + }) confirmOrderChanges({ changes: [orderChange], orderId: order.id }) diff --git a/packages/core/core-flows/src/order/workflows/return/dismiss-item-return-request.ts b/packages/core/core-flows/src/order/workflows/return/dismiss-item-return-request.ts new file mode 100644 index 0000000000..f35f00fc4d --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/return/dismiss-item-return-request.ts @@ -0,0 +1,107 @@ +import { + OrderChangeDTO, + OrderDTO, + OrderWorkflow, + ReturnDTO, +} from "@medusajs/types" +import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils" +import { + WorkflowData, + createStep, + createWorkflow, + transform, +} from "@medusajs/workflows-sdk" +import { useRemoteQueryStep } from "../../../common" +import { previewOrderChangeStep } from "../../steps" +import { createOrderChangeActionsStep } from "../../steps/create-order-change-actions" +import { + throwIfIsCancelled, + throwIfItemsDoesNotExistsInReturn, + throwIfOrderChangeIsNotActive, +} from "../../utils/order-validation" + +const validationStep = createStep( + "dismiss-item-return-request-validation", + async function ( + { + order, + orderChange, + orderReturn, + items, + }: { + order: Pick + orderReturn: ReturnDTO + orderChange: OrderChangeDTO + items: OrderWorkflow.ReceiveOrderReturnItemsWorkflowInput["items"] + }, + context + ) { + throwIfIsCancelled(order, "Order") + throwIfIsCancelled(orderReturn, "Return") + throwIfOrderChangeIsNotActive({ orderChange }) + throwIfItemsDoesNotExistsInReturn({ orderReturn, inputItems: items }) + } +) + +export const dismissItemReturnRequestWorkflowId = "dismiss-item-return-request" +export const dismissItemReturnRequestWorkflow = createWorkflow( + dismissItemReturnRequestWorkflowId, + function ( + input: WorkflowData + ): WorkflowData { + const orderReturn: ReturnDTO = useRemoteQueryStep({ + entry_point: "return", + fields: ["id", "status", "order_id", "canceled_at", "items.*"], + variables: { id: input.return_id }, + list: false, + throw_if_key_not_found: true, + }) + + const order: OrderDTO = useRemoteQueryStep({ + entry_point: "orders", + fields: ["id", "status", "canceled_at"], + 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", "order_id", "return_id"], + 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, items: input.items, orderReturn, orderChange }) + + const orderChangeActionInput = transform( + { order, orderChange, orderReturn, items: input.items }, + ({ order, orderChange, orderReturn, items }) => { + return items.map((item) => ({ + order_change_id: orderChange.id, + order_id: order.id, + return_id: orderReturn.id, + version: orderChange.version, + action: ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM, + internal_note: item.internal_note, + reference: "return", + reference_id: orderReturn.id, + details: { + reference_id: item.id, + quantity: item.quantity, + }, + })) + } + ) + + createOrderChangeActionsStep(orderChangeActionInput) + + return previewOrderChangeStep(order.id) + } +) diff --git a/packages/core/core-flows/src/order/workflows/return/receive-item-return-request.ts b/packages/core/core-flows/src/order/workflows/return/receive-item-return-request.ts new file mode 100644 index 0000000000..47b0b3ce74 --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/return/receive-item-return-request.ts @@ -0,0 +1,107 @@ +import { + OrderChangeDTO, + OrderDTO, + OrderWorkflow, + ReturnDTO, +} from "@medusajs/types" +import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils" +import { + WorkflowData, + createStep, + createWorkflow, + transform, +} from "@medusajs/workflows-sdk" +import { useRemoteQueryStep } from "../../../common" +import { previewOrderChangeStep } from "../../steps" +import { createOrderChangeActionsStep } from "../../steps/create-order-change-actions" +import { + throwIfIsCancelled, + throwIfItemsDoesNotExistsInReturn, + throwIfOrderChangeIsNotActive, +} from "../../utils/order-validation" + +const validationStep = createStep( + "receive-item-return-request-validation", + async function ( + { + order, + orderChange, + orderReturn, + items, + }: { + order: Pick + orderReturn: ReturnDTO + orderChange: OrderChangeDTO + items: OrderWorkflow.ReceiveOrderReturnItemsWorkflowInput["items"] + }, + context + ) { + throwIfIsCancelled(order, "Order") + throwIfIsCancelled(orderReturn, "Return") + throwIfOrderChangeIsNotActive({ orderChange }) + throwIfItemsDoesNotExistsInReturn({ orderReturn, inputItems: items }) + } +) + +export const receiveItemReturnRequestWorkflowId = "receive-item-return-request" +export const receiveItemReturnRequestWorkflow = createWorkflow( + receiveItemReturnRequestWorkflowId, + function ( + input: WorkflowData + ): WorkflowData { + const orderReturn: ReturnDTO = useRemoteQueryStep({ + entry_point: "return", + fields: ["id", "status", "order_id", "canceled_at", "items.*"], + variables: { id: input.return_id }, + list: false, + throw_if_key_not_found: true, + }) + + const order: OrderDTO = useRemoteQueryStep({ + entry_point: "orders", + fields: ["id", "status", "canceled_at"], + 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", "order_id", "return_id"], + 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, items: input.items, orderReturn, orderChange }) + + const orderChangeActionInput = transform( + { order, orderChange, orderReturn, items: input.items }, + ({ order, orderChange, orderReturn, items }) => { + return items.map((item) => ({ + order_change_id: orderChange.id, + order_id: order.id, + return_id: orderReturn.id, + version: orderChange.version, + action: ChangeActionType.RECEIVE_RETURN_ITEM, + internal_note: item.internal_note, + reference: "return", + reference_id: orderReturn.id, + details: { + reference_id: item.id, + quantity: item.quantity, + }, + })) + } + ) + + createOrderChangeActionsStep(orderChangeActionInput) + + return previewOrderChangeStep(order.id) + } +) diff --git a/packages/core/core-flows/src/order/workflows/return/remove-request-item-return.ts b/packages/core/core-flows/src/order/workflows/return/remove-item-return-action.ts similarity index 92% rename from packages/core/core-flows/src/order/workflows/return/remove-request-item-return.ts rename to packages/core/core-flows/src/order/workflows/return/remove-item-return-action.ts index db32486069..f1492843d3 100644 --- a/packages/core/core-flows/src/order/workflows/return/remove-request-item-return.ts +++ b/packages/core/core-flows/src/order/workflows/return/remove-item-return-action.ts @@ -22,7 +22,7 @@ import { } from "../../utils/order-validation" const validationStep = createStep( - "remove-request-item-return-validation", + "remove-item-return-action-validation", async function ({ order, orderChange, @@ -54,9 +54,9 @@ const validationStep = createStep( } ) -export const removeRequestItemReturnWorkflowId = "remove-request-item-return" -export const removeRequestItemReturnWorkflow = createWorkflow( - removeRequestItemReturnWorkflowId, +export const removeItemReturnActionWorkflowId = "remove-item-return-action" +export const removeItemReturnActionWorkflow = createWorkflow( + removeItemReturnActionWorkflowId, function ( input: WorkflowData ): WorkflowData { diff --git a/packages/core/core-flows/src/order/workflows/return/update-receive-item-return-request.ts b/packages/core/core-flows/src/order/workflows/return/update-receive-item-return-request.ts new file mode 100644 index 0000000000..9a5f58cbd6 --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/return/update-receive-item-return-request.ts @@ -0,0 +1,126 @@ +import { + OrderChangeActionDTO, + OrderChangeDTO, + OrderDTO, + OrderWorkflow, + ReturnDTO, +} from "@medusajs/types" +import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils" +import { + WorkflowData, + createStep, + createWorkflow, + transform, +} from "@medusajs/workflows-sdk" +import { useRemoteQueryStep } from "../../../common" +import { + previewOrderChangeStep, + updateOrderChangeActionsStep, +} from "../../steps" +import { + throwIfIsCancelled, + throwIfOrderChangeIsNotActive, +} from "../../utils/order-validation" + +const validationStep = createStep( + "update-receive-item-return-request-validation", + async function ( + { + order, + orderChange, + orderReturn, + input, + }: { + order: OrderDTO + orderReturn: ReturnDTO + orderChange: OrderChangeDTO + input: OrderWorkflow.UpdateReceiveItemReturnRequestWorkflowInput + }, + context + ) { + 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 ( + [ + ChangeActionType.RECEIVE_RETURN_ITEM, + ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM, + ].includes(associatedAction.action as ChangeActionType) + ) { + throw new Error( + `Action ${associatedAction.id} is not receiving an item return request` + ) + } + } +) + +export const updateReceiveItemReturnRequestWorkflowId = + "update-receive-item-return-request" +export const updateReceiveItemReturnRequestWorkflow = createWorkflow( + updateReceiveItemReturnRequestWorkflowId, + function ( + input: WorkflowData + ): WorkflowData { + const orderReturn: ReturnDTO = useRemoteQueryStep({ + entry_point: "return", + fields: ["id", "status", "order_id", "canceled_at"], + variables: { id: input.return_id }, + list: false, + throw_if_key_not_found: true, + }) + + const order: OrderDTO = useRemoteQueryStep({ + entry_point: "orders", + fields: ["id", "status", "canceled_at", "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 }) + + const updateData = transform( + { orderChange, input }, + ({ input, orderChange }) => { + const originalAction = (orderChange.actions ?? []).find( + (a) => a.id === input.action_id + ) as OrderChangeActionDTO + + const data = input.data + return { + id: input.action_id, + details: { + quantity: data.quantity ?? originalAction.details?.quantity, + }, + internal_note: data.internal_note, + } + } + ) + + updateOrderChangeActionsStep([updateData]) + + return previewOrderChangeStep(order.id) + } +) diff --git a/packages/core/types/src/workflow/order/receive-return.ts b/packages/core/types/src/workflow/order/receive-return.ts index 4464852330..8a3683ec09 100644 --- a/packages/core/types/src/workflow/order/receive-return.ts +++ b/packages/core/types/src/workflow/order/receive-return.ts @@ -28,3 +28,12 @@ export interface ReceiveCompleteOrderReturnWorkflowInput { internal_note?: string metadata?: Record | null } + +export interface UpdateReceiveItemReturnRequestWorkflowInput { + return_id: string + action_id: string + data: { + quantity?: BigNumberInput + internal_note?: string | null + } +} diff --git a/packages/core/utils/src/totals/__tests__/totals.ts b/packages/core/utils/src/totals/__tests__/totals.ts index f0c6df69db..19295cdad6 100644 --- a/packages/core/utils/src/totals/__tests__/totals.ts +++ b/packages/core/utils/src/totals/__tests__/totals.ts @@ -647,7 +647,7 @@ describe("Total calculation", function () { return_requested_quantity: 0, return_received_quantity: 1, return_dismissed_quantity: 1, - written_off_quantity: 0, + written_off_quantity: 1, }, tax_lines: [ { @@ -690,7 +690,7 @@ describe("Total calculation", function () { return_received_quantity: 1, return_requested_quantity: 0, shipped_quantity: 2, - written_off_quantity: 0, + written_off_quantity: 1, }, subtotal: 100, total: 88, @@ -704,7 +704,7 @@ describe("Total calculation", function () { return_requested_total: 0, return_received_total: 44, return_dismissed_total: 44, - write_off_total: 0, + write_off_total: 44, refundable_total: 0, refundable_total_per_unit: 0, }, @@ -727,7 +727,7 @@ describe("Total calculation", function () { return_requested_total: 0, return_received_total: 44, return_dismissed_total: 44, - write_off_total: 0, + write_off_total: 44, }) }) }) diff --git a/packages/medusa/src/api/admin/returns/[id]/dismiss-items/[action_id]/route.ts b/packages/medusa/src/api/admin/returns/[id]/dismiss-items/[action_id]/route.ts new file mode 100644 index 0000000000..a8e09f16fa --- /dev/null +++ b/packages/medusa/src/api/admin/returns/[id]/dismiss-items/[action_id]/route.ts @@ -0,0 +1,85 @@ +import { + removeItemReturnActionWorkflow, + updateReceiveItemReturnRequestWorkflow, +} from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../../types/routing" +import { AdminPostReturnsDismissItemsActionReqSchemaType } from "../../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id, action_id } = req.params + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const { result } = await updateReceiveItemReturnRequestWorkflow( + req.scope + ).run({ + input: { + data: { ...req.validatedBody }, + 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: result, + return: orderReturn, + }) +} + +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 removeItemReturnActionWorkflow( + 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/[id]/dismiss-items/route.ts b/packages/medusa/src/api/admin/returns/[id]/dismiss-items/route.ts new file mode 100644 index 0000000000..716b741b5a --- /dev/null +++ b/packages/medusa/src/api/admin/returns/[id]/dismiss-items/route.ts @@ -0,0 +1,41 @@ +import { dismissItemReturnRequestWorkflow } from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { AdminPostReturnsReceiveItemsReqSchemaType } from "../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id } = req.params + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const { result } = await dismissItemReturnRequestWorkflow(req.scope).run({ + input: { ...req.validatedBody, return_id: id }, + }) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return", + variables: { + id, + filters: { + ...req.filterableFields, + }, + }, + fields: req.remoteQueryConfig.fields, + }) + + const [orderReturn] = await remoteQuery(queryObject) + + res.json({ + order_preview: result, + return: orderReturn, + }) +} diff --git a/packages/medusa/src/api/admin/returns/[id]/receive-items/[action_id]/route.ts b/packages/medusa/src/api/admin/returns/[id]/receive-items/[action_id]/route.ts new file mode 100644 index 0000000000..d5ad4867d2 --- /dev/null +++ b/packages/medusa/src/api/admin/returns/[id]/receive-items/[action_id]/route.ts @@ -0,0 +1,85 @@ +import { + removeItemReturnActionWorkflow, + updateReceiveItemReturnRequestWorkflow, +} from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../../types/routing" +import { AdminPostReturnsReceiveItemsActionReqSchemaType } from "../../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id, action_id } = req.params + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const { result } = await updateReceiveItemReturnRequestWorkflow( + req.scope + ).run({ + input: { + data: { ...req.validatedBody }, + 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: result, + return: orderReturn, + }) +} + +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 removeItemReturnActionWorkflow( + 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/[id]/receive-items/route.ts b/packages/medusa/src/api/admin/returns/[id]/receive-items/route.ts new file mode 100644 index 0000000000..296381910b --- /dev/null +++ b/packages/medusa/src/api/admin/returns/[id]/receive-items/route.ts @@ -0,0 +1,41 @@ +import { receiveItemReturnRequestWorkflow } from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { AdminPostReturnsReceiveItemsReqSchemaType } from "../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id } = req.params + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const { result } = await receiveItemReturnRequestWorkflow(req.scope).run({ + input: { ...req.validatedBody, return_id: id }, + }) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return", + variables: { + id, + filters: { + ...req.filterableFields, + }, + }, + fields: req.remoteQueryConfig.fields, + }) + + const [orderReturn] = await remoteQuery(queryObject) + + res.json({ + order_preview: result, + return: orderReturn, + }) +} diff --git a/packages/medusa/src/api/admin/returns/[id]/receive/confirm/route.ts b/packages/medusa/src/api/admin/returns/[id]/receive/confirm/route.ts new file mode 100644 index 0000000000..478470d01c --- /dev/null +++ b/packages/medusa/src/api/admin/returns/[id]/receive/confirm/route.ts @@ -0,0 +1,41 @@ +import { confirmReturnReceiveWorkflow } from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../../types/routing" +import { AdminPostReturnsConfirmRequestReqSchemaType } from "../../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id } = req.params + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const { result } = await confirmReturnReceiveWorkflow(req.scope).run({ + input: { return_id: id }, + }) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return", + variables: { + id, + filters: { + ...req.filterableFields, + }, + }, + fields: req.remoteQueryConfig.fields, + }) + + const [orderReturn] = await remoteQuery(queryObject) + + res.json({ + order_preview: result, + return: orderReturn, + }) +} 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 index 9ae224fc53..4ebf1d7710 100644 --- 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 @@ -1,5 +1,5 @@ import { - removeRequestItemReturnWorkflow, + removeItemReturnActionWorkflow, updateRequestItemReturnWorkflow, } from "@medusajs/core-flows" import { @@ -55,7 +55,7 @@ export const DELETE = async ( const { id, action_id } = req.params - const { result: orderPreview } = await removeRequestItemReturnWorkflow( + const { result: orderPreview } = await removeItemReturnActionWorkflow( req.scope ).run({ input: { diff --git a/packages/medusa/src/api/admin/returns/middlewares.ts b/packages/medusa/src/api/admin/returns/middlewares.ts index 1824882142..b7cdd7f92c 100644 --- a/packages/medusa/src/api/admin/returns/middlewares.ts +++ b/packages/medusa/src/api/admin/returns/middlewares.ts @@ -5,6 +5,7 @@ import * as QueryConfig from "./query-config" import { AdminGetOrdersOrderParams, AdminGetOrdersParams, + AdminPostReceiveReturnItemsReqSchema, AdminPostReceiveReturnsReqSchema, AdminPostReturnsConfirmRequestReqSchema, AdminPostReturnsReqSchema, @@ -142,4 +143,79 @@ export const adminReturnRoutesMiddlewares: MiddlewareRoute[] = [ matcher: "/admin/returns/:id/receive", middlewares: [], }, + { + method: ["POST"], + matcher: "/admin/returns/:id/receive/confirm", + middlewares: [ + validateAndTransformBody(AdminPostReturnsConfirmRequestReqSchema), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/returns/:id/receive-items", + middlewares: [ + validateAndTransformBody(AdminPostReceiveReturnItemsReqSchema), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/returns/:id/receive-items/:action_id", + middlewares: [ + validateAndTransformBody(AdminPostReturnsRequestItemsActionReqSchema), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["DELETE"], + matcher: "/admin/returns/:id/receive-items/:action_id", + middlewares: [ + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/returns/:id/dismiss-items", + middlewares: [ + validateAndTransformBody(AdminPostReceiveReturnItemsReqSchema), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/returns/:id/dismiss-items/:action_id", + middlewares: [ + validateAndTransformBody(AdminPostReturnsRequestItemsActionReqSchema), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["DELETE"], + matcher: "/admin/returns/:id/dismiss-items/:action_id", + middlewares: [ + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, ] diff --git a/packages/medusa/src/api/admin/returns/validators.ts b/packages/medusa/src/api/admin/returns/validators.ts index 5f4de96ea8..54e42ace71 100644 --- a/packages/medusa/src/api/admin/returns/validators.ts +++ b/packages/medusa/src/api/admin/returns/validators.ts @@ -140,6 +140,21 @@ export type AdminPostReturnsRequestItemsReqSchemaType = z.infer< typeof AdminPostReturnsRequestItemsReqSchema > +export const AdminPostReturnsReceiveItemsReqSchema = z.object({ + items: z.array( + z.object({ + id: z.string(), + quantity: z.number(), + description: z.string().optional(), + internal_note: z.string().optional(), + }) + ), +}) + +export type AdminPostReturnsReceiveItemsReqSchemaType = z.infer< + typeof AdminPostReturnsReceiveItemsReqSchema +> + export const AdminPostReturnsRequestItemsActionReqSchema = z.object({ quantity: z.number().optional(), internal_note: z.string().nullish().optional(), @@ -151,6 +166,24 @@ export type AdminPostReturnsRequestItemsActionReqSchemaType = z.infer< typeof AdminPostReturnsRequestItemsActionReqSchema > +export const AdminPostReturnsReceiveItemsActionReqSchema = z.object({ + quantity: z.number().optional(), + internal_note: z.string().nullish().optional(), +}) + +export type AdminPostReturnsReceiveItemsActionReqSchemaType = z.infer< + typeof AdminPostReturnsReceiveItemsActionReqSchema +> + +export const AdminPostReturnsDismissItemsActionReqSchema = z.object({ + quantity: z.number().optional(), + internal_note: z.string().nullish().optional(), +}) + +export type AdminPostReturnsDismissItemsActionReqSchemaType = z.infer< + typeof AdminPostReturnsDismissItemsActionReqSchema +> + export const AdminPostReturnsConfirmRequestReqSchema = z.object({ no_notification: z.boolean().optional(), }) diff --git a/packages/modules/order/integration-tests/__tests__/order-edit.ts b/packages/modules/order/integration-tests/__tests__/order-edit.ts index 77b1a59e45..212f3a18e5 100644 --- a/packages/modules/order/integration-tests/__tests__/order-edit.ts +++ b/packages/modules/order/integration-tests/__tests__/order-edit.ts @@ -328,7 +328,7 @@ moduleIntegrationTestRunner({ return_requested_quantity: 0, return_received_quantity: 0, return_dismissed_quantity: 1, - written_off_quantity: 0, + written_off_quantity: 1, }), }), ]) @@ -474,7 +474,7 @@ moduleIntegrationTestRunner({ return_requested_quantity: 0, return_received_quantity: 0, return_dismissed_quantity: 1, - written_off_quantity: 0, + written_off_quantity: 1, }), }), ]) diff --git a/packages/modules/order/src/services/__tests__/util/actions/returns.ts b/packages/modules/order/src/services/__tests__/util/actions/returns.ts index 20c3ade784..13152e94e6 100644 --- a/packages/modules/order/src/services/__tests__/util/actions/returns.ts +++ b/packages/modules/order/src/services/__tests__/util/actions/returns.ts @@ -298,7 +298,7 @@ describe("Order Return - Actions", function () { return_requested_quantity: "0", return_received_quantity: "1", return_dismissed_quantity: "1", - written_off_quantity: 0, + written_off_quantity: "1", }, }, ]) diff --git a/packages/modules/order/src/utils/actions/receive-damaged-return-item.ts b/packages/modules/order/src/utils/actions/receive-damaged-return-item.ts index 1c859a8974..e78fea2eb2 100644 --- a/packages/modules/order/src/utils/actions/receive-damaged-return-item.ts +++ b/packages/modules/order/src/utils/actions/receive-damaged-return-item.ts @@ -35,6 +35,12 @@ OrderChangeProcessing.registerActionType( toReturn ) + existing.detail.written_off_quantity ??= 0 + existing.detail.written_off_quantity = MathBN.add( + existing.detail.written_off_quantity, + action.details.quantity + ) + setActionReference(existing, action, options) if (previousEvents) { @@ -70,6 +76,11 @@ OrderChangeProcessing.registerActionType( action.details.quantity ) + existing.detail.written_off_quantity = MathBN.sub( + existing.detail.written_off_quantity, + action.details.quantity + ) + unsetActionReference(existing, action) if (previousEvents) {