Skip to content

Commit

Permalink
Fix(medusa): Missing location id on fulfillments (#3462)
Browse files Browse the repository at this point in the history
**What**
- include location id when creating a fulfillment
- Allow location updates to reservations without passing along quantity

**Why**
- location_id on fulfillment was null after creation

Fixes CORE-1242, CORE-1243
  • Loading branch information
pKorsholm authored Mar 14, 2023
1 parent 24604f1 commit 30a3203
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-lions-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---

fix(medusa): Include location_id in fulfillment body
160 changes: 156 additions & 4 deletions integration-tests/plugins/__tests__/inventory/order/order.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ const { setPort, useApi } = require("../../../../helpers/use-api")

const adminSeeder = require("../../../helpers/admin-seeder")
const cartSeeder = require("../../../helpers/cart-seeder")
const { simpleProductFactory } = require("../../../../api/factories")
const {
simpleProductFactory,
simpleCustomerFactory,
} = require("../../../../api/factories")
const { simpleSalesChannelFactory } = require("../../../../api/factories")
const {
simpleOrderFactory,
simpleRegionFactory,
simpleCartFactory,
} = require("../../../factories")

jest.setTimeout(30000)
Expand Down Expand Up @@ -57,20 +61,25 @@ describe("/store/carts", () => {
let invItemId
let variantId
let prodVarInventoryService
let inventoryService
let stockLocationService
let salesChannelLocationService
let regionId

beforeEach(async () => {
const api = useApi()

prodVarInventoryService = appContainer.resolve(
"productVariantInventoryService"
)
const inventoryService = appContainer.resolve("inventoryService")
const stockLocationService = appContainer.resolve("stockLocationService")
const salesChannelLocationService = appContainer.resolve(
inventoryService = appContainer.resolve("inventoryService")
stockLocationService = appContainer.resolve("stockLocationService")
salesChannelLocationService = appContainer.resolve(
"salesChannelLocationService"
)

const r = await simpleRegionFactory(dbConnection, {})
regionId = r.id
await simpleSalesChannelFactory(dbConnection, {
id: "test-channel",
is_default: true,
Expand Down Expand Up @@ -143,6 +152,149 @@ describe("/store/carts", () => {

describe("Fulfillments", () => {
const lineItemId = "line-item-id"

it("Adjusts reservations on successful fulfillment on updated reservation item", async () => {
const api = useApi()

const lineItemId1 = "line-item-id-1"

// create new location for the inventoryItem with a quantity
const loc = await stockLocationService.create({ name: "test-location" })

const customer = await simpleCustomerFactory(dbConnection, {})

const cart = await simpleCartFactory(dbConnection, {
email: "[email protected]",
region: regionId,
line_items: [
{
id: lineItemId1,
variant_id: variantId,
quantity: 1,
unit_price: 1000,
},
],
sales_channel_id: "test-channel",
shipping_address: {},
shipping_methods: [
{
shipping_option: {
region_id: regionId,
},
},
],
})

await appContainer
.resolve("cartService")
.update(cart.id, { customer_id: customer.id })

let inventoryItem = await api.get(
`/admin/inventory-items/${invItemId}`,
adminHeaders
)

expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual(
expect.objectContaining({
stocked_quantity: 1,
reserved_quantity: 0,
available_quantity: 1,
})
)

await api.post(`/store/carts/${cart.id}/payment-sessions`)

const completeRes = await api.post(`/store/carts/${cart.id}/complete`)

const orderId = completeRes.data.data.id
await salesChannelLocationService.associateLocation(
"test-channel",
locationId
)

await inventoryService.createInventoryLevel({
inventory_item_id: invItemId,
location_id: loc.id,
stocked_quantity: 1,
})

inventoryItem = await api.get(
`/admin/inventory-items/${invItemId}`,
adminHeaders
)

expect(inventoryItem.data.inventory_item.location_levels).toEqual(
expect.arrayContaining([
expect.objectContaining({
stocked_quantity: 1,
reserved_quantity: 1,
available_quantity: 0,
}),
expect.objectContaining({
stocked_quantity: 1,
reserved_quantity: 0,
available_quantity: 1,
}),
])
)

// update reservation item
const reservationItems = await api.get(
`/admin/reservations?line_item_id[]=${lineItemId1}`,
adminHeaders
)

const reservationItem = reservationItems.data.reservations[0]

const res = await api.post(
`/admin/reservations/${reservationItem.id}`,
{ location_id: loc.id },
adminHeaders
)

const fulfillmentRes = await api.post(
`/admin/orders/${orderId}/fulfillment`,
{
items: [{ item_id: lineItemId1, quantity: 1 }],
location_id: locationId,
},
adminHeaders
)

expect(fulfillmentRes.status).toBe(200)
expect(
fulfillmentRes.data.order.fulfillments[0].location_id
).toBeTruthy()

inventoryItem = await api.get(
`/admin/inventory-items/${invItemId}`,
adminHeaders
)

const reservations = await api.get(
`/admin/reservations?inventory_item_id[]=${invItemId}`,
adminHeaders
)

expect(reservations.data.reservations.length).toBe(0)
expect(inventoryItem.data.inventory_item.location_levels).toEqual(
expect.arrayContaining([
expect.objectContaining({
location_id: locationId,
stocked_quantity: 0,
reserved_quantity: 0,
available_quantity: 0,
}),
expect.objectContaining({
location_id: loc.id,
stocked_quantity: 1,
reserved_quantity: 0,
available_quantity: 1,
}),
])
)
})

it("Adjusts reservations on successful fulfillment with reservation", async () => {
const api = useApi()

Expand Down
2 changes: 2 additions & 0 deletions integration-tests/plugins/factories/simple-cart-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type CartFactoryData = {
line_items?: LineItemFactoryData[]
shipping_address?: AddressFactoryData
shipping_methods?: ShippingMethodFactoryData[]
sales_channel_id?: string
}

export const simpleCartFactory = async (
Expand Down Expand Up @@ -52,6 +53,7 @@ export const simpleCartFactory = async (
typeof data.email !== "undefined" ? data.email : faker.internet.email(),
region_id: regionId,
shipping_address_id: address.id,
sales_channel_id: data.sales_channel_id || null,
})

const cart = await manager.save(toSave)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ const CreateFulfillmentModal: React.FC<CreateFulfillmentModalProps> = ({
no_notification: noNotis,
} as AdminPostOrdersOrderFulfillmentsReq

if (isLocationFulfillmentEnabled) {
requestObj.location_id = locationSelectValue.value
}

requestObj.items = Object.entries(quantities)
.filter(([, value]) => !!value)
.map(([key, value]) => ({
Expand Down
3 changes: 1 addition & 2 deletions packages/inventory/src/services/reservation-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ export default class ReservationItemService extends TransactionBaseService {

const shouldUpdateLocation =
isDefined(data.location_id) &&
isDefined(data.quantity) &&
data.location_id !== item.location_id

const ops: Promise<unknown>[] = []
Expand All @@ -185,7 +184,7 @@ export default class ReservationItemService extends TransactionBaseService {
.adjustReservedQuantity(
item.inventory_item_id,
data.location_id!,
data.quantity!
data.quantity || item.quantity!
)
)
} else if (shouldUpdateQuantity) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export default async (req, res) => {
await orderServiceTx.createFulfillment(id, validatedBody.items, {
metadata: validatedBody.metadata,
no_notification: validatedBody.no_notification,
location_id: validatedBody.location_id,
})

if (validatedBody.location_id) {
Expand Down

0 comments on commit 30a3203

Please sign in to comment.