diff --git a/.changeset/dirty-lions-add.md b/.changeset/dirty-lions-add.md new file mode 100644 index 0000000000..d0c57bc6c4 --- /dev/null +++ b/.changeset/dirty-lions-add.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): Include location_id in fulfillment body diff --git a/integration-tests/plugins/__tests__/inventory/order/order.js b/integration-tests/plugins/__tests__/inventory/order/order.js index f640332cae..07f081e61e 100644 --- a/integration-tests/plugins/__tests__/inventory/order/order.js +++ b/integration-tests/plugins/__tests__/inventory/order/order.js @@ -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) @@ -57,6 +61,10 @@ describe("/store/carts", () => { let invItemId let variantId let prodVarInventoryService + let inventoryService + let stockLocationService + let salesChannelLocationService + let regionId beforeEach(async () => { const api = useApi() @@ -64,13 +72,14 @@ describe("/store/carts", () => { 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, @@ -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: "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 () => { const api = useApi() diff --git a/integration-tests/plugins/factories/simple-cart-factory.ts b/integration-tests/plugins/factories/simple-cart-factory.ts index 33a10cff75..664ef0d21e 100644 --- a/integration-tests/plugins/factories/simple-cart-factory.ts +++ b/integration-tests/plugins/factories/simple-cart-factory.ts @@ -23,6 +23,7 @@ export type CartFactoryData = { line_items?: LineItemFactoryData[] shipping_address?: AddressFactoryData shipping_methods?: ShippingMethodFactoryData[] + sales_channel_id?: string } export const simpleCartFactory = async ( @@ -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) diff --git a/packages/admin-ui/ui/src/domain/orders/details/create-fulfillment/index.tsx b/packages/admin-ui/ui/src/domain/orders/details/create-fulfillment/index.tsx index f69bb91670..8a5811967d 100644 --- a/packages/admin-ui/ui/src/domain/orders/details/create-fulfillment/index.tsx +++ b/packages/admin-ui/ui/src/domain/orders/details/create-fulfillment/index.tsx @@ -170,6 +170,10 @@ const CreateFulfillmentModal: React.FC = ({ no_notification: noNotis, } as AdminPostOrdersOrderFulfillmentsReq + if (isLocationFulfillmentEnabled) { + requestObj.location_id = locationSelectValue.value + } + requestObj.items = Object.entries(quantities) .filter(([, value]) => !!value) .map(([key, value]) => ({ diff --git a/packages/inventory/src/services/reservation-item.ts b/packages/inventory/src/services/reservation-item.ts index 244984745e..1796f2d243 100644 --- a/packages/inventory/src/services/reservation-item.ts +++ b/packages/inventory/src/services/reservation-item.ts @@ -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[] = [] @@ -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) { diff --git a/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts b/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts index 0546da1da0..8582419981 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts +++ b/packages/medusa/src/api/routes/admin/orders/create-fulfillment.ts @@ -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) {