fix(core-flows): Locations levels check in draft order and order edit flows (#12881)

* fix: Inventory check

* mend

* centralise fields

* Create few-owls-push.md
This commit is contained in:
Oli Juhl
2025-07-03 17:31:04 +02:00
committed by GitHub
parent 779ed018b9
commit 46bf7ae7ae
12 changed files with 233 additions and 93 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/core-flows": patch
---
fix(core-flows): Locations levels check in draft order and order edit flows
@@ -25,6 +25,7 @@ medusaIntegrationTestRunner({
let inventoryItem let inventoryItem
let inventoryItemExtra let inventoryItemExtra
let location let location
let locationTwo
let productExtra let productExtra
const shippingProviderId = "manual_test-provider" const shippingProviderId = "manual_test-provider"
@@ -553,10 +554,29 @@ medusaIntegrationTestRunner({
) )
).data.stock_location ).data.stock_location
locationTwo = (
await api.post(
`/admin/stock-locations`,
{
name: "Test location two",
},
adminHeaders
)
).data.stock_location
await api.post( await api.post(
`/admin/inventory-items/${inventoryItemLarge.id}/location-levels`, `/admin/inventory-items/${inventoryItemLarge.id}/location-levels`,
{ {
location_id: location.id, location_id: location.id,
stocked_quantity: 0,
},
adminHeaders
)
await api.post(
`/admin/inventory-items/${inventoryItemLarge.id}/location-levels`,
{
location_id: locationTwo.id,
stocked_quantity: 10, stocked_quantity: 10,
}, },
adminHeaders adminHeaders
@@ -766,6 +786,14 @@ medusaIntegrationTestRunner({
stock_location_id: location.id, stock_location_id: location.id,
}, },
}, },
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: locationTwo.id,
},
},
]) ])
}) })
@@ -860,6 +888,60 @@ medusaIntegrationTestRunner({
]) ])
) )
}) })
it("should manage inventory across locations in order edit", async () => {
let edit = (
await api.post(
`/admin/order-edits`,
{ order_id: order.id },
adminHeaders
)
).data.order_change
// Add item
await api.post(
`/admin/order-edits/${order.id}/items`,
{
items: [
{
variant_id: product.variants.find((v) => v.title === "L shirt")
.id,
quantity: 1,
},
],
},
adminHeaders
)
edit = (
await api.post(
`/admin/order-edits/${order.id}/request`,
{},
adminHeaders
)
).data.order_change
edit = (
await api.post(
`/admin/order-edits/${order.id}/confirm`,
{},
adminHeaders
)
).data.order_change
order = (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data
.order
expect(order.items.length).toBe(3)
expect(order.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
subtitle: "L shirt",
quantity: 2,
}),
])
)
})
}) })
describe("Order Edit Shipping Methods", () => { describe("Order Edit Shipping Methods", () => {
@@ -118,6 +118,7 @@ export const completeCartFields = [
"items.variant.inventory_items.inventory.location_levels.reserved_quantity", "items.variant.inventory_items.inventory.location_levels.reserved_quantity",
"items.variant.inventory_items.inventory.location_levels.raw_stocked_quantity", "items.variant.inventory_items.inventory.location_levels.raw_stocked_quantity",
"items.variant.inventory_items.inventory.location_levels.raw_reserved_quantity", "items.variant.inventory_items.inventory.location_levels.raw_reserved_quantity",
"items.variant.inventory_items.inventory.location_levels.location_id",
"items.variant.inventory_items.inventory.location_levels.stock_locations.id", "items.variant.inventory_items.inventory.location_levels.stock_locations.id",
"items.variant.inventory_items.inventory.location_levels.stock_locations.name", "items.variant.inventory_items.inventory.location_levels.stock_locations.name",
"items.variant.inventory_items.inventory.location_levels.stock_locations.sales_channels.id", "items.variant.inventory_items.inventory.location_levels.stock_locations.sales_channels.id",
@@ -168,6 +169,7 @@ export const productVariantsFields = [
"inventory_items.inventory.requires_shipping", "inventory_items.inventory.requires_shipping",
"inventory_items.inventory.location_levels.stocked_quantity", "inventory_items.inventory.location_levels.stocked_quantity",
"inventory_items.inventory.location_levels.reserved_quantity", "inventory_items.inventory.location_levels.reserved_quantity",
"inventory_items.inventory.location_levels.location_id",
"inventory_items.inventory.location_levels.raw_stocked_quantity", "inventory_items.inventory.location_levels.raw_stocked_quantity",
"inventory_items.inventory.location_levels.raw_reserved_quantity", "inventory_items.inventory.location_levels.raw_reserved_quantity",
"inventory_items.inventory.location_levels.stock_locations.id", "inventory_items.inventory.location_levels.stock_locations.id",
@@ -9,6 +9,43 @@ import {
deepFlatMap, deepFlatMap,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
export const requiredOrderFieldsForInventoryConfirmation = [
"id",
"version",
"canceled_at",
"sales_channel_id",
"items.*",
"items.variant.manage_inventory",
"items.variant.allow_backorder",
"items.variant.inventory_items.inventory_item_id",
"items.variant.inventory_items.required_quantity",
"items.variant.inventory_items.inventory.location_levels.stocked_quantity",
"items.variant.inventory_items.inventory.location_levels.reserved_quantity",
"items.variant.inventory_items.inventory.location_levels.raw_stocked_quantity",
"items.variant.inventory_items.inventory.location_levels.raw_reserved_quantity",
"items.variant.inventory_items.inventory.location_levels.location_id",
"items.variant.inventory_items.inventory.location_levels.stock_locations.id",
"items.variant.inventory_items.inventory.location_levels.stock_locations.name",
"items.variant.inventory_items.inventory.location_levels.stock_locations.sales_channels.id",
"items.variant.inventory_items.inventory.location_levels.stock_locations.sales_channels.name",
]
export const requiredVariantFieldsForInventoryConfirmation = [
"manage_inventory",
"allow_backorder",
"inventory_items.inventory_item_id",
"inventory_items.required_quantity",
"inventory_items.inventory.location_levels.stocked_quantity",
"inventory_items.inventory.location_levels.reserved_quantity",
"inventory_items.inventory.location_levels.raw_stocked_quantity",
"inventory_items.inventory.location_levels.raw_reserved_quantity",
"inventory_items.inventory.location_levels.location_id",
"inventory_items.inventory.location_levels.stock_locations.id",
"inventory_items.inventory.location_levels.stock_locations.name",
"inventory_items.inventory.location_levels.stock_locations.sales_channels.id",
"inventory_items.inventory.location_levels.stock_locations.sales_channels.name",
]
interface ConfirmInventoryPreparationInput { interface ConfirmInventoryPreparationInput {
product_variant_inventory_items: { product_variant_inventory_items: {
variant_id: string variant_id: string
@@ -3,7 +3,11 @@ import {
AddToCartWorkflowInputDTO, AddToCartWorkflowInputDTO,
ConfirmVariantInventoryWorkflowInputDTO, ConfirmVariantInventoryWorkflowInputDTO,
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
import { CartWorkflowEvents, isDefined } from "@medusajs/framework/utils" import {
CartWorkflowEvents,
deduplicate,
isDefined,
} from "@medusajs/framework/utils"
import { import {
createHook, createHook,
createWorkflow, createWorkflow,
@@ -28,13 +32,14 @@ import {
cartFieldsForPricingContext, cartFieldsForPricingContext,
productVariantsFields, productVariantsFields,
} from "../utils/fields" } from "../utils/fields"
import { requiredVariantFieldsForInventoryConfirmation } from "../utils/prepare-confirm-inventory-input"
import { import {
prepareLineItemData, prepareLineItemData,
PrepareLineItemDataInput, PrepareLineItemDataInput,
} from "../utils/prepare-line-item-data" } from "../utils/prepare-line-item-data"
import { pricingContextResult } from "../utils/schemas"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory" import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
import { refreshCartItemsWorkflow } from "./refresh-cart-items" import { refreshCartItemsWorkflow } from "./refresh-cart-items"
import { pricingContextResult } from "../utils/schemas"
const cartFields = ["completed_at"].concat(cartFieldsForPricingContext) const cartFields = ["completed_at"].concat(cartFieldsForPricingContext)
@@ -163,7 +168,10 @@ export const addToCartWorkflow = createWorkflow(
}).then(() => { }).then(() => {
return useRemoteQueryStep({ return useRemoteQueryStep({
entry_point: "variants", entry_point: "variants",
fields: productVariantsFields, fields: deduplicate([
...productVariantsFields,
...requiredVariantFieldsForInventoryConfirmation,
]),
variables: { variables: {
id: variantIds, id: variantIds,
calculated_price: { calculated_price: {
@@ -4,6 +4,7 @@ import {
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
import { import {
CartWorkflowEvents, CartWorkflowEvents,
deduplicate,
isDefined, isDefined,
MedusaError, MedusaError,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
@@ -25,18 +26,19 @@ import {
findSalesChannelStep, findSalesChannelStep,
} from "../steps" } from "../steps"
import { validateLineItemPricesStep } from "../steps/validate-line-item-prices" import { validateLineItemPricesStep } from "../steps/validate-line-item-prices"
import { validateSalesChannelStep } from "../steps/validate-sales-channel"
import { validateVariantPricesStep } from "../steps/validate-variant-prices" import { validateVariantPricesStep } from "../steps/validate-variant-prices"
import { productVariantsFields } from "../utils/fields" import { productVariantsFields } from "../utils/fields"
import { requiredVariantFieldsForInventoryConfirmation } from "../utils/prepare-confirm-inventory-input"
import { import {
prepareLineItemData, prepareLineItemData,
PrepareLineItemDataInput, PrepareLineItemDataInput,
} from "../utils/prepare-line-item-data" } from "../utils/prepare-line-item-data"
import { pricingContextResult } from "../utils/schemas"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory" import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection" import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection"
import { updateCartPromotionsWorkflow } from "./update-cart-promotions" import { updateCartPromotionsWorkflow } from "./update-cart-promotions"
import { updateTaxLinesWorkflow } from "./update-tax-lines" import { updateTaxLinesWorkflow } from "./update-tax-lines"
import { validateSalesChannelStep } from "../steps/validate-sales-channel"
import { pricingContextResult } from "../utils/schemas"
/** /**
* The data to create the cart, along with custom data that's passed to the workflow's hooks. * The data to create the cart, along with custom data that's passed to the workflow's hooks.
@@ -170,7 +172,10 @@ export const createCartWorkflow = createWorkflow(
}).then(() => { }).then(() => {
return useRemoteQueryStep({ return useRemoteQueryStep({
entry_point: "variants", entry_point: "variants",
fields: productVariantsFields, fields: deduplicate([
...productVariantsFields,
...requiredVariantFieldsForInventoryConfirmation,
]),
variables: { variables: {
id: variantIds, id: variantIds,
calculated_price: { calculated_price: {
@@ -2,7 +2,12 @@ import {
AdditionalData, AdditionalData,
UpdateLineItemInCartWorkflowInputDTO, UpdateLineItemInCartWorkflowInputDTO,
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
import { CartWorkflowEvents, isDefined, MedusaError } from "@medusajs/framework/utils" import {
CartWorkflowEvents,
deduplicate,
isDefined,
MedusaError,
} from "@medusajs/framework/utils"
import { import {
createHook, createHook,
createWorkflow, createWorkflow,
@@ -21,9 +26,10 @@ import {
cartFieldsForPricingContext, cartFieldsForPricingContext,
productVariantsFields, productVariantsFields,
} from "../utils/fields" } from "../utils/fields"
import { requiredVariantFieldsForInventoryConfirmation } from "../utils/prepare-confirm-inventory-input"
import { pricingContextResult } from "../utils/schemas"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory" import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
import { refreshCartItemsWorkflow } from "./refresh-cart-items" import { refreshCartItemsWorkflow } from "./refresh-cart-items"
import { pricingContextResult } from "../utils/schemas"
const cartFields = cartFieldsForPricingContext.concat(["items.*"]) const cartFields = cartFieldsForPricingContext.concat(["items.*"])
@@ -148,7 +154,10 @@ export const updateLineItemInCartWorkflow = createWorkflow(
}).then(() => { }).then(() => {
return useRemoteQueryStep({ return useRemoteQueryStep({
entry_point: "variants", entry_point: "variants",
fields: productVariantsFields, fields: deduplicate([
...productVariantsFields,
...requiredVariantFieldsForInventoryConfirmation,
]),
variables: { variables: {
id: variantIds, id: variantIds,
calculated_price: { calculated_price: {
@@ -10,7 +10,10 @@ import {
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { BigNumberInput, OrderChangeDTO, OrderDTO } from "@medusajs/types" import { BigNumberInput, OrderChangeDTO, OrderDTO } from "@medusajs/types"
import { reserveInventoryStep } from "../../cart" import { reserveInventoryStep } from "../../cart"
import { prepareConfirmInventoryInput } from "../../cart/utils/prepare-confirm-inventory-input" import {
prepareConfirmInventoryInput,
requiredOrderFieldsForInventoryConfirmation,
} from "../../cart/utils/prepare-confirm-inventory-input"
import { useRemoteQueryStep } from "../../common" import { useRemoteQueryStep } from "../../common"
import { import {
createOrUpdateOrderPaymentCollectionWorkflow, createOrUpdateOrderPaymentCollectionWorkflow,
@@ -114,21 +117,7 @@ export const confirmDraftOrderEditWorkflow = createWorkflow(
const orderItems = useRemoteQueryStep({ const orderItems = useRemoteQueryStep({
entry_point: "order", entry_point: "order",
fields: [ fields: requiredOrderFieldsForInventoryConfirmation,
"id",
"version",
"canceled_at",
"sales_channel_id",
"items.*",
"items.variant.manage_inventory",
"items.variant.allow_backorder",
"items.variant.inventory_items.inventory_item_id",
"items.variant.inventory_items.required_quantity",
"items.variant.inventory_items.inventory.location_levels.stock_locations.id",
"items.variant.inventory_items.inventory.location_levels.stock_locations.name",
"items.variant.inventory_items.inventory.location_levels.stock_locations.sales_channels.id",
"items.variant.inventory_items.inventory.location_levels.stock_locations.sales_channels.name",
],
variables: { id: input.order_id }, variables: { id: input.order_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
@@ -19,6 +19,7 @@ export const productVariantsFields = [
"inventory_items.inventory.requires_shipping", "inventory_items.inventory.requires_shipping",
"inventory_items.inventory.location_levels.stocked_quantity", "inventory_items.inventory.location_levels.stocked_quantity",
"inventory_items.inventory.location_levels.reserved_quantity", "inventory_items.inventory.location_levels.reserved_quantity",
"inventory_items.inventory.location_levels.location_id",
"inventory_items.inventory.location_levels.raw_stocked_quantity", "inventory_items.inventory.location_levels.raw_stocked_quantity",
"inventory_items.inventory.location_levels.raw_reserved_quantity", "inventory_items.inventory.location_levels.raw_reserved_quantity",
"inventory_items.inventory.location_levels.stock_locations.id", "inventory_items.inventory.location_levels.stock_locations.id",
@@ -3,7 +3,7 @@ import {
OrderLineItemDTO, OrderLineItemDTO,
OrderWorkflow, OrderWorkflow,
} from "@medusajs/framework/types" } from "@medusajs/framework/types"
import { isDefined, MedusaError } from "@medusajs/framework/utils" import { deduplicate, isDefined, MedusaError } from "@medusajs/framework/utils"
import { import {
createHook, createHook,
createWorkflow, createWorkflow,
@@ -18,15 +18,16 @@ import { findOrCreateCustomerStep } from "../../cart/steps/find-or-create-custom
import { findSalesChannelStep } from "../../cart/steps/find-sales-channel" import { findSalesChannelStep } from "../../cart/steps/find-sales-channel"
import { validateLineItemPricesStep } from "../../cart/steps/validate-line-item-prices" import { validateLineItemPricesStep } from "../../cart/steps/validate-line-item-prices"
import { validateVariantPricesStep } from "../../cart/steps/validate-variant-prices" import { validateVariantPricesStep } from "../../cart/steps/validate-variant-prices"
import { requiredVariantFieldsForInventoryConfirmation } from "../../cart/utils/prepare-confirm-inventory-input"
import { import {
prepareLineItemData, prepareLineItemData,
PrepareLineItemDataInput, PrepareLineItemDataInput,
} from "../../cart/utils/prepare-line-item-data" } from "../../cart/utils/prepare-line-item-data"
import { pricingContextResult } from "../../cart/utils/schemas"
import { confirmVariantInventoryWorkflow } from "../../cart/workflows/confirm-variant-inventory" import { confirmVariantInventoryWorkflow } from "../../cart/workflows/confirm-variant-inventory"
import { useRemoteQueryStep } from "../../common" import { useRemoteQueryStep } from "../../common"
import { createOrderLineItemsStep } from "../steps" import { createOrderLineItemsStep } from "../steps"
import { productVariantsFields } from "../utils/fields" import { productVariantsFields } from "../utils/fields"
import { pricingContextResult } from "../../cart/utils/schemas"
function prepareLineItems(data) { function prepareLineItems(data) {
const items = (data.input.items ?? []).map((item) => { const items = (data.input.items ?? []).map((item) => {
@@ -197,7 +198,10 @@ export const addOrderLineItemsWorkflow = createWorkflow(
}).then(() => { }).then(() => {
return useRemoteQueryStep({ return useRemoteQueryStep({
entry_point: "variants", entry_point: "variants",
fields: productVariantsFields, fields: deduplicate([
...productVariantsFields,
...requiredVariantFieldsForInventoryConfirmation,
]),
variables: { variables: {
id: variantIds, id: variantIds,
calculated_price: { calculated_price: {
@@ -1,5 +1,10 @@
import { AdditionalData, CreateOrderDTO } from "@medusajs/framework/types" import { AdditionalData, CreateOrderDTO } from "@medusajs/framework/types"
import { MedusaError, isDefined, isPresent } from "@medusajs/framework/utils" import {
MedusaError,
deduplicate,
isDefined,
isPresent,
} from "@medusajs/framework/utils"
import { import {
WorkflowData, WorkflowData,
WorkflowResponse, WorkflowResponse,
@@ -14,16 +19,17 @@ import { findOrCreateCustomerStep } from "../../cart/steps/find-or-create-custom
import { findSalesChannelStep } from "../../cart/steps/find-sales-channel" import { findSalesChannelStep } from "../../cart/steps/find-sales-channel"
import { validateLineItemPricesStep } from "../../cart/steps/validate-line-item-prices" import { validateLineItemPricesStep } from "../../cart/steps/validate-line-item-prices"
import { validateVariantPricesStep } from "../../cart/steps/validate-variant-prices" import { validateVariantPricesStep } from "../../cart/steps/validate-variant-prices"
import { requiredVariantFieldsForInventoryConfirmation } from "../../cart/utils/prepare-confirm-inventory-input"
import { import {
PrepareLineItemDataInput, PrepareLineItemDataInput,
prepareLineItemData, prepareLineItemData,
} from "../../cart/utils/prepare-line-item-data" } from "../../cart/utils/prepare-line-item-data"
import { pricingContextResult } from "../../cart/utils/schemas"
import { confirmVariantInventoryWorkflow } from "../../cart/workflows/confirm-variant-inventory" import { confirmVariantInventoryWorkflow } from "../../cart/workflows/confirm-variant-inventory"
import { useRemoteQueryStep } from "../../common" import { useRemoteQueryStep } from "../../common"
import { createOrdersStep } from "../steps" import { createOrdersStep } from "../steps"
import { productVariantsFields } from "../utils/fields" import { productVariantsFields } from "../utils/fields"
import { updateOrderTaxLinesWorkflow } from "./update-tax-lines" import { updateOrderTaxLinesWorkflow } from "./update-tax-lines"
import { pricingContextResult } from "../../cart/utils/schemas"
function prepareLineItems(data) { function prepareLineItems(data) {
const items = (data.input.items ?? []).map((item) => { const items = (data.input.items ?? []).map((item) => {
@@ -221,7 +227,10 @@ export const createOrderWorkflow = createWorkflow(
}).then(() => { }).then(() => {
return useRemoteQueryStep({ return useRemoteQueryStep({
entry_point: "variants", entry_point: "variants",
fields: productVariantsFields, fields: deduplicate([
...productVariantsFields,
...requiredVariantFieldsForInventoryConfirmation,
]),
variables: { variables: {
id: variantIds, id: variantIds,
calculated_price: { calculated_price: {
@@ -17,7 +17,10 @@ import {
transform, transform,
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { reserveInventoryStep } from "../../../cart/steps/reserve-inventory" import { reserveInventoryStep } from "../../../cart/steps/reserve-inventory"
import { prepareConfirmInventoryInput } from "../../../cart/utils/prepare-confirm-inventory-input" import {
prepareConfirmInventoryInput,
requiredOrderFieldsForInventoryConfirmation,
} from "../../../cart/utils/prepare-confirm-inventory-input"
import { emitEventStep, useRemoteQueryStep } from "../../../common" import { emitEventStep, useRemoteQueryStep } from "../../../common"
import { deleteReservationsByLineItemsStep } from "../../../reservation" import { deleteReservationsByLineItemsStep } from "../../../reservation"
import { previewOrderChangeStep } from "../../steps" import { previewOrderChangeStep } from "../../steps"
@@ -171,21 +174,7 @@ export const confirmOrderEditRequestWorkflow = createWorkflow(
const orderItems = useRemoteQueryStep({ const orderItems = useRemoteQueryStep({
entry_point: "order", entry_point: "order",
fields: [ fields: requiredOrderFieldsForInventoryConfirmation,
"id",
"version",
"canceled_at",
"sales_channel_id",
"items.*",
"items.variant.manage_inventory",
"items.variant.allow_backorder",
"items.variant.inventory_items.inventory_item_id",
"items.variant.inventory_items.required_quantity",
"items.variant.inventory_items.inventory.location_levels.stock_locations.id",
"items.variant.inventory_items.inventory.location_levels.stock_locations.name",
"items.variant.inventory_items.inventory.location_levels.stock_locations.sales_channels.id",
"items.variant.inventory_items.inventory.location_levels.stock_locations.sales_channels.name",
],
variables: { id: input.order_id }, variables: { id: input.order_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,