chore(order): cancel return (#7881)

This commit is contained in:
Carlos R. L. Rodrigues authored and GitHub committed 2024-07-02 06:52:58 -03:00
1 parent b3236ff31c
commit 07715e6b50
46 files changed
+1333 -115

No files matched your search

@@ -0,0 +1,29 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CancelOrderClaimDTO, IOrderModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type CancelOrderClaimStepInput = CancelOrderClaimDTO
export const cancelOrderClaimStepId = "cancel-order-claim"
export const cancelOrderClaimStep = createStep(
cancelOrderClaimStepId,
async (data: CancelOrderClaimStepInput, { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.cancelClaim(data)
return new StepResponse(void 0, data.return_id)
},
async (orderId, { container }) => {
if (!orderId) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.revertLastVersion(orderId)
}
)
@@ -0,0 +1,29 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CancelOrderExchangeDTO, IOrderModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type CancelOrderExchangeStepInput = CancelOrderExchangeDTO
export const cancelOrderExchangeStepId = "cancel-order-swap"
export const cancelOrderExchangeStep = createStep(
cancelOrderExchangeStepId,
async (data: CancelOrderExchangeStepInput, { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.cancelExchange(data)
return new StepResponse(void 0, data.return_id)
},
async (orderId, { container }) => {
if (!orderId) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.revertLastVersion(orderId)
}
)
@@ -0,0 +1,29 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CancelOrderReturnDTO, IOrderModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type CancelOrderReturnStepInput = CancelOrderReturnDTO
export const cancelOrderReturnStepId = "cancel-order-return"
export const cancelOrderReturnStep = createStep(
cancelOrderReturnStepId,
async (data: CancelOrderReturnStepInput, { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.cancelReturn(data)
return new StepResponse(void 0, data.return_id)
},
async (orderId, { container }) => {
if (!orderId) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.revertLastVersion(orderId)
}
)
@@ -1,5 +1,8 @@
export * from "./archive-orders" export * from "./archive-orders"
export * from "./cancel-claim"
export * from "./cancel-exchange"
export * from "./cancel-orders" export * from "./cancel-orders"
export * from "./cancel-return"
export * from "./complete-orders" export * from "./complete-orders"
export * from "./create-orders" export * from "./create-orders"
export * from "./get-item-tax-lines" export * from "./get-item-tax-lines"
@@ -60,7 +60,7 @@ const validateOrder = createStep(
pred: (obj: any) => boolean, pred: (obj: any) => boolean,
type: string type: string
) => { ) => {
if (arr?.filter(pred).length) { if (arr?.some(pred)) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
`All ${type} must be canceled before canceling an order` `All ${type} must be canceled before canceling an order`
@@ -0,0 +1,83 @@
import {
FulfillmentDTO,
OrderWorkflow,
PaymentCollectionDTO,
ReturnDTO,
} from "@medusajs/types"
import { MathBN, MedusaError } from "@medusajs/utils"
import {
WorkflowData,
createStep,
createWorkflow,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../common"
import { cancelOrderReturnStep } from "../steps"
import { throwIfReturnIsCancelled } from "../utils/order-validation"
const validateOrder = createStep(
"validate-return",
({
orderReturn,
}: {
orderReturn: ReturnDTO
input: OrderWorkflow.CancelReturnWorkflowInput
}) => {
const orderReturn_ = orderReturn as ReturnDTO & {
payment_collections: PaymentCollectionDTO[]
fulfillments: FulfillmentDTO[]
}
throwIfReturnIsCancelled({ orderReturn })
const throwErrorIf = (
arr: unknown[],
pred: (obj: any) => boolean,
message: string
) => {
if (arr?.some(pred)) {
throw new MedusaError(MedusaError.Types.NOT_ALLOWED, message)
}
}
const notCanceled = (o) => !o.canceled_at
const hasReceived = (o) => MathBN.gt(o.received_quantity, 0)
throwErrorIf(
orderReturn_.fulfillments,
notCanceled,
"All fulfillments must be canceled before canceling a return"
)
throwErrorIf(
orderReturn_.items!,
hasReceived,
"Can't cancel a return which has returned items"
)
}
)
export const cancelReturnWorkflowId = "cancel-return"
export const cancelReturnWorkflow = createWorkflow(
cancelReturnWorkflowId,
(
input: WorkflowData<OrderWorkflow.CancelReturnWorkflowInput>
): WorkflowData<void> => {
const orderReturn: ReturnDTO & { fulfillments: FulfillmentDTO[] } =
useRemoteQueryStep({
entry_point: "return",
fields: [
"id",
"items.id",
"items.received_quantity",
"fulfillments.canceled_at",
],
variables: { id: input.return_id },
list: false,
throw_if_key_not_found: true,
})
validateOrder({ orderReturn, input })
cancelOrderReturnStep({ return_id: orderReturn.id })
}
)
@@ -97,9 +97,13 @@ function prepareShippingMethodData({
returnShippingOption, returnShippingOption,
}: { }: {
orderId: string orderId: string
inputShippingOption: OrderWorkflow.CreateOrderReturnWorkflowInput["return_shipping"] inputShippingOption?: OrderWorkflow.CreateOrderReturnWorkflowInput["return_shipping"]
returnShippingOption: ShippingOptionDTO & WithCalculatedPrice returnShippingOption: ShippingOptionDTO & WithCalculatedPrice
}) { }) {
if (!inputShippingOption) {
return
}
const obj: CreateOrderShippingMethodDTO = { const obj: CreateOrderShippingMethodDTO = {
name: returnShippingOption.name, name: returnShippingOption.name,
order_id: orderId, order_id: orderId,
@@ -217,7 +221,7 @@ function prepareFulfillmentData({
input: { input: {
location_id: locationId, location_id: locationId,
provider_id: returnShippingOption.provider_id, provider_id: returnShippingOption.provider_id,
shipping_option_id: input.return_shipping.option_id, shipping_option_id: input.return_shipping?.option_id,
items: fulfillmentItems, items: fulfillmentItems,
labels: [] as FulfillmentWorkflow.CreateFulfillmentLabelWorkflowDTO[], labels: [] as FulfillmentWorkflow.CreateFulfillmentLabelWorkflowDTO[],
delivery_address: order.shipping_address ?? ({} as any), // TODO: should it be the stock location address? delivery_address: order.shipping_address ?? ({} as any), // TODO: should it be the stock location address?
@@ -235,11 +239,11 @@ function prepareReturnShippingOptionQueryVariables({
region_id?: string region_id?: string
} }
input: { input: {
return_shipping: OrderWorkflow.CreateOrderReturnWorkflowInput["return_shipping"] return_shipping?: OrderWorkflow.CreateOrderReturnWorkflowInput["return_shipping"]
} }
}) { }) {
const variables = { const variables = {
id: input.return_shipping.option_id, id: input.return_shipping?.option_id,
calculated_price: { calculated_price: {
context: { context: {
currency_code: order.currency_code, currency_code: order.currency_code,
@@ -1,6 +1,7 @@
export * from "./archive-orders" export * from "./archive-orders"
export * from "./cancel-order" export * from "./cancel-order"
export * from "./cancel-order-fulfillment" export * from "./cancel-order-fulfillment"
export * from "./cancel-return"
export * from "./complete-orders" export * from "./complete-orders"
export * from "./create-fulfillment" export * from "./create-fulfillment"
export * from "./create-orders" export * from "./create-orders"
+3 -1
View File
@@ -6,7 +6,7 @@ import { BigNumberInput, BigNumberRawValue, BigNumberValue } from "../totals"
export type ChangeActionType = export type ChangeActionType =
| "CANCEL" | "CANCEL"
| "CANCEL_RETURN" | "CANCEL_RETURN_ITEM"
| "FULFILL_ITEM" | "FULFILL_ITEM"
| "CANCEL_ITEM_FULFILLMENT" | "CANCEL_ITEM_FULFILLMENT"
| "ITEM_ADD" | "ITEM_ADD"
@@ -15,8 +15,10 @@ export type ChangeActionType =
| "RECEIVE_RETURN_ITEM" | "RECEIVE_RETURN_ITEM"
| "RETURN_ITEM" | "RETURN_ITEM"
| "SHIPPING_ADD" | "SHIPPING_ADD"
| "SHIPPING_REMOVE"
| "SHIP_ITEM" | "SHIP_ITEM"
| "WRITE_OFF_ITEM" | "WRITE_OFF_ITEM"
| "REINSTATE_ITEM"
export type OrderSummaryDTO = { export type OrderSummaryDTO = {
total: BigNumberValue total: BigNumberValue
+18 -3
View File
@@ -207,6 +207,9 @@ export interface UpdateOrderLineItemDTO
export interface CreateOrderShippingMethodDTO { export interface CreateOrderShippingMethodDTO {
name: string name: string
order_id: string order_id: string
return_id?: string
claim_id?: string
exchange_id?: string
version?: number version?: number
amount: BigNumberInput amount: BigNumberInput
shipping_option_id?: string shipping_option_id?: string
@@ -381,6 +384,7 @@ interface BaseOrderBundledItemActionsDTO {
internal_note?: string | null internal_note?: string | null
note?: string | null note?: string | null
metadata?: Record<string, unknown> | null metadata?: Record<string, unknown> | null
[key: string]: any
} }
interface BaseOrderBundledActionsDTO { interface BaseOrderBundledActionsDTO {
order_id: string order_id: string
@@ -424,6 +428,11 @@ export interface CreateOrderReturnDTO extends BaseOrderBundledActionsDTO {
no_notification?: boolean no_notification?: boolean
} }
export interface CancelOrderReturnDTO
extends Omit<BaseOrderBundledActionsDTO, "order_id"> {
return_id: string
}
export type OrderClaimType = "refund" | "replace" export type OrderClaimType = "refund" | "replace"
export type ClaimReason = export type ClaimReason =
| "missing_item" | "missing_item"
@@ -446,17 +455,23 @@ export interface CreateOrderClaimDTO extends BaseOrderBundledActionsDTO {
no_notification?: boolean no_notification?: boolean
} }
export interface CancelOrderClaimDTO
extends Omit<BaseOrderBundledActionsDTO, "order_id"> {
claim_id: string
}
export interface CreateOrderExchangeDTO extends BaseOrderBundledActionsDTO { export interface CreateOrderExchangeDTO extends BaseOrderBundledActionsDTO {
additional_items?: BaseOrderBundledItemActionsDTO[] additional_items?: BaseOrderBundledItemActionsDTO[]
shipping_methods?: Omit<CreateOrderShippingMethodDTO, "order_id">[] | string[] shipping_methods?: Omit<CreateOrderShippingMethodDTO, "order_id">[] | string[]
return_shipping: Omit<CreateOrderShippingMethodDTO, "order_id"> | string return_shipping?: Omit<CreateOrderShippingMethodDTO, "order_id"> | string
difference_due?: BigNumberInput difference_due?: BigNumberInput
allow_backorder?: boolean allow_backorder?: boolean
no_notification?: boolean no_notification?: boolean
} }
export interface CancelOrderReturnDTO { export interface CancelOrderExchangeDTO
return_id: string extends Omit<BaseOrderBundledActionsDTO, "order_id"> {
exchange_id: string
} }
export interface ReceiveOrderReturnDTO export interface ReceiveOrderReturnDTO
+15 -4
View File
@@ -33,7 +33,10 @@ import {
} from "./common" } from "./common"
import { import {
CancelOrderChangeDTO, CancelOrderChangeDTO,
CancelOrderClaimDTO,
CancelOrderExchangeDTO,
CancelOrderFulfillmentDTO, CancelOrderFulfillmentDTO,
CancelOrderReturnDTO,
ConfirmOrderChangeDTO, ConfirmOrderChangeDTO,
CreateOrderAddressDTO, CreateOrderAddressDTO,
CreateOrderAdjustmentDTO, CreateOrderAdjustmentDTO,
@@ -1582,12 +1585,10 @@ export interface IOrderModuleService extends IModuleService {
sharedContext?: Context sharedContext?: Context
): Promise<ReturnDTO> ): Promise<ReturnDTO>
/*
cancelReturn( cancelReturn(
returnData: CancelOrderReturnDTO, data: CancelOrderReturnDTO,
sharedContext?: Context sharedContext?: Context
): Promise<void> ): Promise<ReturnDTO>
*/
receiveReturn( receiveReturn(
returnData: ReceiveOrderReturnDTO, returnData: ReceiveOrderReturnDTO,
@@ -1599,8 +1600,18 @@ export interface IOrderModuleService extends IModuleService {
sharedContext?: Context sharedContext?: Context
): Promise<OrderClaimDTO> ): Promise<OrderClaimDTO>
cancelClaim(
data: CancelOrderClaimDTO,
sharedContext?: Context
): Promise<OrderClaimDTO>
createExchange( createExchange(
exchangeData: CreateOrderExchangeDTO, exchangeData: CreateOrderExchangeDTO,
sharedContext?: Context sharedContext?: Context
): Promise<OrderExchangeDTO> ): Promise<OrderExchangeDTO>
cancelExchange(
data: CancelOrderExchangeDTO,
sharedContext?: Context
): Promise<OrderExchangeDTO>
} }
@@ -0,0 +1,4 @@
export interface CancelOrderClaimWorkflowInput {
claim_id: string
no_notification?: boolean
}
@@ -0,0 +1,4 @@
export interface CancelOrderExchangeWorkflowInput {
exchange_id: string
no_notification?: boolean
}
@@ -0,0 +1,4 @@
export interface CancelReturnWorkflowInput {
return_id: string
no_notification?: boolean
}
@@ -13,7 +13,7 @@ export interface CreateOrderReturnWorkflowInput {
order_id: string order_id: string
created_by?: string | null // The id of the authenticated user created_by?: string | null // The id of the authenticated user
items: CreateReturnItem[] items: CreateReturnItem[]
return_shipping: { return_shipping?: {
option_id: string option_id: string
price?: number price?: number
} }
@@ -1,5 +1,8 @@
export * from "./cancel-claim"
export * from "./cancel-exchange"
export * from "./cancel-fulfillment" export * from "./cancel-fulfillment"
export * from "./cancel-order" export * from "./cancel-order"
export * from "./cancel-return"
export * from "./create-fulfillment" export * from "./create-fulfillment"
export * from "./create-return-order" export * from "./create-return-order"
export * from "./create-shipment" export * from "./create-shipment"
+12
View File
@@ -86,6 +86,18 @@ export const LINKS = {
Modules.PAYMENT, Modules.PAYMENT,
"payment_collection_id" "payment_collection_id"
), ),
OrderClaimPaymentCollection: composeLinkName(
Modules.ORDER,
"claim_id",
Modules.PAYMENT,
"payment_collection_id"
),
OrderExchangePaymentCollection: composeLinkName(
Modules.ORDER,
"exchange_id",
Modules.PAYMENT,
"payment_collection_id"
),
OrderFulfillment: composeLinkName( OrderFulfillment: composeLinkName(
Modules.ORDER, Modules.ORDER,
"order_id", "order_id",
@@ -1,35 +1,20 @@
import { import { cancelReturnWorkflow } from "@medusajs/core-flows"
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../types/routing" } from "../../../../../types/routing"
import { AdminPostCancelReturnReqSchemaType } from "../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const input = req.validatedBody as AdminPostCancelReturnReqSchemaType
const queryObject = remoteQueryObjectFromString({ const workflow = cancelReturnWorkflow(req.scope)
entryPoint: "return", const { result } = await workflow.run({
variables: { input,
filters: {
...req.filterableFields,
},
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
}) })
const { rows: orders, metadata } = await remoteQuery(queryObject) res.status(200).json({ return: result })
res.json({
orders,
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
})
} }
@@ -52,7 +52,7 @@ const ItemSchema = z.object({
export const AdminPostReturnsReqSchema = z.object({ export const AdminPostReturnsReqSchema = z.object({
order_id: z.string(), order_id: z.string(),
items: z.array(ItemSchema), items: z.array(ItemSchema),
return_shipping: ReturnShippingSchema, return_shipping: ReturnShippingSchema.optional(),
internal_note: z.string().nullish(), internal_note: z.string().nullish(),
receive_now: z.boolean().optional(), receive_now: z.boolean().optional(),
refund_amount: z.number().optional(), refund_amount: z.number().optional(),
@@ -70,3 +70,12 @@ export const AdminPostReceiveReturnsReqSchema = z.object({
export type AdminPostReceiveReturnsReqSchemaType = z.infer< export type AdminPostReceiveReturnsReqSchemaType = z.infer<
typeof AdminPostReceiveReturnsReqSchema typeof AdminPostReceiveReturnsReqSchema
> >
export const AdminPostCancelReturnReqSchema = z.object({
return_id: z.string(),
no_notification: z.boolean().optional(),
internal_note: z.string().nullish(),
})
export type AdminPostCancelReturnReqSchemaType = z.infer<
typeof AdminPostReceiveReturnsReqSchema
>
@@ -0,0 +1,72 @@
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS, Modules } from "@medusajs/utils"
export const OrderClaimPaymentCollection: ModuleJoinerConfig = {
serviceName: LINKS.OrderClaimPaymentCollection,
isLink: true,
databaseConfig: {
tableName: "order_claim_payment_collection",
idPrefix: "claimpay",
},
alias: [
{
name: [
"order_claim_payment_collection",
"order_claim_payment_collections",
],
args: {
entity: "LinkOrderClaimPaymentCollection",
},
},
],
primaryKeys: ["id", "claim_id", "payment_collection_id"],
relationships: [
{
serviceName: Modules.ORDER,
primaryKey: "id",
foreignKey: "claim_id",
alias: "order",
args: {
methodSuffix: "OrderClaims",
},
},
{
serviceName: Modules.PAYMENT,
primaryKey: "id",
foreignKey: "payment_collection_id",
alias: "payment_collection",
args: {
methodSuffix: "PaymentCollections",
},
},
],
extends: [
{
serviceName: Modules.ORDER,
fieldAlias: {
claim_payment_collections: {
path: "claim_payment_collections_link.payment_collection",
isList: true,
},
},
relationship: {
serviceName: LINKS.OrderClaimPaymentCollection,
primaryKey: "claim_id",
foreignKey: "id",
alias: "claim_payment_collections_link",
},
},
{
serviceName: Modules.PAYMENT,
fieldAlias: {
claim: "order_claim_link.order",
},
relationship: {
serviceName: LINKS.OrderClaimPaymentCollection,
primaryKey: "payment_collection_id",
foreignKey: "id",
alias: "order_claim_link",
},
},
],
}
@@ -0,0 +1,72 @@
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS, Modules } from "@medusajs/utils"
export const OrderExchangePaymentCollection: ModuleJoinerConfig = {
serviceName: LINKS.OrderExchangePaymentCollection,
isLink: true,
databaseConfig: {
tableName: "order_exchange_payment_collection",
idPrefix: "excpay",
},
alias: [
{
name: [
"order_exchange_payment_collection",
"order_exchange_payment_collections",
],
args: {
entity: "LinkOrderExchangePaymentCollection",
},
},
],
primaryKeys: ["id", "exchange_id", "payment_collection_id"],
relationships: [
{
serviceName: Modules.ORDER,
primaryKey: "id",
foreignKey: "exchange_id",
alias: "order",
args: {
methodSuffix: "OrderExchanges",
},
},
{
serviceName: Modules.PAYMENT,
primaryKey: "id",
foreignKey: "payment_collection_id",
alias: "payment_collection",
args: {
methodSuffix: "PaymentCollections",
},
},
],
extends: [
{
serviceName: Modules.ORDER,
fieldAlias: {
exchange_payment_collections: {
path: "exchange_payment_collections_link.payment_collection",
isList: true,
},
},
relationship: {
serviceName: LINKS.OrderExchangePaymentCollection,
primaryKey: "exchange_id",
foreignKey: "id",
alias: "exchange_payment_collections_link",
},
},
{
serviceName: Modules.PAYMENT,
fieldAlias: {
exchange: "order_exchange_link.order",
},
relationship: {
serviceName: LINKS.OrderExchangePaymentCollection,
primaryKey: "payment_collection_id",
foreignKey: "id",
alias: "order_exchange_link",
},
},
],
}
@@ -6,7 +6,7 @@ export const OrderPaymentCollection: ModuleJoinerConfig = {
isLink: true, isLink: true,
databaseConfig: { databaseConfig: {
tableName: "order_payment_collection", tableName: "order_payment_collection",
idPrefix: "capaycol", idPrefix: "ordpay",
}, },
alias: [ alias: [
{ {
@@ -1,6 +1,6 @@
import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types" import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types"
import { moduleIntegrationTestRunner } from "medusa-test-utils"
import { Modules } from "@medusajs/utils" import { Modules } from "@medusajs/utils"
import { moduleIntegrationTestRunner } from "medusa-test-utils"
jest.setTimeout(1000000) jest.setTimeout(1000000)
@@ -102,7 +102,7 @@ moduleIntegrationTestRunner<IOrderModuleService>({
customer_id: "joe", customer_id: "joe",
} as CreateOrderDTO } as CreateOrderDTO
it("should create an order, fulfill, ship and return the items and cancel some item return", async function () { it("should create an order, fulfill, ship and return the items", async function () {
const createdOrder = await service.createOrders(input) const createdOrder = await service.createOrders(input)
// Fullfilment // Fullfilment
@@ -477,6 +477,191 @@ moduleIntegrationTestRunner<IOrderModuleService>({
}) })
) )
}) })
it("should create an order, fulfill, return the items and cancel some item return", async function () {
const createdOrder = await service.createOrders(input)
await service.registerFulfillment({
order_id: createdOrder.id,
items: createdOrder.items!.map((item) => {
return {
id: item.id,
quantity: item.quantity,
}
}),
})
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,
shipping_method: {
name: "First return method",
amount: 10,
},
items: createdOrder.items!.map((item) => {
return {
id: item.id,
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: "Second return method",
amount: 0,
},
items: [
{
id: createdOrder.items![1].id,
quantity: 1,
reason_id: reason2.id,
},
],
})
let getOrder = await service.retrieveOrder(createdOrder.id, {
select: [
"id",
"version",
"items.id",
"items.quantity",
"items.detail.id",
"items.detail.version",
"items.detail.quantity",
"items.detail.fulfilled_quantity",
"items.detail.return_requested_quantity",
],
relations: ["items", "items.detail"],
})
let serializedOrder = JSON.parse(JSON.stringify(getOrder))
expect(serializedOrder).toEqual(
expect.objectContaining({
items: [
expect.objectContaining({
quantity: 1,
detail: expect.objectContaining({
quantity: 1,
fulfilled_quantity: 1,
return_requested_quantity: 1,
}),
}),
expect.objectContaining({
quantity: 2,
detail: expect.objectContaining({
quantity: 2,
fulfilled_quantity: 2,
return_requested_quantity: 2,
}),
}),
expect.objectContaining({
quantity: 1,
detail: expect.objectContaining({
quantity: 1,
fulfilled_quantity: 1,
return_requested_quantity: 1,
}),
}),
],
})
)
// Receive second Return
await service.receiveReturn({
return_id: secondReturn.id,
internal_note: "received some items",
items: [
{
id: createdOrder.items![1].id,
quantity: 1,
},
],
})
// Cancel return
await service.cancelReturn({
return_id: orderReturn.id,
})
await expect(
service.cancelReturn({ return_id: secondReturn.id })
).rejects.toThrow(
`Cannot cancel more items than what was requested to return for item ${createdOrder.items[1].id}.`
)
getOrder = await service.retrieveOrder(createdOrder.id, {
select: [
"id",
"version",
"items.id",
"items.quantity",
"items.detail.id",
"items.detail.version",
"items.detail.quantity",
"items.detail.shipped_quantity",
"items.detail.fulfilled_quantity",
"items.detail.return_requested_quantity",
"items.detail.return_received_quantity",
"shipping_methods.id",
],
relations: ["items", "items.detail"],
})
serializedOrder = JSON.parse(JSON.stringify(getOrder))
expect(serializedOrder.shipping_methods).toHaveLength(2)
expect(serializedOrder).toEqual(
expect.objectContaining({
items: [
expect.objectContaining({
quantity: 1,
detail: expect.objectContaining({
quantity: 1,
fulfilled_quantity: 1,
shipped_quantity: 0,
return_requested_quantity: 0,
return_received_quantity: 0,
}),
}),
expect.objectContaining({
quantity: 2,
detail: expect.objectContaining({
quantity: 2,
fulfilled_quantity: 2,
shipped_quantity: 0,
return_requested_quantity: 0,
return_received_quantity: 1,
}),
}),
expect.objectContaining({
quantity: 1,
detail: expect.objectContaining({
quantity: 1,
fulfilled_quantity: 1,
shipped_quantity: 0,
return_requested_quantity: 0,
return_received_quantity: 0,
}),
}),
],
})
)
})
}) })
}, },
}) })
+1 -2
View File
@@ -25,7 +25,6 @@ import { ReturnItem, Transaction } from "@models"
import Claim from "./claim" import Claim from "./claim"
import Exchange from "./exchange" import Exchange from "./exchange"
import Order from "./order" import Order from "./order"
import OrderItem from "./order-item"
import OrderShippingMethod from "./order-shipping-method" import OrderShippingMethod from "./order-shipping-method"
type OptionalReturnProps = DAL.EntityDateColumns type OptionalReturnProps = DAL.EntityDateColumns
@@ -131,7 +130,7 @@ export default class Return {
@OneToMany(() => ReturnItem, (itemDetail) => itemDetail.return, { @OneToMany(() => ReturnItem, (itemDetail) => itemDetail.return, {
cascade: [Cascade.PERSIST], cascade: [Cascade.PERSIST],
}) })
items = new Collection<Rel<OrderItem>>(this) items = new Collection<Rel<ReturnItem>>(this)
@OneToMany( @OneToMany(
() => OrderShippingMethod, () => OrderShippingMethod,
@@ -78,7 +78,7 @@ describe("Order Return - Actions", function () {
actions, actions,
}) })
}).toThrow( }).toThrow(
"Cannot request to return more items than what was shipped for item 1." "Cannot request to return more items than what was fulfilled for item 1."
) )
expect(() => { expect(() => {
@@ -190,7 +190,7 @@ describe("Order Return - Actions", function () {
], ],
}) })
}).toThrow( }).toThrow(
"Cannot request to return more items than what was shipped for item 3." "Cannot request to return more items than what was fulfilled for item 3."
) )
expect(() => { expect(() => {
@@ -0,0 +1,144 @@
import {
Context,
CreateOrderChangeActionDTO,
OrderTypes,
} from "@medusajs/types"
import { promiseAll } from "@medusajs/utils"
import { ChangeActionType } from "../../utils"
async function createOrderChange(
service,
data,
returnRef,
actions,
sharedContext
) {
return await service.createOrderChange_(
{
order_id: returnRef.order_id,
claim_id: returnRef.id,
reference: "return",
reference_id: returnRef.id,
description: data.description,
internal_note: data.internal_note,
created_by: data.created_by,
metadata: data.metadata,
actions,
},
sharedContext
)
}
export async function cancelClaim(
this: any,
data: OrderTypes.CancelOrderClaimDTO,
sharedContext?: Context
) {
const claimOrder = await this.retrieveClaim(
data.claim_id,
{
select: [
"id",
"order_id",
"return.id",
"return.items.id",
"return.items.quantity",
"claim_items.item_id",
"claim_items.quantity",
"additional_items.id",
"additional_items.quantity",
],
relations: ["return.items", "additional_items", "shipping_methods"],
},
sharedContext
)
const actions: CreateOrderChangeActionDTO[] = []
claimOrder.return.items.forEach((item) => {
actions.push({
action: ChangeActionType.CANCEL_RETURN_ITEM,
order_id: claimOrder.order_id,
claim_id: claimOrder.id,
return_id: claimOrder.return.id,
reference: "return",
reference_id: claimOrder.return.id,
details: {
reference_id: item.id,
order_id: claimOrder.order_id,
claim_id: claimOrder.id,
return_id: claimOrder.return.id,
quantity: item.quantity,
},
})
})
claimOrder.claim_items.forEach((item) => {
actions.push({
action: ChangeActionType.REINSTATE_ITEM,
claim_id: claimOrder.id,
reference: "claim",
reference_id: claimOrder.id,
details: {
reference_id: item.id,
claim_id: claimOrder.id,
quantity: item.quantity,
},
})
})
claimOrder.additional_items.forEach((item) => {
actions.push({
action: ChangeActionType.ITEM_REMOVE,
order_id: claimOrder.order_id,
claim_id: claimOrder.id,
reference: "claim",
reference_id: claimOrder.id,
details: {
reference_id: item.id,
order_id: claimOrder.order_id,
claim_id: claimOrder.id,
quantity: item.quantity,
},
})
})
claimOrder.shipping_methods?.forEach((shipping) => {
actions.push({
action: ChangeActionType.SHIPPING_REMOVE,
order_id: claimOrder.order_id,
claim_id: claimOrder.id,
return_id: claimOrder.return.id,
reference: "claim",
reference_id: shipping.id,
amount: shipping.price,
})
})
const [change] = await createOrderChange(
this,
data,
claimOrder,
actions,
sharedContext
)
await promiseAll([
this.updateClaims(
[
{
data: {
canceled_at: new Date(),
},
selector: {
id: claimOrder.id,
},
},
],
sharedContext
),
this.confirmOrderChange(change.id, sharedContext),
])
return claimOrder
}
@@ -0,0 +1,128 @@
import {
Context,
CreateOrderChangeActionDTO,
OrderTypes,
} from "@medusajs/types"
import { promiseAll } from "@medusajs/utils"
import { ChangeActionType } from "../../utils"
async function createOrderChange(
service,
data,
returnRef,
actions,
sharedContext
) {
return await service.createOrderChange_(
{
order_id: returnRef.order_id,
exchange_id: returnRef.id,
reference: "return",
reference_id: returnRef.id,
description: data.description,
internal_note: data.internal_note,
created_by: data.created_by,
metadata: data.metadata,
actions,
},
sharedContext
)
}
export async function cancelExchange(
this: any,
data: OrderTypes.CancelOrderExchangeDTO,
sharedContext?: Context
) {
const exchangeOrder = await this.retrieveExchange(
data.exchange_id,
{
select: [
"id",
"order_id",
"return.id",
"return.items.item_id",
"return.items.quantity",
"additional_items.id",
"additional_items.quantity",
],
relations: ["return.items", "additional_items", "shipping_methods"],
},
sharedContext
)
const actions: CreateOrderChangeActionDTO[] = []
exchangeOrder.return.items.forEach((item) => {
actions.push({
action: ChangeActionType.CANCEL_RETURN_ITEM,
order_id: exchangeOrder.order_id,
exchange_id: exchangeOrder.id,
return_id: exchangeOrder.return.id,
reference: "return",
reference_id: exchangeOrder.return.id,
details: {
reference_id: item.item_id,
order_id: exchangeOrder.order_id,
exchange_id: exchangeOrder.id,
return_id: exchangeOrder.return.id,
quantity: item.quantity,
},
})
})
exchangeOrder.additional_items.forEach((item) => {
actions.push({
action: ChangeActionType.ITEM_REMOVE,
order_id: exchangeOrder.order_id,
exchange_id: exchangeOrder.id,
reference: "exchange",
reference_id: exchangeOrder.id,
details: {
order_id: exchangeOrder.order_id,
reference_id: item.item_id,
exchange_id: exchangeOrder.id,
quantity: item.quantity,
},
})
})
exchangeOrder.shipping_methods?.forEach((shipping) => {
actions.push({
action: ChangeActionType.SHIPPING_REMOVE,
order_id: exchangeOrder.order_id,
exchange_id: exchangeOrder.id,
return_id: exchangeOrder.return.id,
reference: "exchange",
reference_id: shipping.id,
amount: shipping.price,
})
})
const [change] = await createOrderChange(
this,
data,
exchangeOrder,
actions,
sharedContext
)
await promiseAll([
this.updateExchanges(
[
{
data: {
canceled_at: new Date(),
},
selector: {
id: exchangeOrder.id,
},
},
],
sharedContext
),
this.confirmOrderChange(change.id, sharedContext),
])
return exchangeOrder
}
@@ -0,0 +1,107 @@
import {
Context,
CreateOrderChangeActionDTO,
OrderTypes,
} from "@medusajs/types"
import { promiseAll } from "@medusajs/utils"
import { ChangeActionType } from "../../utils"
async function createOrderChange(
service,
data,
returnRef,
actions,
sharedContext
) {
return await service.createOrderChange_(
{
order_id: returnRef.order_id,
return_id: returnRef.id,
reference: "return",
reference_id: returnRef.id,
description: data.description,
internal_note: data.internal_note,
created_by: data.created_by,
metadata: data.metadata,
actions,
},
sharedContext
)
}
export async function cancelReturn(
this: any,
data: OrderTypes.CancelOrderReturnDTO,
sharedContext?: Context
) {
const returnOrder = await this.retrieveReturn(
data.return_id,
{
select: [
"id",
"order_id",
"items.item_id",
"items.quantity",
"items.received_quantity",
],
relations: ["items", "shipping_methods"],
},
sharedContext
)
const actions: CreateOrderChangeActionDTO[] = []
returnOrder.items.forEach((item) => {
actions.push({
action: ChangeActionType.CANCEL_RETURN_ITEM,
order_id: returnOrder.order_id,
return_id: returnOrder.id,
reference: "return",
reference_id: returnOrder.id,
details: {
reference_id: item.item_id,
order_id: returnOrder.order_id,
return_id: returnOrder.id,
quantity: item.quantity,
},
})
})
returnOrder.shipping_methods?.forEach((shipping) => {
actions.push({
action: ChangeActionType.SHIPPING_REMOVE,
order_id: returnOrder.order_id,
return_id: returnOrder.id,
reference: "return",
reference_id: shipping.id,
amount: shipping.price,
})
})
const [change] = await createOrderChange(
this,
data,
returnOrder,
actions,
sharedContext
)
await promiseAll([
this.updateReturns(
[
{
data: {
canceled_at: new Date(),
},
selector: {
id: returnOrder.id,
},
},
],
sharedContext
),
this.confirmOrderChange(change.id, sharedContext),
])
return returnOrder
}
@@ -196,9 +196,9 @@ async function processShippingMethods(
const methods = await service.createShippingMethods( const methods = await service.createShippingMethods(
[ [
{ {
...shippingMethod,
order_id: data.order_id, order_id: data.order_id,
claim_id: claimReference.id, claim_id: claimReference.id,
...shippingMethod,
}, },
], ],
sharedContext sharedContext
@@ -247,10 +247,10 @@ async function processReturnShipping(
const methods = await service.createShippingMethods( const methods = await service.createShippingMethods(
[ [
{ {
...data.return_shipping,
order_id: data.order_id, order_id: data.order_id,
claim_id: claimReference.id, claim_id: claimReference.id,
return_id: returnReference.id, return_id: returnReference.id,
...data.return_shipping,
}, },
], ],
sharedContext sharedContext
@@ -162,9 +162,9 @@ async function processShippingMethods(
const methods = await service.createShippingMethods( const methods = await service.createShippingMethods(
[ [
{ {
...shippingMethod,
order_id: data.order_id, order_id: data.order_id,
exchange_id: exchangeReference.id, exchange_id: exchangeReference.id,
...shippingMethod,
}, },
], ],
sharedContext sharedContext
@@ -208,10 +208,10 @@ async function processReturnShipping(
const methods = await service.createShippingMethods( const methods = await service.createShippingMethods(
[ [
{ {
...data.return_shipping,
order_id: data.order_id, order_id: data.order_id,
exchange_id: exchangeReference.id, exchange_id: exchangeReference.id,
return_id: returnReference.id, return_id: returnReference.id,
...data.return_shipping,
}, },
], ],
sharedContext sharedContext
@@ -6,6 +6,7 @@ import {
import { import {
ReturnStatus, ReturnStatus,
getShippingMethodsTotals, getShippingMethodsTotals,
isDefined,
isString, isString,
promiseAll, promiseAll,
} from "@medusajs/utils" } from "@medusajs/utils"
@@ -59,13 +60,17 @@ async function processShippingMethod(
) { ) {
let shippingMethodId let shippingMethodId
if (!isDefined(data.shipping_method)) {
return
}
if (!isString(data.shipping_method)) { if (!isString(data.shipping_method)) {
const methods = await service.createShippingMethods( const methods = await service.createShippingMethods(
[ [
{ {
...data.shipping_method,
order_id: data.order_id, order_id: data.order_id,
return_id: returnRef.id, return_id: returnRef.id,
...data.shipping_method,
}, },
], ],
sharedContext sharedContext
@@ -90,8 +95,11 @@ async function processShippingMethod(
action: ChangeActionType.SHIPPING_ADD, action: ChangeActionType.SHIPPING_ADD,
reference: "order_shipping_method", reference: "order_shipping_method",
reference_id: shippingMethodId, reference_id: shippingMethodId,
return_id: returnRef.id,
amount: calculatedAmount.total, amount: calculatedAmount.total,
details: {
order_id: returnRef.order_id,
return_id: returnRef.id,
},
}) })
} }
} }
@@ -134,8 +142,11 @@ export async function createReturn(
const em = sharedContext!.transactionManager as any const em = sharedContext!.transactionManager as any
const returnRef = createReturnReference(em, data, order) const returnRef = createReturnReference(em, data, order)
const actions: CreateOrderChangeActionDTO[] = [] const actions: CreateOrderChangeActionDTO[] = []
returnRef.items = createReturnItems(em, data, returnRef, actions) returnRef.items = createReturnItems(em, data, returnRef, actions)
await processShippingMethod(this, data, returnRef, actions, sharedContext) await processShippingMethod(this, data, returnRef, actions, sharedContext)
const change = await createOrderChange( const change = await createOrderChange(
this, this,
data, data,
@@ -1,4 +1,7 @@
export * from "./cancel-claim"
export * from "./cancel-exchange"
export * from "./cancel-fulfillment" export * from "./cancel-fulfillment"
export * from "./cancel-return"
export * from "./create-claim" export * from "./create-claim"
export * from "./create-exchange" export * from "./create-exchange"
export * from "./create-return" export * from "./create-return"
@@ -1188,9 +1188,13 @@ export default class OrderModuleService<
return { return {
shipping_method: dt, shipping_method: dt,
order_id: dt.order_id, order_id: dt.order_id,
return_id: dt.return_id,
claim_id: dt.claim_id,
exchange_id: dt.exchange_id,
version: mapOrderVersion[dt.order_id], version: mapOrderVersion[dt.order_id],
} }
}) })
methods = await this.createShippingMethodsBulk_( methods = await this.createShippingMethodsBulk_(
orderShippingMethodData as any, orderShippingMethodData as any,
sharedContext sharedContext
@@ -1214,11 +1218,14 @@ export default class OrderModuleService<
sharedContext sharedContext
) )
const methods = data.map((method) => { const methods = data.map((methodData) => {
return { return {
shipping_method: method, shipping_method: methodData,
order_id: order.id, order_id: order.id,
version: method.version ?? order.version ?? 1, return_id: methodData.return_id,
claim_id: methodData.claim_id,
exchange_id: methodData.exchange_id,
version: methodData.version ?? order.version ?? 1,
} }
}) })
@@ -1952,6 +1959,9 @@ export default class OrderModuleService<
select: [ select: [
"id", "id",
"order_id", "order_id",
"return_id",
"exchange_id",
"claim_id",
"ordering", "ordering",
"version", "version",
"applied", "applied",
@@ -2108,7 +2118,15 @@ export default class OrderModuleService<
sharedContext?: Context sharedContext?: Context
): Promise<any> { ): Promise<any> {
const options = { const options = {
select: ["id", "order_id", "version", "status"], select: [
"id",
"order_id",
"return_id",
"claim_id",
"exchange_id",
"version",
"status",
],
relations: [] as string[], relations: [] as string[],
order: {}, order: {},
} }
@@ -2253,15 +2271,7 @@ export default class OrderModuleService<
let orders = await super.listOrders( let orders = await super.listOrders(
{ id: deduplicate(ordersIds) }, { id: deduplicate(ordersIds) },
{ {
select: [ select: ["id", "version", "items.detail", "summary", "total"],
"id",
"version",
"items.detail",
"transactions",
"shipping_methods",
"summary",
"total",
],
relations: [ relations: [
"transactions", "transactions",
"items", "items",
@@ -2277,7 +2287,7 @@ export default class OrderModuleService<
const { const {
itemsToUpsert, itemsToUpsert,
shippingMethodsToInsert, shippingMethodsToUpsert,
summariesToUpsert, summariesToUpsert,
orderToUpdate, orderToUpdate,
} = applyChangesToOrder(orders, actionsMap) } = applyChangesToOrder(orders, actionsMap)
@@ -2295,9 +2305,9 @@ export default class OrderModuleService<
summariesToUpsert.length summariesToUpsert.length
? this.orderSummaryService_.upsert(summariesToUpsert, sharedContext) ? this.orderSummaryService_.upsert(summariesToUpsert, sharedContext)
: null, : null,
shippingMethodsToInsert.length shippingMethodsToUpsert.length
? this.orderShippingMethodService_.create( ? this.orderShippingMethodService_.upsert(
shippingMethodsToInsert, shippingMethodsToUpsert,
sharedContext sharedContext
) )
: null, : null,
@@ -2305,7 +2315,7 @@ export default class OrderModuleService<
return { return {
items: itemsToUpsert as any, items: itemsToUpsert as any,
shippingMethods: shippingMethodsToInsert as any, shippingMethods: shippingMethodsToUpsert as any,
} }
} }
@@ -2803,8 +2813,7 @@ export default class OrderModuleService<
async createReturn( async createReturn(
data: OrderTypes.CreateOrderReturnDTO, data: OrderTypes.CreateOrderReturnDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<any> { ): Promise<OrderTypes.ReturnDTO> {
// TODO: type ReturnDTO
const ret = await BundledActions.createReturn.bind(this)( const ret = await BundledActions.createReturn.bind(this)(
data, data,
sharedContext sharedContext
@@ -2813,7 +2822,12 @@ export default class OrderModuleService<
return await this.retrieveReturn( return await this.retrieveReturn(
ret.id, ret.id,
{ {
relations: ["items"], relations: [
"items",
"shipping_methods",
"shipping_methods.tax_lines",
"shipping_methods.adjustments",
],
}, },
sharedContext sharedContext
) )
@@ -2823,11 +2837,16 @@ export default class OrderModuleService<
async receiveReturn( async receiveReturn(
data: OrderTypes.ReceiveOrderReturnDTO, data: OrderTypes.ReceiveOrderReturnDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<any> { ): Promise<OrderTypes.ReturnDTO> {
const ret = await this.receiveReturn_(data, sharedContext) const ret = await this.receiveReturn_(data, sharedContext)
return await this.retrieveReturn(ret.id, { return await this.retrieveReturn(ret.id, {
relations: ["items"], relations: [
"items",
"shipping_methods",
"shipping_methods.tax_lines",
"shipping_methods.adjustments",
],
}) })
} }
@@ -2839,11 +2858,36 @@ export default class OrderModuleService<
return await BundledActions.receiveReturn.bind(this)(data, sharedContext) return await BundledActions.receiveReturn.bind(this)(data, sharedContext)
} }
@InjectManager("baseRepository_")
async cancelReturn(
data: OrderTypes.CancelOrderReturnDTO,
@MedusaContext() sharedContext?: Context
): Promise<OrderTypes.ReturnDTO> {
const ret = await this.cancelReturn_(data, sharedContext)
return await this.retrieveReturn(ret.id, {
relations: [
"items",
"shipping_methods",
"shipping_methods.tax_lines",
"shipping_methods.adjustments",
],
})
}
@InjectTransactionManager("baseRepository_")
private async cancelReturn_(
data: OrderTypes.CancelOrderReturnDTO,
@MedusaContext() sharedContext?: Context
): Promise<any> {
return await BundledActions.cancelReturn.bind(this)(data, sharedContext)
}
@InjectManager("baseRepository_") @InjectManager("baseRepository_")
async createClaim( async createClaim(
data: OrderTypes.CreateOrderClaimDTO, data: OrderTypes.CreateOrderClaimDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<any> { ): Promise<OrderTypes.OrderClaimDTO> {
const ret = await this.createClaim_(data, sharedContext) const ret = await this.createClaim_(data, sharedContext)
const claim = await this.retrieveOrderClaim( const claim = await this.retrieveOrderClaim(
@@ -2865,7 +2909,7 @@ export default class OrderModuleService<
sharedContext sharedContext
) )
return await this.baseRepository_.serialize<OrderTypes.OrderClaimDTO[]>( return await this.baseRepository_.serialize<OrderTypes.OrderClaimDTO>(
claim, claim,
{ {
populate: true, populate: true,
@@ -2874,18 +2918,38 @@ export default class OrderModuleService<
} }
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
async createExchange_( async createClaim_(
data: OrderTypes.CreateOrderExchangeDTO, data: OrderTypes.CreateOrderClaimDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<any> { ): Promise<any> {
return await BundledActions.createExchange.bind(this)(data, sharedContext) return await BundledActions.createClaim.bind(this)(data, sharedContext)
}
@InjectManager("baseRepository_")
async cancelClaim(
data: OrderTypes.CancelOrderClaimDTO,
@MedusaContext() sharedContext?: Context
): Promise<OrderTypes.OrderClaimDTO> {
const ret = await this.cancelClaim_(data, sharedContext)
return await this.retrieveOrderClaim(ret.id, {
relations: ["items"],
})
}
@InjectTransactionManager("baseRepository_")
private async cancelClaim_(
data: OrderTypes.CancelOrderClaimDTO,
@MedusaContext() sharedContext?: Context
): Promise<any> {
return await BundledActions.cancelClaim.bind(this)(data, sharedContext)
} }
@InjectManager("baseRepository_") @InjectManager("baseRepository_")
async createExchange( async createExchange(
data: OrderTypes.CreateOrderExchangeDTO, data: OrderTypes.CreateOrderExchangeDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<any> { ): Promise<OrderTypes.OrderExchangeDTO> {
const ret = await this.createExchange_(data, sharedContext) const ret = await this.createExchange_(data, sharedContext)
const claim = await this.retrieveOrderExchange( const claim = await this.retrieveOrderExchange(
@@ -2905,7 +2969,7 @@ export default class OrderModuleService<
sharedContext sharedContext
) )
return await this.baseRepository_.serialize<OrderTypes.OrderExchangeDTO[]>( return await this.baseRepository_.serialize<OrderTypes.OrderExchangeDTO>(
claim, claim,
{ {
populate: true, populate: true,
@@ -2914,11 +2978,31 @@ export default class OrderModuleService<
} }
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
async createClaim_( async createExchange_(
data: OrderTypes.CreateOrderClaimDTO, data: OrderTypes.CreateOrderExchangeDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<any> { ): Promise<any> {
return await BundledActions.createClaim.bind(this)(data, sharedContext) return await BundledActions.createExchange.bind(this)(data, sharedContext)
}
@InjectManager("baseRepository_")
async cancelExchange(
data: OrderTypes.CancelOrderExchangeDTO,
@MedusaContext() sharedContext?: Context
): Promise<OrderTypes.OrderExchangeDTO> {
const ret = await this.cancelExchange_(data, sharedContext)
return await this.retrieveOrderExchange(ret.id, {
relations: ["items"],
})
}
@InjectTransactionManager("baseRepository_")
private async cancelExchange_(
data: OrderTypes.CancelOrderExchangeDTO,
@MedusaContext() sharedContext?: Context
): Promise<any> {
return await BundledActions.cancelExchange.bind(this)(data, sharedContext)
} }
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
@@ -4,6 +4,9 @@ export interface CreateOrderShippingMethodDTO {
name: string name: string
shipping_option_id?: string shipping_option_id?: string
order_id: string order_id: string
return_id?: string
claim_id?: string
exchange_id?: string
version?: number version?: number
amount: BigNumberInput amount: BigNumberInput
data?: Record<string, unknown> data?: Record<string, unknown>
@@ -32,7 +32,7 @@ export type VirtualOrder = {
}[] }[]
shipping_methods: { shipping_methods: {
shipping_method_id: string id: string
order_id: string order_id: string
return_id?: string return_id?: string
claim_id?: string claim_id?: string
@@ -89,14 +89,14 @@ export interface OrderChangeEvent {
claim_id?: string claim_id?: string
exchange_id?: string exchange_id?: string
group_id?: string change_id?: string
evaluationOnly?: boolean evaluationOnly?: boolean
details?: any details?: any
resolve?: { resolve?: {
group_id?: string change_id?: string
reference_id?: string reference_id?: string
amount?: BigNumberInput amount?: BigNumberInput
} }
@@ -1,6 +1,5 @@
export enum ChangeActionType { export enum ChangeActionType {
CANCEL = "CANCEL", CANCEL = "CANCEL",
CANCEL_RETURN = "CANCEL_RETURN",
FULFILL_ITEM = "FULFILL_ITEM", FULFILL_ITEM = "FULFILL_ITEM",
CANCEL_ITEM_FULFILLMENT = "CANCEL_ITEM_FULFILLMENT", CANCEL_ITEM_FULFILLMENT = "CANCEL_ITEM_FULFILLMENT",
ITEM_ADD = "ITEM_ADD", ITEM_ADD = "ITEM_ADD",
@@ -8,7 +7,10 @@ export enum ChangeActionType {
RECEIVE_DAMAGED_RETURN_ITEM = "RECEIVE_DAMAGED_RETURN_ITEM", RECEIVE_DAMAGED_RETURN_ITEM = "RECEIVE_DAMAGED_RETURN_ITEM",
RECEIVE_RETURN_ITEM = "RECEIVE_RETURN_ITEM", RECEIVE_RETURN_ITEM = "RECEIVE_RETURN_ITEM",
RETURN_ITEM = "RETURN_ITEM", RETURN_ITEM = "RETURN_ITEM",
CANCEL_RETURN_ITEM = "CANCEL_RETURN_ITEM",
SHIPPING_ADD = "SHIPPING_ADD", SHIPPING_ADD = "SHIPPING_ADD",
SHIPPING_REMOVE = "SHIPPING_REMOVE",
SHIP_ITEM = "SHIP_ITEM", SHIP_ITEM = "SHIP_ITEM",
WRITE_OFF_ITEM = "WRITE_OFF_ITEM", WRITE_OFF_ITEM = "WRITE_OFF_ITEM",
REINSTATE_ITEM = "REINSTATE_ITEM",
} }
@@ -3,7 +3,7 @@ import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change" import { OrderChangeProcessing } from "../calculate-order-change"
import { setActionReference } from "../set-action-reference" import { setActionReference } from "../set-action-reference"
OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN, { OrderChangeProcessing.registerActionType(ChangeActionType.CANCEL_RETURN_ITEM, {
operation({ action, currentOrder }) { operation({ action, currentOrder }) {
const existing = currentOrder.items.find( const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id (item) => item.id === action.details.reference_id
@@ -9,4 +9,5 @@ export * from "./receive-return-item"
export * from "./return-item" export * from "./return-item"
export * from "./ship-item" export * from "./ship-item"
export * from "./shipping-add" export * from "./shipping-add"
export * from "./shipping-remove"
export * from "./write-off-item" export * from "./write-off-item"
@@ -0,0 +1,64 @@
import { MathBN, MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
import { setActionReference } from "../set-action-reference"
OrderChangeProcessing.registerActionType(ChangeActionType.REINSTATE_ITEM, {
operation({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.written_off_quantity ??= 0
existing.detail.written_off_quantity = MathBN.sub(
existing.detail.written_off_quantity,
action.details.quantity
)
setActionReference(existing, action)
},
revert({ action, currentOrder }) {
const existing = currentOrder.items.find(
(item) => item.id === action.details.reference_id
)!
existing.detail.written_off_quantity = MathBN.add(
existing.detail.written_off_quantity,
action.details.quantity
)
},
validate({ action, currentOrder }) {
const refId = action.details?.reference_id
if (!isDefined(refId)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Details reference ID is required."
)
}
const existing = currentOrder.items.find((item) => item.id === refId)
if (!existing) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Item ID "${refId}" not found.`
)
}
if (!action.details?.quantity) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Quantity to reinstate item ${refId} is required.`
)
}
const quantityAvailable = existing!.quantity ?? 0
const greater = MathBN.gt(action.details?.quantity, quantityAvailable)
if (greater) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Cannot unclaim more items than what was ordered."
)
}
},
})
@@ -57,7 +57,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RETURN_ITEM, {
} }
const quantityAvailable = MathBN.sub( const quantityAvailable = MathBN.sub(
existing!.detail?.shipped_quantity ?? 0, existing!.detail?.fulfilled_quantity ?? 0,
existing!.detail?.return_requested_quantity ?? 0 existing!.detail?.return_requested_quantity ?? 0
) )
@@ -65,7 +65,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.RETURN_ITEM, {
if (greater) { if (greater) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
`Cannot request to return more items than what was shipped for item ${refId}.` `Cannot request to return more items than what was fulfilled for item ${refId}.`
) )
} }
}, },
@@ -1,6 +1,7 @@
import { MedusaError, isDefined } from "@medusajs/utils" import { MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key" import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change" import { OrderChangeProcessing } from "../calculate-order-change"
import { setActionReference } from "../set-action-reference"
OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, { OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, {
operation({ action, currentOrder }) { operation({ action, currentOrder }) {
@@ -8,15 +9,20 @@ OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, {
? currentOrder.shipping_methods ? currentOrder.shipping_methods
: [currentOrder.shipping_methods] : [currentOrder.shipping_methods]
const existing = shipping.find((sh) => sh.id === action.reference_id)
if (existing) {
setActionReference(existing, action)
} else {
shipping.push({ shipping.push({
shipping_method_id: action.reference_id!, id: action.reference_id!,
order_id: currentOrder.id, order_id: currentOrder.id,
return_id: action.return_id, return_id: action.return_id,
claim_id: action.claim_id, claim_id: action.claim_id,
exchange_id: action.exchange_id, exchange_id: action.exchange_id,
price: action.amount as number, price: action.amount as number,
}) })
}
currentOrder.shipping_methods = shipping currentOrder.shipping_methods = shipping
}, },
@@ -26,7 +32,7 @@ OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_ADD, {
: [currentOrder.shipping_methods] : [currentOrder.shipping_methods]
const existingIndex = shipping.findIndex( const existingIndex = shipping.findIndex(
(item) => item.shipping_method_id === action.reference_id (item) => item.id === action.reference_id
) )
if (existingIndex > -1) { if (existingIndex > -1) {
@@ -0,0 +1,56 @@
import { MedusaError, isDefined } from "@medusajs/utils"
import { ChangeActionType } from "../action-key"
import { OrderChangeProcessing } from "../calculate-order-change"
OrderChangeProcessing.registerActionType(ChangeActionType.SHIPPING_REMOVE, {
operation({ action, currentOrder }) {
const shipping = Array.isArray(currentOrder.shipping_methods)
? currentOrder.shipping_methods
: [currentOrder.shipping_methods]
const existingIndex = shipping.findIndex(
(item) => item.id === action.reference_id
)
if (existingIndex > -1) {
shipping.splice(existingIndex, 1)
}
currentOrder.shipping_methods = shipping
},
revert({ action, currentOrder }) {
const shipping = Array.isArray(currentOrder.shipping_methods)
? currentOrder.shipping_methods
: [currentOrder.shipping_methods]
const existingIndex = shipping.findIndex(
(item) => item.id === action.reference_id
)
if (existingIndex > -1) {
shipping.push({
id: action.reference_id!,
order_id: currentOrder.id,
return_id: action.return_id,
claim_id: action.claim_id,
exchange_id: action.exchange_id,
price: action.amount as number,
})
}
},
validate({ action }) {
if (!action.reference_id) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Reference ID is required."
)
}
if (!isDefined(action.amount)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Amount is required."
)
}
},
})
@@ -15,7 +15,7 @@ export function applyChangesToOrder(
actionsMap: Record<string, any[]> actionsMap: Record<string, any[]>
) { ) {
const itemsToUpsert: OrderItem[] = [] const itemsToUpsert: OrderItem[] = []
const shippingMethodsToInsert: OrderShippingMethod[] = [] const shippingMethodsToUpsert: OrderShippingMethod[] = []
const summariesToUpsert: any[] = [] const summariesToUpsert: any[] = []
const orderToUpdate: any[] = [] const orderToUpdate: any[] = []
@@ -68,8 +68,9 @@ export function applyChangesToOrder(
...((shippingMethod as any).detail ?? shippingMethod), ...((shippingMethod as any).detail ?? shippingMethod),
version, version,
} }
delete sm.id delete sm.id
shippingMethodsToInsert.push(sm) shippingMethodsToUpsert.push(sm)
} }
orderToUpdate.push({ orderToUpdate.push({
@@ -85,7 +86,7 @@ export function applyChangesToOrder(
return { return {
itemsToUpsert, itemsToUpsert,
shippingMethodsToInsert, shippingMethodsToUpsert,
summariesToUpsert, summariesToUpsert,
orderToUpdate, orderToUpdate,
} }
@@ -34,11 +34,18 @@ export function setFindMethods<T>(klass: Constructor<T>, entity: any) {
let orderAlias = "o0" let orderAlias = "o0"
if (isRelatedEntity) { if (isRelatedEntity) {
if (!config.options.populate.includes("order.items")) {
config.options.populate.unshift("order.items")
}
// first relation is always order if the entity is not Order // first relation is always order if the entity is not Order
const index = config.options.populate.findIndex((p) => p === "order")
if (index > -1) {
config.options.populate.splice(index, 1)
}
config.options.populate.unshift("order") config.options.populate.unshift("order")
orderAlias = "o1" orderAlias = "o1"
config.options.populate.unshift("order.items")
} }
let defaultVersion = knex.raw(`"${orderAlias}"."version"`) let defaultVersion = knex.raw(`"${orderAlias}"."version"`)
@@ -105,7 +112,12 @@ export function setFindMethods<T>(klass: Constructor<T>, entity: any) {
let orderAlias = "o0" let orderAlias = "o0"
if (isRelatedEntity) { if (isRelatedEntity) {
// first relation is always order if entity is not Order // first relation is always order if the entity is not Order
const index = config.options.populate.findIndex((p) => p === "order")
if (index > -1) {
config.options.populate.splice(index, 1)
}
config.options.populate.unshift("order") config.options.populate.unshift("order")
orderAlias = "o1" orderAlias = "o1"
} }
@@ -118,10 +118,10 @@ export class OrderChangeProcessing {
const amount = MathBN.mult(action.amount!, type.isDeduction ? -1 : 1) const amount = MathBN.mult(action.amount!, type.isDeduction ? -1 : 1)
if (action.group_id && !action.evaluationOnly) { if (action.change_id && !action.evaluationOnly) {
this.groupTotal[action.group_id] ??= 0 this.groupTotal[action.change_id] ??= 0
this.groupTotal[action.group_id] = MathBN.add( this.groupTotal[action.change_id] = MathBN.add(
this.groupTotal[action.group_id], this.groupTotal[action.change_id],
amount amount
) )
} }
@@ -146,7 +146,7 @@ export class OrderChangeProcessing {
amount amount
) )
} else { } else {
if (!this.isEventDone(action) && !action.group_id) { if (!this.isEventDone(action) && !action.change_id) {
summary.difference_sum = MathBN.add(summary.difference_sum, amount) summary.difference_sum = MathBN.add(summary.difference_sum, amount)
} }
summary.current_order_total = MathBN.add( summary.current_order_total = MathBN.add(
@@ -243,8 +243,8 @@ export class OrderChangeProcessing {
if (action.resolve.reference_id) { if (action.resolve.reference_id) {
this.resolveReferences(action) this.resolveReferences(action)
} }
const groupId = action.resolve.group_id ?? "__default" const groupId = action.resolve.change_id ?? "__default"
if (action.resolve.group_id) { if (action.resolve.change_id) {
// resolve all actions in the same group // resolve all actions in the same group
this.resolveGroup(action) this.resolveGroup(action)
} }
@@ -320,7 +320,7 @@ export class OrderChangeProcessing {
const type = OrderChangeProcessing.typeDefinition[actionKey] const type = OrderChangeProcessing.typeDefinition[actionKey]
const actions = this.actionsProcessed[actionKey] const actions = this.actionsProcessed[actionKey]
for (const action of actions) { for (const action of actions) {
if (!resolve?.group_id || action?.group_id !== resolve.group_id) { if (!resolve?.change_id || action?.change_id !== resolve.change_id) {
continue continue
} }
@@ -5,8 +5,9 @@ import {
deduplicate, deduplicate,
isDefined, isDefined,
} from "@medusajs/utils" } from "@medusajs/utils"
import { Order, OrderClaim, OrderExchange, Return } from "@models"
// Reshape the order object to match the OrderDTO
// This function is used to format the order object before returning to the main module methods
export function formatOrder( export function formatOrder(
order, order,
options: { options: {
@@ -20,8 +21,9 @@ export function formatOrder(
orders.map((order) => { orders.map((order) => {
let mainOrder = order let mainOrder = order
const isRelatedEntity = options?.entity !== Order const isRelatedEntity = options?.entity?.name !== "Order"
// If the entity is a related entity, the original order is located in the order property
if (isRelatedEntity) { if (isRelatedEntity) {
if (!order.order) { if (!order.order) {
return order return order
@@ -48,11 +50,12 @@ export function formatOrder(
formatOrderReturn(order.return, mainOrder) formatOrderReturn(order.return, mainOrder)
} }
if (options.entity === OrderClaim) { const entityName = options.entity.name
if (entityName === "OrderClaim") {
formatClaim(order) formatClaim(order)
} else if (options.entity === OrderExchange) { } else if (entityName === "OrderExchange") {
formatExchange(order) formatExchange(order)
} else if (options.entity === Return) { } else if (entityName === "Return") {
formatReturn(order) formatReturn(order)
} }
} }
@@ -85,9 +88,6 @@ export function formatOrder(
} }
function formatOrderReturn(orderReturn, mainOrder) { function formatOrderReturn(orderReturn, mainOrder) {
orderReturn.items = orderReturn.items.filter(
(item) => !item.is_additional_item
)
orderReturn.items.forEach((orderItem) => { orderReturn.items.forEach((orderItem) => {
const item = mainOrder.items?.find((item) => item.id === orderItem.item_id) const item = mainOrder.items?.find((item) => item.id === orderItem.item_id)
@@ -154,7 +154,15 @@ function formatReturn(returnOrder) {
}) })
} }
// Map the public order model to the repository model format
// As the public responses have a different shape than the repository responses, this function is used to map the public properties to the internal db entities
// e.g "items" is the relation between "line-item" and "order" + "version", The line item itself is in "items.item"
// This helper maps to the correct repository to query the DB, and the function "formatOrder" remap the response to the public shape
export function mapRepositoryToOrderModel(config, isRelatedEntity = false) { export function mapRepositoryToOrderModel(config, isRelatedEntity = false) {
if (isRelatedEntity) {
return mapRepositoryToRelatedEntity(config)
}
const conf = { ...config } const conf = { ...config }
function replace(obj, type): string[] | undefined { function replace(obj, type): string[] | undefined {
@@ -223,3 +231,36 @@ export function mapRepositoryToOrderModel(config, isRelatedEntity = false) {
return conf return conf
} }
// This function has the same purpose as "mapRepositoryToOrderModel" but for returns, claims and exchanges
function mapRepositoryToRelatedEntity(config) {
const conf = { ...config }
function replace(obj, type): string[] | undefined {
if (!isDefined(obj[type])) {
return
}
return deduplicate(
obj[type].sort().map((rel) => {
if (
rel.includes("shipping_methods") &&
!rel.includes("shipping_methods.shipping_method")
) {
obj.populate.push("shipping_methods.shipping_method")
return rel.replace(
"shipping_methods",
"shipping_methods.shipping_method"
)
}
return rel
})
)
}
conf.options.fields = replace(config.options, "fields")
conf.options.populate = replace(config.options, "populate")
return conf
}