feat(order): cancel fulfillment (#7573)

This commit is contained in:
Carlos R. L. Rodrigues
2024-06-02 09:33:24 -03:00
committed by GitHub
parent 4e04214612
commit af0140d317
30 changed files with 745 additions and 298 deletions
@@ -1,12 +1,12 @@
import { LinkDefinition, RemoteLink } from "@medusajs/modules-sdk"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ContainerRegistrationKeys } from "@medusajs/utils"
type CreateRemoteLinksStepInput = LinkDefinition[]
export const createLinksStepId = "create-links"
export const createLinkStep = createStep(
export const createLinksStepId = "create-remote-links"
export const createRemoteLinkStep = createStep(
createLinksStepId,
async (data: CreateRemoteLinksStepInput, { container }) => {
const link = container.resolve<RemoteLink>(
@@ -0,0 +1,29 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CancelOrderFulfillmentDTO, IOrderModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type CancelOrderFulfillmentStepInput = CancelOrderFulfillmentDTO
export const cancelOrderFulfillmentStepId = "cancel-order-fullfillment"
export const cancelOrderFulfillmentStep = createStep(
cancelOrderFulfillmentStepId,
async (data: CancelOrderFulfillmentStepInput, { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.cancelFulfillment(data)
return new StepResponse(void 0, data.order_id)
},
async (orderId, { container }) => {
if (!orderId) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.revertLastVersion(orderId)
}
)
@@ -0,0 +1,148 @@
import { Modules } from "@medusajs/modules-sdk"
import { FulfillmentDTO, OrderDTO, OrderWorkflow } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import {
WorkflowData,
createStep,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../common"
import { cancelFulfillmentWorkflow } from "../../fulfillment"
import { adjustInventoryLevelsStep } from "../../inventory"
import { cancelOrderFulfillmentStep } from "../steps/cancel-fulfillment"
import {
throwIfItemsDoesNotExistsInOrder,
throwIfOrderIsCancelled,
} from "../utils/order-validation"
const validateOrder = createStep(
"validate-order",
({
order,
input,
}: {
order: OrderDTO & { fulfillments: FulfillmentDTO[] }
input: OrderWorkflow.CancelOrderFulfillmentWorkflowInput
}) => {
throwIfOrderIsCancelled({ order })
const fulfillment = order.fulfillments.find(
(f) => f.id === input.fulfillment_id
)
if (!fulfillment) {
throw new Error(
`Fulfillment with id ${input.fulfillment_id} not found in the order`
)
}
if (fulfillment.shipped_at) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`The fulfillment has already been shipped. Shipped fulfillments cannot be canceled`
)
}
throwIfItemsDoesNotExistsInOrder({
order,
inputItems: fulfillment.items.map((i) => ({
id: i.line_item_id as string,
quantity: i.quantity,
})),
})
}
)
function prepareCancelOrderFulfillmentData({
order,
fulfillment,
}: {
order: OrderDTO
fulfillment: FulfillmentDTO
}) {
return {
order_id: order.id,
reference: Modules.FULFILLMENT,
reference_id: fulfillment.id,
items: fulfillment.items!.map((i) => {
return {
id: i.line_item_id as string,
quantity: i.quantity,
}
}),
}
}
function prepareInventoryUpdate({
fulfillment,
}: {
order: OrderDTO
fulfillment: FulfillmentDTO
}) {
const inventoryAdjustment: {
inventory_item_id: string
location_id: string
adjustment: number // TODO: BigNumberInput
}[] = []
for (const item of fulfillment.items) {
inventoryAdjustment.push({
inventory_item_id: item.inventory_item_id as string,
location_id: fulfillment.location_id,
adjustment: item.quantity,
})
}
return {
inventoryAdjustment,
}
}
export const cancelOrderFulfillmentWorkflowId = "cancel-order-fulfillment"
export const cancelOrderFulfillmentWorkflow = createWorkflow(
cancelOrderFulfillmentWorkflowId,
(
input: WorkflowData<OrderWorkflow.CancelOrderFulfillmentWorkflowInput>
): WorkflowData<void> => {
const order: OrderDTO & { fulfillments: FulfillmentDTO[] } =
useRemoteQueryStep({
entry_point: "orders",
fields: [
"id",
"status",
"items.*",
"fulfillments.*",
"fulfillments.items.*",
],
variables: { id: input.order_id },
list: false,
throw_if_key_not_found: true,
})
validateOrder({ order, input })
const fulfillment = transform({ input, order }, ({ input, order }) => {
return order.fulfillments.find((f) => f.id === input.fulfillment_id)!
})
cancelFulfillmentWorkflow.runAsStep({
input: {
id: input.fulfillment_id,
},
})
const cancelOrderFulfillmentData = transform(
{ order, fulfillment },
prepareCancelOrderFulfillmentData
)
cancelOrderFulfillmentStep(cancelOrderFulfillmentData)
const { inventoryAdjustment } = transform(
{ order, fulfillment },
prepareInventoryUpdate
)
adjustInventoryLevelsStep(inventoryAdjustment)
}
)
@@ -4,6 +4,7 @@ import {
FulfillmentWorkflow,
OrderDTO,
OrderWorkflow,
ReservationItemDTO,
} from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import {
@@ -13,7 +14,7 @@ import {
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { createLinkStep, useRemoteQueryStep } from "../../common"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../common"
import { createFulfillmentWorkflow } from "../../fulfillment"
import { adjustInventoryLevelsStep } from "../../inventory"
import {
@@ -70,6 +71,7 @@ function prepareFulfillmentData({
order,
input,
shippingOption,
reservations,
}: {
order: OrderDTO
input: OrderWorkflow.CreateOrderFulfillmentWorkflowInput
@@ -78,15 +80,21 @@ function prepareFulfillmentData({
provider_id: string
service_zone: { fulfillment_set: { location?: { id: string } } }
}
reservations: ReservationItemDTO[]
}) {
const inputItems = input.items
const orderItemsMap = new Map<string, Required<OrderDTO>["items"][0]>(
order.items!.map((i) => [i.id, i])
)
const reservationItemMap = new Map<string, ReservationItemDTO>(
reservations.map((r) => [r.line_item_id as string, r])
)
const fulfillmentItems = inputItems.map((i) => {
const orderItem = orderItemsMap.get(i.id)!
const reservation = reservationItemMap.get(i.id)!
return {
line_item_id: i.id,
inventory_item_id: reservation.inventory_item_id,
quantity: i.quantity,
title: orderItem.variant_title ?? orderItem.title,
sku: orderItem.variant_sku || "",
@@ -118,7 +126,7 @@ function prepareFulfillmentData({
}
}
function prepareInventoryReservations({ reservations, order, input }) {
function prepareInventoryUpdate({ reservations, order, input }) {
if (!reservations || !reservations.length) {
throw new Error(
`No stock reservation found for items ${input.items.map((i) => i.id)}`
@@ -215,8 +223,27 @@ export const createOrderFulfillmentWorkflow = createWorkflow(
throw_if_key_not_found: true,
}).config({ name: "get-shipping-option" })
const lineItemIds = transform({ order }, ({ order }) => {
return order.items?.map((i) => i.id)
})
const reservations = useRemoteQueryStep({
entry_point: "reservations",
fields: [
"id",
"line_item_id",
"quantity",
"inventory_item_id",
"location_id",
],
variables: {
filter: {
line_item_id: lineItemIds,
},
},
}).config({ name: "get-reservations" })
const fulfillmentData = transform(
{ order, input, shippingOption },
{ order, input, shippingOption, reservations },
prepareFulfillmentData
)
@@ -240,30 +267,11 @@ export const createOrderFulfillmentWorkflow = createWorkflow(
]
}
)
createLinkStep(link)
const lineItemIds = transform({ order }, ({ order }) => {
return order.items?.map((i) => i.id)
})
const reservations = useRemoteQueryStep({
entry_point: "reservations",
fields: [
"id",
"line_item_id",
"quantity",
"inventory_item_id",
"location_id",
],
variables: {
filter: {
line_item_id: lineItemIds,
},
},
}).config({ name: "get-reservations" })
createRemoteLinkStep(link)
const { toDelete, toUpdate, inventoryAdjustment } = transform(
{ order, reservations, input },
prepareInventoryReservations
prepareInventoryUpdate
)
parallelize(
@@ -21,7 +21,7 @@ import {
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { createLinkStep, useRemoteQueryStep } from "../../common"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../common"
import { createReturnFulfillmentWorkflow } from "../../fulfillment"
import { updateOrderTaxLinesStep } from "../steps"
import { createReturnStep } from "../steps/create-return"
@@ -326,6 +326,6 @@ export const createReturnOrderWorkflow = createWorkflow(
]
}
)
createLinkStep(link)
createRemoteLinkStep(link)
}
)
@@ -1,4 +1,5 @@
export * from "./archive-orders"
export * from "./cancel-order-fulfillment"
export * from "./complete-orders"
export * from "./create-fulfillment"
export * from "./create-orders"
@@ -1,18 +1,18 @@
import { updateProductsStep } from "../steps/update-products"
import {
dismissRemoteLinkStep,
createLinkStep,
useRemoteQueryStep,
} from "../../common"
import { arrayDifference } from "@medusajs/utils"
import { Modules } from "@medusajs/modules-sdk"
import { ProductTypes } from "@medusajs/types"
import { arrayDifference } from "@medusajs/utils"
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import {
createRemoteLinkStep,
dismissRemoteLinkStep,
useRemoteQueryStep,
} from "../../common"
type UpdateProductsStepInputSelector = {
selector: ProductTypes.FilterableProductProps
@@ -161,7 +161,7 @@ export const updateProductsWorkflow = createWorkflow(
prepareSalesChannelLinks
)
createLinkStep(salesChannelLinks)
createRemoteLinkStep(salesChannelLinks)
return updatedProducts
}