Fix(medusa): Missing location id on fulfillments (#3462)
**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
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa): Include location_id in fulfillment body
|
||||||
@@ -6,11 +6,15 @@ const { setPort, useApi } = require("../../../../helpers/use-api")
|
|||||||
|
|
||||||
const adminSeeder = require("../../../helpers/admin-seeder")
|
const adminSeeder = require("../../../helpers/admin-seeder")
|
||||||
const cartSeeder = require("../../../helpers/cart-seeder")
|
const cartSeeder = require("../../../helpers/cart-seeder")
|
||||||
const { simpleProductFactory } = require("../../../../api/factories")
|
const {
|
||||||
|
simpleProductFactory,
|
||||||
|
simpleCustomerFactory,
|
||||||
|
} = require("../../../../api/factories")
|
||||||
const { simpleSalesChannelFactory } = require("../../../../api/factories")
|
const { simpleSalesChannelFactory } = require("../../../../api/factories")
|
||||||
const {
|
const {
|
||||||
simpleOrderFactory,
|
simpleOrderFactory,
|
||||||
simpleRegionFactory,
|
simpleRegionFactory,
|
||||||
|
simpleCartFactory,
|
||||||
} = require("../../../factories")
|
} = require("../../../factories")
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
@@ -57,6 +61,10 @@ describe("/store/carts", () => {
|
|||||||
let invItemId
|
let invItemId
|
||||||
let variantId
|
let variantId
|
||||||
let prodVarInventoryService
|
let prodVarInventoryService
|
||||||
|
let inventoryService
|
||||||
|
let stockLocationService
|
||||||
|
let salesChannelLocationService
|
||||||
|
let regionId
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
@@ -64,13 +72,14 @@ describe("/store/carts", () => {
|
|||||||
prodVarInventoryService = appContainer.resolve(
|
prodVarInventoryService = appContainer.resolve(
|
||||||
"productVariantInventoryService"
|
"productVariantInventoryService"
|
||||||
)
|
)
|
||||||
const inventoryService = appContainer.resolve("inventoryService")
|
inventoryService = appContainer.resolve("inventoryService")
|
||||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
stockLocationService = appContainer.resolve("stockLocationService")
|
||||||
const salesChannelLocationService = appContainer.resolve(
|
salesChannelLocationService = appContainer.resolve(
|
||||||
"salesChannelLocationService"
|
"salesChannelLocationService"
|
||||||
)
|
)
|
||||||
|
|
||||||
const r = await simpleRegionFactory(dbConnection, {})
|
const r = await simpleRegionFactory(dbConnection, {})
|
||||||
|
regionId = r.id
|
||||||
await simpleSalesChannelFactory(dbConnection, {
|
await simpleSalesChannelFactory(dbConnection, {
|
||||||
id: "test-channel",
|
id: "test-channel",
|
||||||
is_default: true,
|
is_default: true,
|
||||||
@@ -143,6 +152,149 @@ describe("/store/carts", () => {
|
|||||||
|
|
||||||
describe("Fulfillments", () => {
|
describe("Fulfillments", () => {
|
||||||
const lineItemId = "line-item-id"
|
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: "adrien@test.com",
|
||||||
|
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 () => {
|
it("Adjusts reservations on successful fulfillment with reservation", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type CartFactoryData = {
|
|||||||
line_items?: LineItemFactoryData[]
|
line_items?: LineItemFactoryData[]
|
||||||
shipping_address?: AddressFactoryData
|
shipping_address?: AddressFactoryData
|
||||||
shipping_methods?: ShippingMethodFactoryData[]
|
shipping_methods?: ShippingMethodFactoryData[]
|
||||||
|
sales_channel_id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const simpleCartFactory = async (
|
export const simpleCartFactory = async (
|
||||||
@@ -52,6 +53,7 @@ export const simpleCartFactory = async (
|
|||||||
typeof data.email !== "undefined" ? data.email : faker.internet.email(),
|
typeof data.email !== "undefined" ? data.email : faker.internet.email(),
|
||||||
region_id: regionId,
|
region_id: regionId,
|
||||||
shipping_address_id: address.id,
|
shipping_address_id: address.id,
|
||||||
|
sales_channel_id: data.sales_channel_id || null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const cart = await manager.save(toSave)
|
const cart = await manager.save(toSave)
|
||||||
|
|||||||
@@ -170,6 +170,10 @@ const CreateFulfillmentModal: React.FC<CreateFulfillmentModalProps> = ({
|
|||||||
no_notification: noNotis,
|
no_notification: noNotis,
|
||||||
} as AdminPostOrdersOrderFulfillmentsReq
|
} as AdminPostOrdersOrderFulfillmentsReq
|
||||||
|
|
||||||
|
if (isLocationFulfillmentEnabled) {
|
||||||
|
requestObj.location_id = locationSelectValue.value
|
||||||
|
}
|
||||||
|
|
||||||
requestObj.items = Object.entries(quantities)
|
requestObj.items = Object.entries(quantities)
|
||||||
.filter(([, value]) => !!value)
|
.filter(([, value]) => !!value)
|
||||||
.map(([key, value]) => ({
|
.map(([key, value]) => ({
|
||||||
|
|||||||
@@ -166,7 +166,6 @@ export default class ReservationItemService extends TransactionBaseService {
|
|||||||
|
|
||||||
const shouldUpdateLocation =
|
const shouldUpdateLocation =
|
||||||
isDefined(data.location_id) &&
|
isDefined(data.location_id) &&
|
||||||
isDefined(data.quantity) &&
|
|
||||||
data.location_id !== item.location_id
|
data.location_id !== item.location_id
|
||||||
|
|
||||||
const ops: Promise<unknown>[] = []
|
const ops: Promise<unknown>[] = []
|
||||||
@@ -185,7 +184,7 @@ export default class ReservationItemService extends TransactionBaseService {
|
|||||||
.adjustReservedQuantity(
|
.adjustReservedQuantity(
|
||||||
item.inventory_item_id,
|
item.inventory_item_id,
|
||||||
data.location_id!,
|
data.location_id!,
|
||||||
data.quantity!
|
data.quantity || item.quantity!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else if (shouldUpdateQuantity) {
|
} else if (shouldUpdateQuantity) {
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export default async (req, res) => {
|
|||||||
await orderServiceTx.createFulfillment(id, validatedBody.items, {
|
await orderServiceTx.createFulfillment(id, validatedBody.items, {
|
||||||
metadata: validatedBody.metadata,
|
metadata: validatedBody.metadata,
|
||||||
no_notification: validatedBody.no_notification,
|
no_notification: validatedBody.no_notification,
|
||||||
|
location_id: validatedBody.location_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (validatedBody.location_id) {
|
if (validatedBody.location_id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user