feat: Confirm inventory in create cart workflow (#6635)
This commit is contained in:
@@ -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"],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)!
|
||||
|
||||
@@ -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)!
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user