feat(medusa): complete cart create reservation (#7250)

This commit is contained in:
Carlos R. L. Rodrigues
2024-05-06 14:36:55 -03:00
committed by GitHub
parent a736e728b8
commit 5228b14ca9
18 changed files with 195 additions and 77 deletions

View File

@@ -1,5 +1,5 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IInventoryService } from "@medusajs/types"
import { IInventoryServiceNext } from "@medusajs/types"
import { promiseAll } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { MedusaError } from "medusa-core-utils"
@@ -18,7 +18,7 @@ export const confirmInventoryStepId = "confirm-inventory-step"
export const confirmInventoryStep = createStep(
confirmInventoryStepId,
async (data: StepInput, { container }) => {
const inventoryService = container.resolve<IInventoryService>(
const inventoryService = container.resolve<IInventoryServiceNext>(
ModuleRegistrationName.INVENTORY
)

View File

@@ -22,4 +22,4 @@ export * from "./set-tax-lines-for-items"
export * from "./update-cart-promotions"
export * from "./update-carts"
export * from "./validate-cart-shipping-options"
export * from "./validate-variants-existence"
export * from "./validate-variant-prices"

View File

@@ -0,0 +1,51 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IInventoryServiceNext } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
items: {
id?: string
inventory_item_id: string
required_quantity: number
allow_backorder: boolean
quantity: number
location_ids: string[]
}[]
}
export const reserveInventoryStepId = "reserve-inventory-step"
export const reserveInventoryStep = createStep(
reserveInventoryStepId,
async (data: StepInput, { container }) => {
const inventoryService = container.resolve<IInventoryServiceNext>(
ModuleRegistrationName.INVENTORY
)
const items = data.items.map((item) => ({
line_item_id: item.id,
inventory_item_id: item.inventory_item_id,
quantity: item.required_quantity * item.quantity,
allow_backorder: item.allow_backorder,
location_id: item.location_ids[0],
}))
const reservations = await inventoryService.createReservationItems(items)
return new StepResponse(void 0, {
reservations: reservations.map((r) => r.id),
})
},
async (data, { container }) => {
if (!data) {
return
}
const inventoryService = container.resolve<IInventoryServiceNext>(
ModuleRegistrationName.INVENTORY
)
await inventoryService.deleteReservationItems(data.reservations)
return new StepResponse()
}
)

View File

@@ -0,0 +1,31 @@
import { BigNumberInput } from "@medusajs/types"
import { MedusaError, isDefined } from "@medusajs/utils"
import { createStep } from "@medusajs/workflows-sdk"
interface StepInput {
variants: {
id: string
calculated_price?: BigNumberInput
}[]
}
export const validateVariantPricesStepId = "validate-variant-prices"
export const validateVariantPricesStep = createStep(
validateVariantPricesStepId,
async (data: StepInput, { container }) => {
const priceNotFound: string[] = []
for (const variant of data.variants) {
if (!isDefined(variant.calculated_price)) {
priceNotFound.push(variant.id)
}
}
if (priceNotFound.length > 0) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variants with IDs ${priceNotFound.join(", ")} do not have a price`
)
}
}
)

View File

@@ -1,38 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IProductModuleService } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
variantIds: string[]
}
export const validateVariantsExistStepId = "validate-variants-exist"
export const validateVariantsExistStep = createStep(
validateVariantsExistStepId,
async (data: StepInput, { container }) => {
const productModuleService = container.resolve<IProductModuleService>(
ModuleRegistrationName.PRODUCT
)
const variants = await productModuleService.listVariants(
{ id: data.variantIds },
{ select: ["id"] }
)
const variantIdToData = new Set(variants.map((v) => v.id))
const notFoundVariants = new Set(
[...data.variantIds].filter((x) => !variantIdToData.has(x))
)
if (notFoundVariants.size) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variants with IDs ${[...notFoundVariants].join(", ")} do not exist`
)
}
return new StepResponse(Array.from(variants.map((v) => v.id)))
}
)

