Fix/adjust reservations correctly (#3474)

**What**
- Adjust reservations correctly according to the following heuristic: 

adjustment by addition:  (i.e. positive quantity adjustment passed to the adjustment method)
- if a reservation for the line-item in the location exists add quantity to that
- if not create a new reservation

adjustment by subtraction: 
- if a reservation with the exact quantity exists, delete it and return
- if a reservation with a greater quantity exists, subtract from it and return 
- otherwise delete from reservations until a reservation with greater quantity than the remaining is found and adjust that with the remaining quantity OR there are no more reservations

Fixes CORE-1247
This commit is contained in:
Philip Korsholm
2023-03-16 10:47:54 +01:00
committed by GitHub
parent 38c8d49f46
commit 02c77d7059
9 changed files with 392 additions and 102 deletions

View File

@@ -278,7 +278,7 @@ describe("Inventory Items endpoints", () => {
})
})
it.skip("Creates an inventory item using the api", async () => {
it("Creates an inventory item using the api", async () => {
const product = await simpleProductFactory(dbConnection, {})
const api = useApi()
@@ -316,20 +316,14 @@ describe("Inventory Items endpoints", () => {
)
expect(variantInventoryRes.data).toEqual({
variant: {
variant: expect.objectContaining({
id: variantId,
inventory: [
expect.objectContaining({
...inventoryItemCreateRes.data.inventory_item,
}),
],
sales_channel_availability: [
expect.objectContaining({
available_quantity: 0,
channel_name: "Default Sales Channel",
}),
],
},
}),
})
expect(variantInventoryRes.status).toEqual(200)
})

View File

@@ -295,7 +295,7 @@ describe("/store/carts", () => {
)
})
it("Adjusts reservations on successful fulfillment with reservation", async () => {
it("Adjusts reservation on successful fulfillment with reservation", async () => {
const api = useApi()
await prodVarInventoryService.reserveQuantity(variantId, 1, {
@@ -350,6 +350,273 @@ describe("/store/carts", () => {
)
})
it("Deletes multiple reservations on successful fulfillment with reservation", async () => {
const api = useApi()
const a = await inventoryService.updateInventoryLevel(
invItemId,
locationId,
{ stocked_quantity: 2 }
)
await prodVarInventoryService.reserveQuantity(variantId, 1, {
locationId: locationId,
lineItemId: order.items[0].id,
})
await prodVarInventoryService.reserveQuantity(variantId, 1, {
locationId: locationId,
lineItemId: order.items[0].id,
})
let inventoryItem = await api.get(
`/admin/inventory-items/${invItemId}`,
adminHeaders
)
expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual(
expect.objectContaining({
stocked_quantity: 2,
reserved_quantity: 2,
available_quantity: 0,
})
)
const fulfillmentRes = await api.post(
`/admin/orders/${order.id}/fulfillment`,
{
items: [{ item_id: lineItemId, quantity: 2 }],
location_id: locationId,
},
adminHeaders
)
expect(fulfillmentRes.status).toBe(200)
expect(fulfillmentRes.data.order.fulfillment_status).toBe("fulfilled")
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[0]).toEqual(
expect.objectContaining({
stocked_quantity: 0,
reserved_quantity: 0,
available_quantity: 0,
})
)
})
it("Deletes single reservation on successful fulfillment with partial reservation", async () => {
const api = useApi()
await inventoryService.updateInventoryLevel(invItemId, locationId, {
stocked_quantity: 2,
})
await prodVarInventoryService.reserveQuantity(variantId, 1, {
locationId: locationId,
lineItemId: order.items[0].id,
})
let inventoryItem = await api.get(
`/admin/inventory-items/${invItemId}`,
adminHeaders
)
expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual(
expect.objectContaining({
stocked_quantity: 2,
reserved_quantity: 1,
available_quantity: 1,
})
)
const fulfillmentRes = await api.post(
`/admin/orders/${order.id}/fulfillment`,
{
items: [{ item_id: lineItemId, quantity: 2 }],
location_id: locationId,
},
adminHeaders
)
expect(fulfillmentRes.status).toBe(200)
expect(fulfillmentRes.data.order.fulfillment_status).toBe("fulfilled")
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[0]).toEqual(
expect.objectContaining({
stocked_quantity: 0,
reserved_quantity: 0,
available_quantity: 0,
})
)
})
it("Adjusts single reservation on successful fulfillment with over-reserved line item", async () => {
const api = useApi()
const a = await inventoryService.updateInventoryLevel(
invItemId,
locationId,
{ stocked_quantity: 3 }
)
await prodVarInventoryService.reserveQuantity(variantId, 3, {
locationId: locationId,
lineItemId: order.items[0].id,
})
let inventoryItem = await api.get(
`/admin/inventory-items/${invItemId}`,
adminHeaders
)
expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual(
expect.objectContaining({
stocked_quantity: 3,
reserved_quantity: 3,
available_quantity: 0,
})
)
const fulfillmentRes = await api.post(
`/admin/orders/${order.id}/fulfillment`,
{
items: [{ item_id: lineItemId, quantity: 2 }],
location_id: locationId,
},
adminHeaders
)
expect(fulfillmentRes.status).toBe(200)
expect(fulfillmentRes.data.order.fulfillment_status).toBe("fulfilled")
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(1)
expect(reservations.data.reservations).toEqual([
expect.objectContaining({
quantity: 1,
}),
])
expect(inventoryItem.data.inventory_item.location_levels[0]).toEqual(
expect.objectContaining({
stocked_quantity: 1,
reserved_quantity: 1,
available_quantity: 0,
})
)
})
it("Prioritizes adjusting reservations at the chosen location", async () => {
const api = useApi()
const sl = await stockLocationService.create({
name: "test-location 1",
})
await inventoryService.createInventoryLevel({
inventory_item_id: invItemId,
location_id: sl.id,
stocked_quantity: 3,
})
const a = await inventoryService.updateInventoryLevel(
invItemId,
locationId,
{ stocked_quantity: 3 }
)
await prodVarInventoryService.reserveQuantity(variantId, 1, {
locationId: locationId,
lineItemId: order.items[0].id,
})
await prodVarInventoryService.reserveQuantity(variantId, 2, {
locationId: sl.id,
lineItemId: order.items[0].id,
})
let inventoryItem = await api.get(
`/admin/inventory-items/${invItemId}`,
adminHeaders
)
expect(inventoryItem.data.inventory_item.location_levels).toEqual(
expect.arrayContaining([
expect.objectContaining({
location_id: locationId,
stocked_quantity: 3,
reserved_quantity: 1,
available_quantity: 2,
}),
expect.objectContaining({
location_id: sl.id,
stocked_quantity: 3,
reserved_quantity: 2,
available_quantity: 1,
}),
])
)
const fulfillmentRes = await api.post(
`/admin/orders/${order.id}/fulfillment`,
{
items: [{ item_id: lineItemId, quantity: 2 }],
location_id: locationId,
},
adminHeaders
)
expect(fulfillmentRes.status).toBe(200)
expect(fulfillmentRes.data.order.fulfillment_status).toBe("fulfilled")
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(1)
expect(reservations.data.reservations).toEqual([
expect.objectContaining({
quantity: 1,
location_id: sl.id,
}),
])
})
it("increases stocked quantity when return is received at location", async () => {
const api = useApi()