fix(core-flows): handle inventory kit items in mark-as-shipped/delivered flows (#12269)
* fix(core-flows): handle invetory kit items in mark-as-shipped and mark-as-delivered flows * fix: typo * chore: more assertion * chore: fix comment * Update packages/core/core-flows/src/order/workflows/create-shipment.ts Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> * fix: undo comment --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
@@ -168,7 +168,7 @@ function prepareCancelOrderFulfillmentData({
|
||||
// We need to take this into account when canceling the fulfillment to compute quantity of line items not being fulfilled based on fulfillment items and qunatities.
|
||||
// NOTE: for now we only need to find one inventory item of a line item to compute this since when a fulfillment is created all inventory items are fulfilled together.
|
||||
// If we allow to cancel partial fulfillments for an order item, we need to change this.
|
||||
//
|
||||
|
||||
if (iitems?.length) {
|
||||
const iitem = iitems.find(
|
||||
(i) => i.inventory.id === fitem.inventory_item_id
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import {
|
||||
AdditionalData,
|
||||
BigNumberInput,
|
||||
FulfillmentDTO,
|
||||
InventoryItemDTO,
|
||||
OrderDTO,
|
||||
OrderLineItemDTO,
|
||||
OrderWorkflow,
|
||||
ProductVariantDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { FulfillmentEvents, Modules } from "@medusajs/framework/utils"
|
||||
import { FulfillmentEvents, MathBN, Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
@@ -22,9 +26,17 @@ import {
|
||||
throwIfOrderIsCancelled,
|
||||
} from "../utils/order-validation"
|
||||
|
||||
/**
|
||||
* The data to validate the order shipment creation.
|
||||
*/
|
||||
type OrderItemWithVariantDTO = OrderLineItemDTO & {
|
||||
variant?: ProductVariantDTO & {
|
||||
inventory_items: {
|
||||
inventory: InventoryItemDTO
|
||||
variant_id: string
|
||||
inventory_item_id: string
|
||||
required_quantity: number
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export type CreateShipmentValidateOrderStepInput = {
|
||||
/**
|
||||
* The order to create the shipment for.
|
||||
@@ -38,16 +50,16 @@ export type CreateShipmentValidateOrderStepInput = {
|
||||
|
||||
/**
|
||||
* This step validates that a shipment can be created for an order. If the order is cancelled,
|
||||
* the items don't exist in the order, or the fulfillment doesn't exist in the order,
|
||||
* the items don't exist in the order, or the fulfillment doesn't exist in the order,
|
||||
* the step will throw an error.
|
||||
*
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
*
|
||||
* You can retrieve an order's details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
|
||||
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
|
||||
*
|
||||
*
|
||||
* :::
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const data = createShipmentValidateOrder({
|
||||
* order: {
|
||||
@@ -68,10 +80,7 @@ export type CreateShipmentValidateOrderStepInput = {
|
||||
*/
|
||||
export const createShipmentValidateOrder = createStep(
|
||||
"create-shipment-validate-order",
|
||||
({
|
||||
order,
|
||||
input,
|
||||
}: CreateShipmentValidateOrderStepInput) => {
|
||||
({ order, input }: CreateShipmentValidateOrderStepInput) => {
|
||||
const inputItems = input.items
|
||||
|
||||
throwIfOrderIsCancelled({ order })
|
||||
@@ -100,15 +109,48 @@ function prepareRegisterShipmentData({
|
||||
const order_ = order as OrderDTO & { fulfillments: FulfillmentDTO[] }
|
||||
const fulfillment = order_.fulfillments.find((f) => f.id === fulfillId)!
|
||||
|
||||
const lineItemIds = new Array(
|
||||
...new Set(fulfillment.items.map((i) => i.line_item_id))
|
||||
)
|
||||
|
||||
return {
|
||||
order_id: order.id,
|
||||
reference: Modules.FULFILLMENT,
|
||||
reference_id: fulfillment.id,
|
||||
created_by: input.created_by,
|
||||
items: (input.items ?? order.items)!.map((i) => {
|
||||
items: lineItemIds.map((lineItemId) => {
|
||||
// find order item
|
||||
const orderItem = order.items!.find(
|
||||
(i) => i.id === lineItemId
|
||||
) as OrderItemWithVariantDTO
|
||||
// find inventory items
|
||||
const iitems = orderItem!.variant?.inventory_items
|
||||
// find fulfillment item
|
||||
const fitem = fulfillment.items.find(
|
||||
(i) => i.line_item_id === lineItemId
|
||||
)!
|
||||
|
||||
let quantity: BigNumberInput = fitem.quantity
|
||||
|
||||
// NOTE: if the order item has an inventory kit or `required_qunatity` > 1, fulfillment items wont't match 1:1 with order items.
|
||||
// - for each inventory item in the kit, a fulfillment item will be created i.e. one line item could have multiple fulfillment items
|
||||
// - the quantity of the fulfillment item will be the quantity of the order item multiplied by the required quantity of the inventory item
|
||||
//
|
||||
// We need to take this into account when creating a shipment to compute quantity of line items being shipped based on fulfillment items and qunatities.
|
||||
// NOTE: for now we only need to find one inventory item of a line item to compute this since when a fulfillment is created all inventory items are fulfilled together.
|
||||
// If we allow to cancel partial fulfillments for an order item, we need to change this.
|
||||
|
||||
if (iitems?.length) {
|
||||
const iitem = iitems.find(
|
||||
(i) => i.inventory.id === fitem.inventory_item_id
|
||||
)
|
||||
|
||||
quantity = MathBN.div(quantity, iitem!.required_quantity)
|
||||
}
|
||||
|
||||
return {
|
||||
id: i.id,
|
||||
quantity: i.quantity,
|
||||
id: lineItemId as string,
|
||||
quantity,
|
||||
}
|
||||
}),
|
||||
}
|
||||
@@ -117,17 +159,18 @@ function prepareRegisterShipmentData({
|
||||
/**
|
||||
* The data to create a shipment for an order, along with custom data that's passed to the workflow's hooks.
|
||||
*/
|
||||
export type CreateOrderShipmentWorkflowInput = OrderWorkflow.CreateOrderShipmentWorkflowInput & AdditionalData
|
||||
export type CreateOrderShipmentWorkflowInput =
|
||||
OrderWorkflow.CreateOrderShipmentWorkflowInput & AdditionalData
|
||||
|
||||
export const createOrderShipmentWorkflowId = "create-order-shipment"
|
||||
/**
|
||||
* This workflow creates a shipment for an order. It's used by the [Create Order Shipment Admin API Route](https://docs.medusajs.com/api/admin#orders_postordersidfulfillmentsfulfillment_idshipments).
|
||||
*
|
||||
* This workflow has a hook that allows you to perform custom actions on the created shipment. For example, you can pass under `additional_data` custom data that
|
||||
*
|
||||
* This workflow has a hook that allows you to perform custom actions on the created shipment. For example, you can pass under `additional_data` custom data that
|
||||
* allows you to create custom data models linked to the shipment.
|
||||
*
|
||||
*
|
||||
* You can also use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around creating a shipment.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await createOrderShipmentWorkflow(container)
|
||||
* .run({
|
||||
@@ -145,18 +188,16 @@ export const createOrderShipmentWorkflowId = "create-order-shipment"
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Creates a shipment for an order.
|
||||
*
|
||||
*
|
||||
* @property hooks.shipmentCreated - This hook is executed after the shipment is created. You can consume this hook to perform custom actions on the created shipment.
|
||||
*/
|
||||
export const createOrderShipmentWorkflow = createWorkflow(
|
||||
createOrderShipmentWorkflowId,
|
||||
(
|
||||
input: WorkflowData<CreateOrderShipmentWorkflowInput>
|
||||
) => {
|
||||
(input: WorkflowData<CreateOrderShipmentWorkflowInput>) => {
|
||||
const order: OrderDTO = useRemoteQueryStep({
|
||||
entry_point: "orders",
|
||||
fields: [
|
||||
@@ -164,8 +205,16 @@ export const createOrderShipmentWorkflow = createWorkflow(
|
||||
"status",
|
||||
"region_id",
|
||||
"currency_code",
|
||||
"items.*",
|
||||
"items.id",
|
||||
"items.quantity",
|
||||
"items.variant.manage_inventory",
|
||||
"items.variant.inventory_items.inventory.id",
|
||||
"items.variant.inventory_items.required_quantity",
|
||||
"fulfillments.*",
|
||||
"fulfillments.items.id",
|
||||
"fulfillments.items.quantity",
|
||||
"fulfillments.items.line_item_id",
|
||||
"fulfillments.items.inventory_item_id",
|
||||
],
|
||||
variables: { id: input.order_id },
|
||||
list: false,
|
||||
|
||||
+66
-13
@@ -1,9 +1,13 @@
|
||||
import {
|
||||
BigNumberInput,
|
||||
FulfillmentDTO,
|
||||
InventoryItemDTO,
|
||||
OrderDTO,
|
||||
OrderLineItemDTO,
|
||||
ProductVariantDTO,
|
||||
RegisterOrderDeliveryDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { FulfillmentEvents, Modules } from "@medusajs/framework/utils"
|
||||
import { FulfillmentEvents, MathBN, Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
@@ -20,6 +24,17 @@ import {
|
||||
throwIfOrderIsCancelled,
|
||||
} from "../utils/order-validation"
|
||||
|
||||
type OrderItemWithVariantDTO = OrderLineItemDTO & {
|
||||
variant?: ProductVariantDTO & {
|
||||
inventory_items: {
|
||||
inventory: InventoryItemDTO
|
||||
variant_id: string
|
||||
inventory_item_id: string
|
||||
required_quantity: number
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The data to validate the order fulfillment deliverability.
|
||||
*/
|
||||
@@ -27,7 +42,7 @@ export type OrderFulfillmentDeliverabilityValidationStepInput = {
|
||||
/**
|
||||
* The order to validate the fulfillment deliverability for.
|
||||
*/
|
||||
order: OrderDTO & {
|
||||
order: OrderDTO & {
|
||||
/**
|
||||
* The fulfillments in the order.
|
||||
*/
|
||||
@@ -45,14 +60,14 @@ export const orderFulfillmentDeliverablilityValidationStepId =
|
||||
* This step validates that the order fulfillment can be delivered. If the order is cancelled,
|
||||
* the items to mark as delivered don't exist in the order, or the fulfillment doesn't exist in the order,
|
||||
* the step will throw an error.
|
||||
*
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
*
|
||||
* You can retrieve an order and fulfillment's details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
|
||||
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
|
||||
*
|
||||
*
|
||||
* :::
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const data = orderFulfillmentDeliverablilityValidationStep({
|
||||
* order: {
|
||||
@@ -113,14 +128,48 @@ function prepareRegisterDeliveryData({
|
||||
(f) => f.id === fulfillment.id
|
||||
)!
|
||||
|
||||
const lineItemIds = new Array(
|
||||
...new Set(orderFulfillment.items.map((i) => i.line_item_id))
|
||||
)
|
||||
|
||||
return {
|
||||
order_id: order.id,
|
||||
reference: Modules.FULFILLMENT,
|
||||
reference_id: orderFulfillment.id,
|
||||
items: orderFulfillment.items!.map((i) => {
|
||||
items: lineItemIds!.map((lineItemId) => {
|
||||
// find order item
|
||||
const orderItem = order.items!.find(
|
||||
(i) => i.id === lineItemId
|
||||
) as OrderItemWithVariantDTO
|
||||
// find inventory items
|
||||
const iitems = orderItem!.variant?.inventory_items
|
||||
// find fulfillment item
|
||||
const fitem = orderFulfillment.items.find(
|
||||
(i) => i.line_item_id === lineItemId
|
||||
)!
|
||||
|
||||
let quantity: BigNumberInput = fitem.quantity
|
||||
|
||||
// NOTE: if the order item has an inventory kit or `required_qunatity` > 1, fulfillment items wont't match 1:1 with order items.
|
||||
// - for each inventory item in the kit, a fulfillment item will be created i.e. one line item could have multiple fulfillment items
|
||||
// - the quantity of the fulfillment item will be the quantity of the order item multiplied by the required quantity of the inventory item
|
||||
//
|
||||
// We need to take this into account when marking the fulfillment as delivered to compute quantity of line items being delivered based on fulfillment items and qunatities.
|
||||
// NOTE: for now we only need to find one inventory item of a line item to compute this since when a fulfillment is created all inventory items are fulfilled together.
|
||||
// If we allow to cancel partial fulfillments for an order item, we need to change this.
|
||||
//
|
||||
|
||||
if (iitems?.length) {
|
||||
const iitem = iitems.find(
|
||||
(i) => i.inventory.id === fitem.inventory_item_id
|
||||
)
|
||||
|
||||
quantity = MathBN.div(quantity, iitem!.required_quantity)
|
||||
}
|
||||
|
||||
return {
|
||||
id: i.line_item_id!,
|
||||
quantity: i.quantity!,
|
||||
id: lineItemId as string,
|
||||
quantity,
|
||||
}
|
||||
}),
|
||||
}
|
||||
@@ -145,10 +194,10 @@ export const markOrderFulfillmentAsDeliveredWorkflowId =
|
||||
/**
|
||||
* This workflow marks a fulfillment in an order as delivered. It's used by the
|
||||
* [Mark Fulfillment as Delivered Admin API Route](https://docs.medusajs.com/api/admin#orders_postordersidfulfillmentsfulfillment_idmarkasdelivered).
|
||||
*
|
||||
*
|
||||
* You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around
|
||||
* marking a fulfillment as delivered.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await markOrderFulfillmentAsDeliveredWorkflow(container)
|
||||
* .run({
|
||||
@@ -157,9 +206,9 @@ export const markOrderFulfillmentAsDeliveredWorkflowId =
|
||||
* fulfillmentId: "ful_123",
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Mark a fulfillment in an order as delivered.
|
||||
*/
|
||||
export const markOrderFulfillmentAsDeliveredWorkflow = createWorkflow(
|
||||
@@ -185,8 +234,12 @@ export const markOrderFulfillmentAsDeliveredWorkflow = createWorkflow(
|
||||
"fulfillments.items.id",
|
||||
"fulfillments.items.quantity",
|
||||
"fulfillments.items.line_item_id",
|
||||
"fulfillments.items.inventory_item_id",
|
||||
"items.id",
|
||||
"items.quantity",
|
||||
"items.variant.manage_inventory",
|
||||
"items.variant.inventory_items.inventory.id",
|
||||
"items.variant.inventory_items.required_quantity",
|
||||
],
|
||||
variables: { id: orderId },
|
||||
throw_if_key_not_found: true,
|
||||
|
||||
Reference in New Issue
Block a user