View File

@@ -8,6 +8,7 @@ interface ConfirmInventoryPreparationInput {
required_quantity: number
}[]
items: {
id?: string
variant_id?: string
quantity: BigNumberInput
}[]
@@ -20,6 +21,7 @@ interface ConfirmInventoryPreparationInput {
}
interface ConfirmInventoryItem {
id?: string
inventory_item_id: string
required_quantity: number
allow_backorder: boolean
@@ -63,6 +65,7 @@ export const prepareConfirmInventoryInput = ({
}
itemsToConfirm.push({
id: item.id,
inventory_item_id: variantInventoryItem.inventory_item_id,
required_quantity: variantInventoryItem.required_quantity,
allow_backorder: !!variant.allow_backorder,

View File

@@ -11,6 +11,7 @@ import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import { addToCartStep, refreshCartShippingMethodsStep } from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
import {
cartFieldsForRefreshSteps,
productVariantsFields,
@@ -51,6 +52,8 @@ export const addToCartWorkflow = createWorkflow(
throw_if_key_not_found: true,
})
validateVariantPricesStep({ variants })
confirmVariantInventoryWorkflow.runAsStep({
input: {
sales_channel_id: input.cart.sales_channel_id as string,

View File

@@ -6,6 +6,7 @@ import {
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { createOrderFromCartStep } from "../steps"
import { reserveInventoryStep } from "../steps/reserve-inventory"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { completeCartFields } from "../utils/fields"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
@@ -40,6 +41,7 @@ export const completeCartWorkflow = createWorkflow(
data.cart.items.forEach((item) => {
allItems.push({
id: item.id,
variant_id: item.variant_id,
quantity: item.quantity,
})
@@ -54,9 +56,8 @@ export const completeCartWorkflow = createWorkflow(
}
)
confirmVariantInventoryWorkflow.runAsStep({
const formatedInventoryItems = confirmVariantInventoryWorkflow.runAsStep({
input: {
ignore_price_check: true,
sales_channel_id,
variants,
items,
@@ -65,6 +66,8 @@ export const completeCartWorkflow = createWorkflow(
updateTaxLinesStep({ cart_or_cart_id: cart, force_tax_calculation: true })
reserveInventoryStep(formatedInventoryItems)
const finalCart = useRemoteQueryStep({
entry_point: "cart",
fields: completeCartFields,

View File

@@ -1,5 +1,5 @@
import { ConfirmVariantInventoryWorkflowInputDTO } from "@medusajs/types"
import { MedusaError, deepFlatMap, isDefined } from "@medusajs/utils"
import { MedusaError, deepFlatMap } from "@medusajs/utils"
import {
WorkflowData,
createWorkflow,
@@ -8,13 +8,25 @@ import {
import { confirmInventoryStep } from "../steps"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
interface Output {
items: {
id?: string
inventory_item_id: string
required_quantity: number
allow_backorder: boolean
quantity: number
location_ids: string[]
}[]
}
export const confirmVariantInventoryWorkflowId = "confirm-item-inventory"
export const confirmVariantInventoryWorkflow = createWorkflow(
confirmVariantInventoryWorkflowId,
(input: WorkflowData<ConfirmVariantInventoryWorkflowInputDTO>) => {
(
input: WorkflowData<ConfirmVariantInventoryWorkflowInputDTO>
): WorkflowData<Output> => {
const confirmInventoryInput = transform({ input }, (data) => {
const productVariantInventoryItems = new Map<string, any>()
const priceNotFound: string[] = []
const stockLocationIds = new Set<string>()
const allVariants = new Map<string, any>()
let hasSalesChannelStockLocation = false
@@ -26,6 +38,10 @@ export const confirmVariantInventoryWorkflow = createWorkflow(
data.input,
"variants.inventory_items.inventory.location_levels.stock_locations.sales_channels",
({ variants, inventory_items, stock_locations, sales_channels }) => {
if (!variants) {
return
}
if (
!hasSalesChannelStockLocation &&
sales_channels?.id === salesChannelId
@@ -33,19 +49,19 @@ export const confirmVariantInventoryWorkflow = createWorkflow(
hasSalesChannelStockLocation = true
}
if (!isDefined(variants.calculated_price)) {
priceNotFound.push(variants.id)
if (stock_locations) {
stockLocationIds.add(stock_locations.id)
}
stockLocationIds.add(stock_locations.id)
const inventoryItemId = inventory_items.inventory_item_id
if (!productVariantInventoryItems.has(inventoryItemId)) {
productVariantInventoryItems.set(inventoryItemId, {
variant_id: inventory_items.variant_id,
inventory_item_id: inventoryItemId,
required_quantity: inventory_items.required_quantity,
})
if (inventory_items) {
const inventoryItemId = inventory_items.inventory_item_id
if (!productVariantInventoryItems.has(inventoryItemId)) {
productVariantInventoryItems.set(inventoryItemId, {
variant_id: inventory_items.variant_id,
inventory_item_id: inventoryItemId,
required_quantity: inventory_items.required_quantity,
})
}
}
if (!allVariants.has(variants.id)) {
@@ -72,13 +88,6 @@ export const confirmVariantInventoryWorkflow = createWorkflow(
)
}
if (priceNotFound.length && !data.input.ignore_price_check) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variants with IDs ${priceNotFound.join(", ")} do not have a price`
)
}
const items = prepareConfirmInventoryInput({
product_variant_inventory_items: Array.from(
productVariantInventoryItems.values()
@@ -92,5 +101,7 @@ export const confirmVariantInventoryWorkflow = createWorkflow(
})
confirmInventoryStep(confirmInventoryInput)
return confirmInventoryInput
}
)

View File

@@ -15,6 +15,7 @@ import {
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
import { productVariantsFields } from "../utils/fields"
import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
@@ -68,6 +69,8 @@ export const createCartWorkflow = createWorkflow(
throw_if_key_not_found: true,
})
validateVariantPricesStep({ variants })
confirmVariantInventoryWorkflow.runAsStep({
input: {
sales_channel_id: salesChannel.id,

View File

@@ -78,7 +78,7 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
({ shipping_options }) => {
const { calculated_price, ...options } = shipping_options ?? {}
if (!calculated_price) {
if (options?.id && !calculated_price) {
optionsMissingPrices.push(options.id)
}

View File

@@ -8,6 +8,7 @@ import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import { updateLineItemsStep } from "../../line-item/steps"
import { refreshCartShippingMethodsStep } from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
import {
cartFieldsForRefreshSteps,
productVariantsFields,
@@ -47,6 +48,8 @@ export const updateLineItemInCartWorkflow = createWorkflow(
throw_if_key_not_found: true,
})
validateVariantPricesStep({ variants })
const items = transform({ input }, (data) => {
return [data.input.item]
})

View File

@@ -13,7 +13,6 @@ import { findOrCreateCustomerStep } from "../../definition/cart/steps/find-or-cr
import { findSalesChannelStep } from "../../definition/cart/steps/find-sales-channel"
import { getVariantPriceSetsStep } from "../../definition/cart/steps/get-variant-price-sets"
import { getVariantsStep } from "../../definition/cart/steps/get-variants"
import { validateVariantsExistStep } from "../../definition/cart/steps/validate-variants-existence"
import { prepareConfirmInventoryInput } from "../../definition/cart/utils/prepare-confirm-inventory-input"
import { prepareLineItemData } from "../../definition/cart/utils/prepare-line-item-data"
import { createOrdersStep, updateOrderTaxLinesStep } from "../steps"
@@ -39,8 +38,8 @@ export const createOrdersWorkflow = createWorkflow(
findOrCreateCustomerStep({
customerId: input.customer_id,
email: input.email,
}),
validateVariantsExistStep({ variantIds })
})
// validateVariantsExistStep({ variantIds })
)
const variants = getVariantsStep({