fix(core-flows): fulfillment reservation check (#11735)
This commit is contained in:
5
.changeset/many-cycles-count.md
Normal file
5
.changeset/many-cycles-count.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(core-flows): fulfillment reservation check
|
||||||
@@ -331,6 +331,17 @@ medusaIntegrationTestRunner({
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// create reservation for inventory item that is initially on the order
|
||||||
|
const inventoryModule = container.resolve(Modules.INVENTORY)
|
||||||
|
await inventoryModule.createReservationItems([
|
||||||
|
{
|
||||||
|
inventory_item_id: inventoryItem.id,
|
||||||
|
location_id: location.id,
|
||||||
|
quantity: 2,
|
||||||
|
line_item_id: order.items[0].id,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
const shippingOptionPayload = {
|
const shippingOptionPayload = {
|
||||||
name: "Return shipping",
|
name: "Return shipping",
|
||||||
service_zone_id: fulfillmentSet.service_zones[0].id,
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
|||||||
@@ -325,6 +325,17 @@ medusaIntegrationTestRunner({
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// create reservation for inventory item that is initially on the order
|
||||||
|
const inventoryModule = container.resolve(Modules.INVENTORY)
|
||||||
|
await inventoryModule.createReservationItems([
|
||||||
|
{
|
||||||
|
inventory_item_id: inventoryItem.id,
|
||||||
|
location_id: location.id,
|
||||||
|
quantity: 2,
|
||||||
|
line_item_id: order.items[0].id,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
const shippingOptionPayload = {
|
const shippingOptionPayload = {
|
||||||
name: "Return shipping",
|
name: "Return shipping",
|
||||||
service_zone_id: fulfillmentSet.service_zones[0].id,
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ medusaIntegrationTestRunner({
|
|||||||
let location
|
let location
|
||||||
let item
|
let item
|
||||||
let returnShippingOption
|
let returnShippingOption
|
||||||
|
let outboundShippingOption
|
||||||
const shippingProviderId = "manual_test-provider"
|
const shippingProviderId = "manual_test-provider"
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -135,7 +136,7 @@ medusaIntegrationTestRunner({
|
|||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
amount: 20,
|
amount: 0, // Free shipping
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
rules: [
|
rules: [
|
||||||
@@ -160,7 +161,7 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.shipping_option
|
).data.shipping_option
|
||||||
|
|
||||||
const outboundShippingOption = (
|
outboundShippingOption = (
|
||||||
await api.post(
|
await api.post(
|
||||||
"/admin/shipping-options",
|
"/admin/shipping-options",
|
||||||
outboundShippingOptionPayload,
|
outboundShippingOptionPayload,
|
||||||
@@ -289,6 +290,15 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// If claim has outbound items, we need to set the outbound shipping method for reservation to be created
|
||||||
|
// TODO: change condition in confirm workfow to crate reservation depending on shipping since we can have
|
||||||
|
// reservations for inventory items that don't require shipping
|
||||||
|
await api.post(
|
||||||
|
`/admin/claims/${singleOutboundClaim.id}/outbound/shipping-method`,
|
||||||
|
{ shipping_option_id: outboundShippingOption.id },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/claims/${singleOutboundClaim.id}/request`,
|
`/admin/claims/${singleOutboundClaim.id}/request`,
|
||||||
{},
|
{},
|
||||||
|
|||||||
@@ -239,6 +239,17 @@ medusaIntegrationTestRunner({
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// create reservation for inventory item that is initially on the order
|
||||||
|
const inventoryModule = container.resolve(Modules.INVENTORY)
|
||||||
|
await inventoryModule.createReservationItems([
|
||||||
|
{
|
||||||
|
inventory_item_id: inventoryItem.id,
|
||||||
|
location_id: location.id,
|
||||||
|
quantity: 2,
|
||||||
|
line_item_id: order.items[0].id,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
const shippingOptionPayload = {
|
const shippingOptionPayload = {
|
||||||
name: "Return shipping",
|
name: "Return shipping",
|
||||||
service_zone_id: fulfillmentSet.service_zones[0].id,
|
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||||
@@ -866,7 +877,9 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.inventory_levels
|
).data.inventory_levels
|
||||||
expect(inventoryLevel[0].stocked_quantity).toEqual(2)
|
|
||||||
|
// we had 2 items in stock that were reserved for the order and then fulfilled
|
||||||
|
expect(inventoryLevel[0].stocked_quantity).toEqual(0)
|
||||||
|
|
||||||
let result = await api.post(
|
let result = await api.post(
|
||||||
`/admin/returns/${returnId}/receive`,
|
`/admin/returns/${returnId}/receive`,
|
||||||
@@ -987,7 +1000,7 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.inventory_levels
|
).data.inventory_levels
|
||||||
expect(inventoryLevel[0].stocked_quantity).toEqual(3)
|
expect(inventoryLevel[0].stocked_quantity).toEqual(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -486,6 +486,9 @@ medusaIntegrationTestRunner({
|
|||||||
it("should revert an order fulfillment when it fails and recreate it when tried again", async () => {
|
it("should revert an order fulfillment when it fails and recreate it when tried again", async () => {
|
||||||
const order = await createOrderFixture({ container, product, location })
|
const order = await createOrderFixture({ container, product, location })
|
||||||
|
|
||||||
|
const itemId = order.items?.find((i) => i.title === "Custom Item 2")!
|
||||||
|
.id as string
|
||||||
|
|
||||||
// Create a fulfillment
|
// Create a fulfillment
|
||||||
const createOrderFulfillmentData: OrderWorkflow.CreateOrderFulfillmentWorkflowInput =
|
const createOrderFulfillmentData: OrderWorkflow.CreateOrderFulfillmentWorkflowInput =
|
||||||
{
|
{
|
||||||
@@ -493,7 +496,7 @@ medusaIntegrationTestRunner({
|
|||||||
created_by: "user_1",
|
created_by: "user_1",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: order.items![0].id,
|
id: itemId,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -540,12 +543,14 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
const [orderFulfill] = await remoteQuery(remoteQueryObject)
|
const [orderFulfill] = await remoteQuery(remoteQueryObject)
|
||||||
|
|
||||||
|
const fulfilledItem = orderFulfill.items?.find((i) => i.id === itemId)
|
||||||
|
|
||||||
expect(orderFulfill.fulfillments).toHaveLength(1)
|
expect(orderFulfill.fulfillments).toHaveLength(1)
|
||||||
expect(orderFulfill.items[0].detail.fulfilled_quantity).toEqual(1)
|
expect(fulfilledItem?.detail.fulfilled_quantity).toEqual(1)
|
||||||
|
|
||||||
const inventoryModule = container.resolve(Modules.INVENTORY)
|
const inventoryModule = container.resolve(Modules.INVENTORY)
|
||||||
const reservation = await inventoryModule.listReservationItems({
|
const reservation = await inventoryModule.listReservationItems({
|
||||||
line_item_id: order.items![0].id,
|
line_item_id: itemId,
|
||||||
})
|
})
|
||||||
expect(reservation).toHaveLength(0)
|
expect(reservation).toHaveLength(0)
|
||||||
|
|
||||||
|
|||||||
@@ -42,16 +42,16 @@ export type OrderClaimAddNewItemValidationStepInput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This step validates that new items can be added to the claim. If the
|
* This step validates that new items can be added to the claim. If the
|
||||||
* order or claim is canceled, or the order change is not active, the step will throw an error.
|
* order or claim is canceled, or the order change is not active, the step will throw an error.
|
||||||
*
|
*
|
||||||
* :::note
|
* :::note
|
||||||
*
|
*
|
||||||
* You can retrieve an order, order claim, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
|
* You can retrieve an order, order claim, and order change details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
|
||||||
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
|
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
|
||||||
*
|
*
|
||||||
* :::
|
* :::
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const data = orderClaimAddNewItemValidationStep({
|
* const data = orderClaimAddNewItemValidationStep({
|
||||||
* order: {
|
* order: {
|
||||||
@@ -85,10 +85,10 @@ export const orderClaimAddNewItemWorkflowId = "claim-add-new-item"
|
|||||||
/**
|
/**
|
||||||
* This workflow adds outbound (or new) items to a claim. It's used by the
|
* This workflow adds outbound (or new) items to a claim. It's used by the
|
||||||
* [Add Outbound Items Admin API Route](https://docs.medusajs.com/api/admin#claims_postclaimsidoutbounditems).
|
* [Add Outbound Items Admin API Route](https://docs.medusajs.com/api/admin#claims_postclaimsidoutbounditems).
|
||||||
*
|
*
|
||||||
* You can use this workflow within your customizations or your own custom workflows, allowing you to add outbound items to a claim
|
* You can use this workflow within your customizations or your own custom workflows, allowing you to add outbound items to a claim
|
||||||
* in your custom flows.
|
* in your custom flows.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const { result } = await orderClaimAddNewItemWorkflow(container)
|
* const { result } = await orderClaimAddNewItemWorkflow(container)
|
||||||
* .run({
|
* .run({
|
||||||
@@ -102,9 +102,9 @@ export const orderClaimAddNewItemWorkflowId = "claim-add-new-item"
|
|||||||
* ]
|
* ]
|
||||||
* }
|
* }
|
||||||
* })
|
* })
|
||||||
*
|
*
|
||||||
* @summary
|
* @summary
|
||||||
*
|
*
|
||||||
* Add outbound or new items to a claim.
|
* Add outbound or new items to a claim.
|
||||||
*/
|
*/
|
||||||
export const orderClaimAddNewItemWorkflow = createWorkflow(
|
export const orderClaimAddNewItemWorkflow = createWorkflow(
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ function extractShippingOption({ orderPreview, orderClaim, returnId }) {
|
|||||||
|
|
||||||
for (const action of modifiedShippingMethod_.actions) {
|
for (const action of modifiedShippingMethod_.actions) {
|
||||||
if (action.action === ChangeActionType.SHIPPING_ADD) {
|
if (action.action === ChangeActionType.SHIPPING_ADD) {
|
||||||
if (action.return?.id === returnId) {
|
if (!!action.return_id && action.return_id === returnId) {
|
||||||
returnShippingMethod = shippingMethod
|
returnShippingMethod = shippingMethod
|
||||||
} else if (action.claim_id === orderClaim.id) {
|
} else if (action.claim_id === orderClaim.id) {
|
||||||
claimShippingMethod = shippingMethod
|
claimShippingMethod = shippingMethod
|
||||||
|
|||||||
@@ -241,10 +241,14 @@ function prepareInventoryUpdate({
|
|||||||
}[] = []
|
}[] = []
|
||||||
|
|
||||||
const allItems = itemsList ?? order.items
|
const allItems = itemsList ?? order.items
|
||||||
for (const item of allItems) {
|
const itemsToFulfill = allItems.filter((i) => i.id in inputItemsMap)
|
||||||
|
|
||||||
|
// iterate over items that are being fulfilled
|
||||||
|
for (const item of itemsToFulfill) {
|
||||||
const reservation = reservationMap[item.id]
|
const reservation = reservationMap[item.id]
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
if (item.manage_inventory) {
|
if (item.variant?.manage_inventory) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`No stock reservation found for item ${item.id} - ${item.title} (${item.variant_title})`
|
`No stock reservation found for item ${item.id} - ${item.title} (${item.variant_title})`
|
||||||
)
|
)
|
||||||
@@ -345,6 +349,7 @@ export const createOrderFulfillmentWorkflow = createWorkflow(
|
|||||||
"items.variant.height",
|
"items.variant.height",
|
||||||
"items.variant.width",
|
"items.variant.width",
|
||||||
"items.variant.material",
|
"items.variant.material",
|
||||||
|
"items.variant_title",
|
||||||
"shipping_address.*",
|
"shipping_address.*",
|
||||||
"shipping_methods.id",
|
"shipping_methods.id",
|
||||||
"shipping_methods.shipping_option_id",
|
"shipping_methods.shipping_option_id",
|
||||||
@@ -394,9 +399,11 @@ export const createOrderFulfillmentWorkflow = createWorkflow(
|
|||||||
}).config({ name: "get-shipping-option" })
|
}).config({ name: "get-shipping-option" })
|
||||||
|
|
||||||
const lineItemIds = transform(
|
const lineItemIds = transform(
|
||||||
{ order, itemsList: input.items_list },
|
{ order, itemsList: input.items_list, inputItemsMap },
|
||||||
({ order, itemsList }) => {
|
({ order, itemsList, inputItemsMap }) => {
|
||||||
return (itemsList ?? order.items)!.map((i) => i.id)
|
return (itemsList ?? order.items)!
|
||||||
|
.map((i) => i.id)
|
||||||
|
.filter((i) => i in inputItemsMap)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const reservations = useRemoteQueryStep({
|
const reservations = useRemoteQueryStep({
|
||||||
|
|||||||
Reference in New Issue
Block a user