fix: allow backorder variants to be added to cart even if no locations (#12083)
* fix: allow backorder variants to be added to cart even if no locations * document and unit test prepareConfirmInventoryInput
This commit is contained in:
1 parent
f615ebb7e8
commit
8804ca2f9c
6 files changed
+631
-14
No files matched your search
@@ -151,7 +151,7 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("POST /store/carts", () => {
|
describe("POST /store/carts", () => {
|
||||||
it("should succesffully create a cart", async () => {
|
it("should successfully create a cart", async () => {
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/store/carts`,
|
`/store/carts`,
|
||||||
{
|
{
|
||||||
@@ -255,10 +255,10 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("POST /store/carts/:id/line-items", () => {
|
describe("POST /store/carts/:id/line-items", () => {
|
||||||
let shippingOption, shippingOptionExpensive
|
let shippingOption, shippingOptionExpensive, stockLocation
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const stockLocation = (
|
stockLocation = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/admin/stock-locations`,
|
`/admin/stock-locations`,
|
||||||
{ name: "test location" },
|
{ name: "test location" },
|
||||||
@@ -894,6 +894,67 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("with manage_inventory true", () => {
|
||||||
|
let inventoryItem
|
||||||
|
beforeEach(async () => {
|
||||||
|
await api.post(
|
||||||
|
`/admin/products/${product.id}/variants/${product.variants[0].id}`,
|
||||||
|
{ manage_inventory: true },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
inventoryItem = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items`,
|
||||||
|
{ sku: "bottle" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.inventory_item
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("with allow_backorder true", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await api.post(
|
||||||
|
`/admin/products/${product.id}/variants/${product.variants[0].id}`,
|
||||||
|
{ allow_backorder: true },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add item to cart even if no inventory locations", async () => {
|
||||||
|
let response = await api.post(
|
||||||
|
`/store/carts/${cart.id}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add item to cart even if inventory is empty", async () => {
|
||||||
|
await api.post(
|
||||||
|
`/admin/inventory-items/${inventoryItem.id}/location-levels/batch`,
|
||||||
|
{ create: [{ location_id: stockLocation.id }] },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
let response = await api.post(
|
||||||
|
`/store/carts/${cart.id}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("POST /store/carts/:id/line-items/:id", () => {
|
describe("POST /store/carts/:id/line-items/:id", () => {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ async function populateData(api: any) {
|
|||||||
console.log(err)
|
console.log(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
await setTimeout(2000)
|
await setTimeout(10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env.ENABLE_INDEX_MODULE = "true"
|
process.env.ENABLE_INDEX_MODULE = "true"
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Timeout to allow indexing to finish
|
// Timeout to allow indexing to finish
|
||||||
await setTimeout(4000)
|
await setTimeout(10000)
|
||||||
|
|
||||||
const { data: results } = await fetchAndRetry(
|
const { data: results } = await fetchAndRetry(
|
||||||
async () =>
|
async () =>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
} from "../../../helpers/create-admin-user"
|
} from "../../../helpers/create-admin-user"
|
||||||
import { setupTaxStructure } from "../fixtures"
|
import { setupTaxStructure } from "../fixtures"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(100000)
|
||||||
|
|
||||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
|
||||||
@@ -165,6 +165,14 @@ medusaIntegrationTestRunner({
|
|||||||
inventory_item_id: inventoryItem.id,
|
inventory_item_id: inventoryItem.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
[Modules.PRODUCT]: {
|
||||||
|
variant_id: product_2.variants[0].id,
|
||||||
|
},
|
||||||
|
[Modules.INVENTORY]: {
|
||||||
|
inventory_item_id: inventoryItem.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
await setupTaxStructure(taxModule)
|
await setupTaxStructure(taxModule)
|
||||||
|
|||||||
+533
@@ -0,0 +1,533 @@
|
|||||||
|
import { ConfirmVariantInventoryWorkflowInputDTO } from "@medusajs/framework/types"
|
||||||
|
import { MedusaError } from "@medusajs/framework/utils"
|
||||||
|
import { prepareConfirmInventoryInput } from "../prepare-confirm-inventory-input"
|
||||||
|
|
||||||
|
describe("prepareConfirmInventoryInput", () => {
|
||||||
|
it("should use the quantity from the itemsToUpdate", () => {
|
||||||
|
const input: ConfirmVariantInventoryWorkflowInputDTO = {
|
||||||
|
sales_channel_id: "sc_1",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "pv_1",
|
||||||
|
manage_inventory: true,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
variant_id: "pv_1",
|
||||||
|
required_quantity: 3,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_1",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: "pv_1",
|
||||||
|
quantity: 1,
|
||||||
|
id: "item_1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
itemsToUpdate: [
|
||||||
|
{
|
||||||
|
variant_id: "pv_1",
|
||||||
|
quantity: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = prepareConfirmInventoryInput({ input })
|
||||||
|
|
||||||
|
expect(output).toEqual({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_1",
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
required_quantity: 3,
|
||||||
|
quantity: 2, // overrides the quantity from the items array
|
||||||
|
allow_backorder: false,
|
||||||
|
location_ids: ["sl_1"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should only return variants with manage_inventory set to true", () => {
|
||||||
|
const input: ConfirmVariantInventoryWorkflowInputDTO = {
|
||||||
|
sales_channel_id: "sc_1",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "pv_1",
|
||||||
|
manage_inventory: true,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
variant_id: "pv_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_1",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pv_2",
|
||||||
|
manage_inventory: false,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_2",
|
||||||
|
variant_id: "pv_2",
|
||||||
|
required_quantity: 1,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_1",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: "pv_1",
|
||||||
|
quantity: 1,
|
||||||
|
id: "item_1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant_id: "pv_2",
|
||||||
|
quantity: 1,
|
||||||
|
id: "item_2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = prepareConfirmInventoryInput({ input })
|
||||||
|
|
||||||
|
expect(output).toEqual({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_1",
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
quantity: 1,
|
||||||
|
allow_backorder: false,
|
||||||
|
location_ids: ["sl_1"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return all inventory items for a variant", () => {
|
||||||
|
const input: ConfirmVariantInventoryWorkflowInputDTO = {
|
||||||
|
sales_channel_id: "sc_1",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "pv_1",
|
||||||
|
manage_inventory: true,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
variant_id: "pv_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_1",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_2",
|
||||||
|
variant_id: "pv_1",
|
||||||
|
required_quantity: 2,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_1",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: "pv_1",
|
||||||
|
quantity: 1,
|
||||||
|
id: "item_1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = prepareConfirmInventoryInput({ input })
|
||||||
|
|
||||||
|
expect(output).toEqual({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_1",
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
quantity: 1,
|
||||||
|
allow_backorder: false,
|
||||||
|
location_ids: ["sl_1"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "item_1",
|
||||||
|
inventory_item_id: "ii_2",
|
||||||
|
required_quantity: 2,
|
||||||
|
quantity: 1,
|
||||||
|
allow_backorder: false,
|
||||||
|
location_ids: ["sl_1"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error if any variant has no stock locations linked to the sales channel", () => {
|
||||||
|
const input = {
|
||||||
|
sales_channel_id: "sc_1",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "pv_1",
|
||||||
|
manage_inventory: true,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
variant_id: "pv_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_1",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pv_2",
|
||||||
|
manage_inventory: true,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_2",
|
||||||
|
variant_id: "pv_2",
|
||||||
|
required_quantity: 1,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_2",
|
||||||
|
sales_channels: [{ id: "sc_2" }], // Different sales channel
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: "pv_1",
|
||||||
|
quantity: 1,
|
||||||
|
id: "item_1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant_id: "pv_2",
|
||||||
|
quantity: 1,
|
||||||
|
id: "item_2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => prepareConfirmInventoryInput({ input })).toThrow(MedusaError)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("if allow_backorder is true, it should return normally even if there's no stock location for the sales channel", () => {
|
||||||
|
const input = {
|
||||||
|
sales_channel_id: "sc_1",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "pv_1",
|
||||||
|
manage_inventory: true,
|
||||||
|
allow_backorder: true,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
variant_id: "pv_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_2",
|
||||||
|
sales_channels: [{ id: "sc_2" }], // Different sales channel
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: "pv_1",
|
||||||
|
quantity: 1,
|
||||||
|
id: "item_1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = prepareConfirmInventoryInput({ input })
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_1",
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
quantity: 1,
|
||||||
|
allow_backorder: true,
|
||||||
|
location_ids: [], // TODO: what should this be?
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return only stock locations with availability, if any", () => {
|
||||||
|
const input = {
|
||||||
|
sales_channel_id: "sc_1",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "pv_1",
|
||||||
|
manage_inventory: true,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
variant_id: "pv_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stocked_quantity: 10, // 10 - 9 = 1 < 2: no availability
|
||||||
|
reserved_quantity: 9,
|
||||||
|
location_id: "sl_1",
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_1",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stocked_quantity: 7, // 7 - 5 = 2 >= 2: availability
|
||||||
|
reserved_quantity: 5,
|
||||||
|
location_id: "sl_2",
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_2",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: "pv_1",
|
||||||
|
quantity: 2,
|
||||||
|
id: "item_1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = prepareConfirmInventoryInput({ input })
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_1",
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
quantity: 2,
|
||||||
|
allow_backorder: false,
|
||||||
|
location_ids: ["sl_2"], // Only includes location with available stock
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return all locations if none has availability", () => {
|
||||||
|
const input = {
|
||||||
|
sales_channel_id: "sc_1",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "pv_1",
|
||||||
|
manage_inventory: true,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
variant_id: "pv_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stocked_quantity: 1,
|
||||||
|
reserved_quantity: 1, // 1 - 1 = 0 < 2: no availability
|
||||||
|
location_id: "sl_1",
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_1",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stocked_quantity: 4,
|
||||||
|
reserved_quantity: 3, // 4 - 3 = 1 < 2: no availability
|
||||||
|
location_id: "sl_2",
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_2",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: "pv_1",
|
||||||
|
quantity: 2,
|
||||||
|
id: "item_1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = prepareConfirmInventoryInput({ input })
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "item_1",
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
required_quantity: 1,
|
||||||
|
quantity: 2,
|
||||||
|
allow_backorder: false,
|
||||||
|
location_ids: ["sl_1", "sl_2"], // Includes all locations since none has availability
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error if any variant has no inventory items", () => {
|
||||||
|
const input = {
|
||||||
|
sales_channel_id: "sc_1",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "pv_1",
|
||||||
|
manage_inventory: true,
|
||||||
|
inventory_items: [
|
||||||
|
{
|
||||||
|
inventory_item_id: "ii_1",
|
||||||
|
variant_id: "pv_1",
|
||||||
|
required_quantity: 3,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
location_levels: {
|
||||||
|
stock_locations: [
|
||||||
|
{
|
||||||
|
id: "sl_1",
|
||||||
|
sales_channels: [{ id: "sc_1" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pv_2",
|
||||||
|
manage_inventory: true,
|
||||||
|
inventory_items: [], // No inventory items
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: "pv_1",
|
||||||
|
quantity: 1,
|
||||||
|
id: "item_1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant_id: "pv_2",
|
||||||
|
quantity: 1,
|
||||||
|
id: "item_2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => prepareConfirmInventoryInput({ input })).toThrow(MedusaError)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -38,6 +38,17 @@ interface ConfirmInventoryItem {
|
|||||||
location_ids: string[]
|
location_ids: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function prepares the input for the confirm inventory workflow.
|
||||||
|
* In essesnce, it maps a list of cart items to a list of inventory items,
|
||||||
|
* serving as a bridge between the cart and inventory domains.
|
||||||
|
*
|
||||||
|
* @throws {MedusaError} INVALID_DATA if any cart item is for a variant that has no inventory items.
|
||||||
|
* @throws {MedusaError} INVALID_DATA if any cart item is for a variant with no stock locations in the input.sales_channel_id. An exception is made for variants with allow_backorder set to true.
|
||||||
|
*
|
||||||
|
* @returns {ConfirmInventoryPreparationInput}
|
||||||
|
* A list of inventory items to confirm. Only inventory items for variants with managed inventory are included.
|
||||||
|
*/
|
||||||
export const prepareConfirmInventoryInput = (data: {
|
export const prepareConfirmInventoryInput = (data: {
|
||||||
input: ConfirmVariantInventoryWorkflowInputDTO
|
input: ConfirmVariantInventoryWorkflowInputDTO
|
||||||
}) => {
|
}) => {
|
||||||
@@ -45,7 +56,7 @@ export const prepareConfirmInventoryInput = (data: {
|
|||||||
const stockLocationIds = new Set<string>()
|
const stockLocationIds = new Set<string>()
|
||||||
const allVariants = new Map<string, any>()
|
const allVariants = new Map<string, any>()
|
||||||
const mapLocationAvailability = new Map<string, Map<string, BigNumberInput>>()
|
const mapLocationAvailability = new Map<string, Map<string, BigNumberInput>>()
|
||||||
let hasSalesChannelStockLocation = false
|
const variantsWithLocationForChannel = new Set<string>()
|
||||||
let hasManagedInventory = false
|
let hasManagedInventory = false
|
||||||
|
|
||||||
const salesChannelId = data.input.sales_channel_id
|
const salesChannelId = data.input.sales_channel_id
|
||||||
@@ -75,11 +86,8 @@ export const prepareConfirmInventoryInput = (data: {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (salesChannelId && sales_channels?.id === salesChannelId) {
|
||||||
!hasSalesChannelStockLocation &&
|
variantsWithLocationForChannel.add(variants.id)
|
||||||
sales_channels?.id === salesChannelId
|
|
||||||
) {
|
|
||||||
hasSalesChannelStockLocation = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location_levels && inventory_items) {
|
if (location_levels && inventory_items) {
|
||||||
@@ -140,12 +148,19 @@ export const prepareConfirmInventoryInput = (data: {
|
|||||||
return { items: [] }
|
return { items: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (salesChannelId && !hasSalesChannelStockLocation) {
|
if (salesChannelId) {
|
||||||
|
for (const variant of allVariants.values()) {
|
||||||
|
if (
|
||||||
|
!variantsWithLocationForChannel.has(variant.id) &&
|
||||||
|
!variant.allow_backorder
|
||||||
|
) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.INVALID_DATA,
|
||||||
`Sales channel ${salesChannelId} is not associated with any stock location.`
|
`Sales channel ${salesChannelId} is not associated with any stock location for variant ${variant.id}.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const items = formatInventoryInput({
|
const items = formatInventoryInput({
|
||||||
product_variant_inventory_items: Array.from(
|
product_variant_inventory_items: Array.from(
|
||||||
|
|||||||
Reference in new issue
Block a user