feat: Confirm inventory in create cart workflow (#6635)

This commit is contained in:
Oli Juhl
2024-03-13 18:24:08 +01:00
committed by GitHub
parent b78f863d80
commit 02e784ce78
11 changed files with 652 additions and 102 deletions

View File

@@ -18,6 +18,7 @@ import {
ICartModuleService,
ICustomerModuleService,
IFulfillmentModuleService,
IInventoryServiceNext,
IPaymentModuleService,
IPricingModuleService,
IProductModuleService,
@@ -45,6 +46,8 @@ medusaIntegrationTestRunner({
let productModule: IProductModuleService
let pricingModule: IPricingModuleService
let paymentModule: IPaymentModuleService
let inventoryModule: IInventoryServiceNext
let stockLocationModule: IStockLocationServiceNext
let fulfillmentModule: IFulfillmentModuleService
let locationModule: IStockLocationServiceNext
let remoteLink, remoteQuery
@@ -64,6 +67,10 @@ medusaIntegrationTestRunner({
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
paymentModule = appContainer.resolve(ModuleRegistrationName.PAYMENT)
inventoryModule = appContainer.resolve(ModuleRegistrationName.INVENTORY)
stockLocationModule = appContainer.resolve(
ModuleRegistrationName.STOCK_LOCATION
)
fulfillmentModule = appContainer.resolve(
ModuleRegistrationName.FULFILLMENT
)
@@ -97,6 +104,10 @@ medusaIntegrationTestRunner({
name: "Webshop",
})
const location = await stockLocationModule.create({
name: "Warehouse",
})
const [product] = await productModule.create([
{
title: "Test product",
@@ -108,6 +119,19 @@ medusaIntegrationTestRunner({
},
])
const inventoryItem = await inventoryModule.create({
sku: "inv-1234",
})
await inventoryModule.createInventoryLevels([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
stocked_quantity: 2,
reserved_quantity: 0,
},
])
const priceSet = await pricingModule.create({
prices: [
{
@@ -119,13 +143,29 @@ medusaIntegrationTestRunner({
await remoteLink.create([
{
productService: {
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
pricingService: {
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItem.id,
},
},
])
const { result } = await createCartWorkflow(appContainer).run({
@@ -184,6 +224,99 @@ medusaIntegrationTestRunner({
])
})
it("should throw if variants are out of stock", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await stockLocationModule.create({
name: "Warehouse",
})
const [product] = await productModule.create([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
],
},
])
const inventoryItem = await inventoryModule.create({
sku: "inv-1234",
})
await inventoryModule.createInventoryLevels([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
stocked_quantity: 2,
reserved_quantity: 2,
},
])
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItem.id,
},
},
])
const { errors } = await createCartWorkflow(appContainer).run({
input: {
sales_channel_id: salesChannel.id,
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
],
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "confirm-inventory-step",
handlerType: "invoke",
error: new Error(
"Some variant does not have the required inventory"
),
},
])
})
it("should throw if sales channel is disabled", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
@@ -283,8 +416,17 @@ medusaIntegrationTestRunner({
describe("AddToCartWorkflow", () => {
it("should add item to cart", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await stockLocationModule.create({
name: "Warehouse",
})
let cart = await cartModuleService.create({
currency_code: "usd",
sales_channel_id: salesChannel.id,
})
const [product] = await productModule.create([
@@ -298,6 +440,19 @@ medusaIntegrationTestRunner({
},
])
const inventoryItem = await inventoryModule.create({
sku: "inv-1234",
})
await inventoryModule.createInventoryLevels([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
stocked_quantity: 2,
reserved_quantity: 0,
},
])
const priceSet = await pricingModule.create({
prices: [
{
@@ -309,13 +464,29 @@ medusaIntegrationTestRunner({
await remoteLink.create([
{
productService: {
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
pricingService: {
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItem.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
@@ -354,8 +525,17 @@ medusaIntegrationTestRunner({
})
it("should throw if no price sets for variant exist", async () => {
const cart = await cartModuleService.create({
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await stockLocationModule.create({
name: "Warehouse",
})
let cart = await cartModuleService.create({
currency_code: "usd",
sales_channel_id: salesChannel.id,
})
const [product] = await productModule.create([
@@ -369,6 +549,38 @@ medusaIntegrationTestRunner({
},
])
const inventoryItem = await inventoryModule.create({
sku: "inv-1234",
})
await inventoryModule.createInventoryLevels([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
stocked_quantity: 2,
reserved_quantity: 0,
},
])
await remoteLink.create([
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItem.id,
},
},
])
const { errors } = await addToCartWorkflow(appContainer).run({
input: {
items: [
@@ -423,6 +635,14 @@ medusaIntegrationTestRunner({
describe("updateLineItemInCartWorkflow", () => {
it("should update item in cart", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await stockLocationModule.create({
name: "Warehouse",
})
const [product] = await productModule.create([
{
title: "Test product",
@@ -434,6 +654,19 @@ medusaIntegrationTestRunner({
},
])
const inventoryItem = await inventoryModule.create({
sku: "inv-1234",
})
await inventoryModule.createInventoryLevels([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
stocked_quantity: 2,
reserved_quantity: 0,
},
])
const priceSet = await pricingModule.create({
prices: [
{
@@ -445,17 +678,34 @@ medusaIntegrationTestRunner({
await remoteLink.create([
{
productService: {
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
pricingService: {
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItem.id,
},
},
])
let cart = await cartModuleService.create({
currency_code: "usd",
sales_channel_id: salesChannel.id,
items: [
{
variant_id: product.variants[0].id,
@@ -473,7 +723,9 @@ medusaIntegrationTestRunner({
const item = cart.items?.[0]!
await updateLineItemInCartWorkflow(appContainer).run({
const { errors } = await updateLineItemInCartWorkflow(
appContainer
).run({
input: {
cart,
item,
@@ -510,6 +762,14 @@ medusaIntegrationTestRunner({
},
})
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await stockLocationModule.create({
name: "Warehouse",
})
const [product] = await productModule.create([
{
title: "Test product",
@@ -521,6 +781,55 @@ medusaIntegrationTestRunner({
},
])
const inventoryItem = await inventoryModule.create({
sku: "inv-1234",
})
await inventoryModule.createInventoryLevels([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
stocked_quantity: 2,
reserved_quantity: 0,
},
])
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.INVENTORY]: {
inventory_item_id: inventoryItem.id,
},
},
])
let cart = await cartModuleService.create({
currency_code: "usd",
items: [
@@ -533,26 +842,6 @@ medusaIntegrationTestRunner({
],
})
const priceSet = await pricingModule.create({
prices: [
{
amount: 5000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
productService: {
variant_id: product.variants[0].id,
},
pricingService: {
price_set_id: priceSet.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
select: ["id", "region_id", "currency_code"],
relations: ["items", "items.variant_id", "items.metadata"],

View File

@@ -80,9 +80,11 @@ medusaIntegrationTestRunner({
title: "Test product",
variants: [
{
manage_inventory: false,
title: "Test variant",
},
{
manage_inventory: false,
title: "Test variant 2",
},
],
@@ -178,10 +180,16 @@ medusaIntegrationTestRunner({
const [product] = await productModule.create([
{
title: "Test product default tax",
variants: [{ title: "Test variant default tax" }],
variants: [
{ title: "Test variant default tax", manage_inventory: false },
],
},
])
const salesChannel = await scModule.create({
name: "Webshop",
})
const [priceSet] = await pricingModule.create([
{ prices: [{ amount: 3000, currency_code: "usd" }] },
])
@@ -204,6 +212,7 @@ medusaIntegrationTestRunner({
province: "NY",
postal_code: "94016",
},
sales_channel_id: salesChannel.id,
items: [
{
quantity: 1,
@@ -789,25 +798,32 @@ medusaIntegrationTestRunner({
email: "tony@stark-industries.com",
})
const salesChannel = await scModule.create({
name: "Webshop",
})
const [productWithSpecialTax] = await productModule.create([
{
// This product ID is setup in the tax structure fixture (setupTaxStructure)
id: "product_id_1",
title: "Test product",
variants: [{ title: "Test variant" }],
variants: [{ title: "Test variant", manage_inventory: false }],
} as any,
])
const [productWithDefaultTax] = await productModule.create([
{
title: "Test product default tax",
variants: [{ title: "Test variant default tax" }],
variants: [
{ title: "Test variant default tax", manage_inventory: false },
],
},
])
const cart = await cartModule.create({
currency_code: "usd",
customer_id: customer.id,
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: {
customer_id: customer.id,

View File

@@ -0,0 +1,47 @@
import { IInventoryService } from "@medusajs/types"
import { promiseAll } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { MedusaError } from "medusa-core-utils"
import { ModuleRegistrationName } from "../../../../../modules-sdk/dist"
interface StepInput {
items: {
inventory_item_id: string
required_quantity: number
quantity: number
location_ids: string[]
}[]
}
export const confirmInventoryStepId = "confirm-inventory-step"
export const confirmInventoryStep = createStep(
confirmInventoryStepId,
async (data: StepInput, { container }) => {
const inventoryService = container.resolve<IInventoryService>(
ModuleRegistrationName.INVENTORY
)
// TODO: Should be bulk
const promises = data.items.map((item) => {
const itemQuantity = item.required_quantity * item.quantity
return inventoryService.confirmInventory(
item.inventory_item_id,
item.location_ids,
itemQuantity
)
})
const inventoryCoverage = await promiseAll(promises)
if (inventoryCoverage.some((hasCoverage) => !hasCoverage)) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Some variant does not have the required inventory`,
MedusaError.Codes.INSUFFICIENT_INVENTORY
)
}
return new StepResponse(null)
}
)

View File

@@ -1,5 +1,6 @@
export * from "./add-shipping-method-to-cart"
export * from "./add-to-cart"
export * from "./confirm-inventory"
export * from "./create-carts"
export * from "./create-line-item-adjustments"
export * from "./create-shipping-method-adjustments"

View File

@@ -0,0 +1,63 @@
import { MedusaError } from "medusa-core-utils"
interface ConfirmInventoryPreparationInput {
product_variant_inventory_items: {
variant_id: string
inventory_item_id: string
required_quantity: number
}[]
items: { variant_id?: string; quantity: number }[]
variants: { id: string; manage_inventory?: boolean }[]
location_ids: string[]
}
interface ConfirmInventoryItem {
inventory_item_id: string
required_quantity: number
quantity: number
location_ids: string[]
}
export const prepareConfirmInventoryInput = ({
product_variant_inventory_items,
location_ids,
items,
variants,
}: ConfirmInventoryPreparationInput) => {
if (!product_variant_inventory_items.length) {
return []
}
const variantsMap = new Map<
string,
{ id: string; manage_inventory?: boolean }
>(variants.map((v) => [v.id, v]))
const itemsToConfirm: ConfirmInventoryItem[] = []
items.forEach((item) => {
const variantInventoryItem = product_variant_inventory_items.find(
(i) => i.variant_id === item.variant_id
)
if (!variantInventoryItem) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variant ${item.variant_id} does not have any inventory items associated with it.`
)
}
const variant = variantsMap.get(item.variant_id!)
if (variant?.manage_inventory) {
itemsToConfirm.push({
inventory_item_id: variantInventoryItem.inventory_item_id,
required_quantity: variantInventoryItem.required_quantity,
quantity: item.quantity,
location_ids: location_ids,
})
}
})
return itemsToConfirm
}

View File

@@ -7,31 +7,95 @@ import {
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { MedusaError } from "medusa-core-utils"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import {
addToCartStep,
confirmInventoryStep,
getVariantPriceSetsStep,
getVariantsStep,
validateVariantsExistStep,
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
// TODO: The AddToCartWorkflow are missing the following steps:
// - Confirm inventory exists (inventory module)
// - Refresh/delete shipping methods (fulfillment module)
export const addToCartWorkflowId = "add-to-cart"
export const addToCartWorkflow = createWorkflow(
addToCartWorkflowId,
(input: WorkflowData<AddToCartWorkflowInputDTO>) => {
const variantIds = validateVariantsExistStep({
variantIds: transform({ input }, (data) => {
return (data.input.items ?? []).map((i) => i.variant_id)
}),
const variantIds = transform({ input }, (data) => {
return (data.input.items ?? []).map((i) => i.variant_id)
})
validateVariantsExistStep({ variantIds })
const variants = getVariantsStep({
filter: { id: variantIds },
config: {
select: [
"id",
"title",
"sku",
"barcode",
"product.id",
"product.title",
"product.description",
"product.subtitle",
"product.thumbnail",
"product.type",
"product.collection",
"product.handle",
],
relations: ["product"],
},
})
const salesChannelLocations = useRemoteQueryStep({
entry_point: "sales_channels",
fields: ["id", "name", "stock_locations.id", "stock_locations.name"],
variables: { id: input.cart.sales_channel_id },
})
const productVariantInventoryItems = useRemoteQueryStep({
entry_point: "product_variant_inventory_items",
fields: ["variant_id", "inventory_item_id", "required_quantity"],
variables: { variant_id: variantIds },
}).config({ name: "inventory-items" })
const confirmInventoryInput = transform(
{ productVariantInventoryItems, salesChannelLocations, input, variants },
(data) => {
if (!data.salesChannelLocations.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Sales channel ${data.input.cart.sales_channel_id} is not associated with any stock locations.`
)
}
const items = prepareConfirmInventoryInput({
product_variant_inventory_items: data.productVariantInventoryItems,
location_ids: data.salesChannelLocations[0].stock_locations.map(
(l) => l.id
),
items: data.input.items!,
variants: data.variants.map((v) => ({
id: v.id,
manage_inventory: v.manage_inventory,
})),
})
return { items }
}
)
confirmInventoryStep(confirmInventoryInput)
// TODO: This is on par with the context used in v1.*, but we can be more flexible.
const pricingContext = transform({ cart: input.cart }, (data) => {
return {
@@ -46,31 +110,6 @@ export const addToCartWorkflow = createWorkflow(
context: pricingContext,
})
const variants = getVariantsStep(
transform({ variantIds }, (data) => {
return {
filter: { id: data.variantIds },
config: {
select: [
"id",
"title",
"sku",
"barcode",
"product.id",
"product.title",
"product.description",
"product.subtitle",
"product.thumbnail",
"product.type",
"product.collection",
"product.handle",
],
relations: ["product"],
},
}
})
)
const lineItems = transform({ priceSets, input, variants }, (data) => {
const items = (data.input.items ?? []).map((item) => {
const variant = data.variants.find((v) => v.id === item.variant_id)!

View File

@@ -5,7 +5,10 @@ import {
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { MedusaError } from "medusa-core-utils"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import {
confirmInventoryStep,
createCartsStep,
findOneOrAnyRegionStep,
findOrCreateCustomerStep,
@@ -16,11 +19,11 @@ import {
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
// TODO: The UpdateLineItemsWorkflow are missing the following steps:
// - Confirm inventory exists (inventory module)
// TODO: The createCartWorkflow are missing the following steps:
// - Refresh/delete shipping methods (fulfillment module)
export const createCartWorkflowId = "create-cart"
@@ -45,6 +48,73 @@ export const createCartWorkflow = createWorkflow(
validateVariantsExistStep({ variantIds })
)
const variants = getVariantsStep({
filter: { id: variantIds },
config: {
select: [
"id",
"title",
"sku",
"manage_inventory",
"barcode",
"product.id",
"product.title",
"product.description",
"product.subtitle",
"product.thumbnail",
"product.type",
"product.collection",
"product.handle",
],
relations: ["product"],
},
})
const salesChannelLocations = useRemoteQueryStep({
entry_point: "sales_channels",
fields: ["id", "name", "stock_locations.id", "stock_locations.name"],
variables: { id: salesChannel.id },
})
const productVariantInventoryItems = useRemoteQueryStep({
entry_point: "product_variant_inventory_items",
fields: ["variant_id", "inventory_item_id", "required_quantity"],
variables: { variant_id: variantIds },
}).config({ name: "inventory-items" })
const confirmInventoryInput = transform(
{ productVariantInventoryItems, salesChannelLocations, input, variants },
(data) => {
// We don't want to confirm inventory if there are no items in the cart.
if (!data.input.items) {
return { items: [] }
}
if (!data.salesChannelLocations.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Sales channel ${data.input.sales_channel_id} is not associated with any stock locations.`
)
}
const items = prepareConfirmInventoryInput({
product_variant_inventory_items: data.productVariantInventoryItems,
location_ids: data.salesChannelLocations[0].stock_locations.map(
(l) => l.id
),
items: data.input.items!,
variants: data.variants.map((v) => ({
id: v.id,
manage_inventory: v.manage_inventory,
})),
})
return { items }
}
)
confirmInventoryStep(confirmInventoryInput)
// TODO: This is on par with the context used in v1.*, but we can be more flexible.
const pricingContext = transform(
{ input, region, customerData },
@@ -84,31 +154,6 @@ export const createCartWorkflow = createWorkflow(
}
)
const variants = getVariantsStep(
transform({ variantIds }, (data) => {
return {
filter: { id: data.variantIds },
config: {
select: [
"id",
"title",
"sku",
"barcode",
"product.id",
"product.title",
"product.description",
"product.subtitle",
"product.thumbnail",
"product.type",
"product.collection",
"product.handle",
],
relations: ["product"],
},
}
})
)
const lineItems = transform({ priceSets, input, variants }, (data) => {
const items = (data.input.items ?? []).map((item) => {
const variant = data.variants.find((v) => v.id === item.variant_id)!

View File

@@ -4,15 +4,20 @@ import {
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { MedusaError } from "medusa-core-utils"
import {
confirmInventoryStep,
getVariantPriceSetsStep,
getVariantsStep,
} from ".."
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import { updateLineItemsStep } from "../../line-item/steps"
import { getVariantPriceSetsStep } from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
// TODO: The UpdateLineItemsWorkflow are missing the following steps:
// - Confirm inventory exists (inventory module)
// - Validate shipping methods for new items (fulfillment module)
// - Refresh line item adjustments (promotion module)
export const updateLineItemInCartWorkflowId = "update-line-item-in-cart"
export const updateLineItemInCartWorkflow = createWorkflow(
@@ -32,6 +37,51 @@ export const updateLineItemInCartWorkflow = createWorkflow(
data.input.item.variant_id!,
])
const salesChannelLocations = useRemoteQueryStep({
entry_point: "sales_channels",
fields: ["id", "name", "stock_locations.id", "stock_locations.name"],
variables: { id: input.cart.sales_channel_id },
})
const productVariantInventoryItems = useRemoteQueryStep({
entry_point: "product_variant_inventory_items",
fields: ["variant_id", "inventory_item_id", "required_quantity"],
variables: { variant_id: variantIds },
}).config({ name: "inventory-items" })
const variants = getVariantsStep({
filter: { id: variantIds },
config: { select: ["id", "manage_inventory"] },
})
const confirmInventoryInput = transform(
{ productVariantInventoryItems, salesChannelLocations, input, variants },
(data) => {
if (!data.salesChannelLocations.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Sales channel ${data.input.cart.sales_channel_id} is not associated with any stock locations.`
)
}
const items = prepareConfirmInventoryInput({
product_variant_inventory_items: data.productVariantInventoryItems,
location_ids: data.salesChannelLocations[0].stock_locations.map(
(l) => l.id
),
items: [data.input.item],
variants: data.variants.map((v) => ({
id: v.id,
manage_inventory: v.manage_inventory,
})),
})
return { items }
}
)
confirmInventoryStep(confirmInventoryInput)
const priceSets = getVariantPriceSetsStep({
variantIds,
context: pricingContext,

View File

@@ -1,29 +1,29 @@
import { InternalModuleDeclaration } from "@medusajs/modules-sdk"
import {
Context,
DAL,
IInventoryServiceNext,
InventoryNext,
InventoryTypes,
ModuleJoinerConfig,
ModulesSdkTypes,
InventoryNext,
ReservationItemDTO,
} from "@medusajs/types"
import {
CommonEvents,
EmitEvents,
InjectManager,
InjectTransactionManager,
InventoryEvents,
MedusaContext,
MedusaError,
ModulesSdkUtils,
isDefined,
partitionArray,
} from "@medusajs/utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import { InventoryItem, InventoryLevel, ReservationItem } from "@models"
import { DAL } from "@medusajs/types"
import { InjectTransactionManager } from "@medusajs/utils"
import { InjectManager } from "@medusajs/utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import InventoryLevelService from "./inventory-level"
import { partitionArray } from "@medusajs/utils"
import { InventoryEvents } from "@medusajs/utils"
import { CommonEvents } from "@medusajs/utils"
import { isDefined } from "@medusajs/utils"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService

View File

@@ -6,7 +6,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
serviceName: LINKS.SalesChannelLocation,
isLink: true,
databaseConfig: {
tableName: "sales_channel_locations",
tableName: "sales_channel_stock_location",
idPrefix: "scloc",
},
alias: [

View File

@@ -2,8 +2,8 @@ import * as StockLocationModels from "@models"
import * as StockLocationRepostiories from "@repositories"
import * as StockLocationServices from "@services"
import { ModuleExports } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
import { ModuleExports } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
import { StockLocationModuleService } from "@services"