diff --git a/.changeset/itchy-ants-smoke.md b/.changeset/itchy-ants-smoke.md new file mode 100644 index 0000000000..782317df82 --- /dev/null +++ b/.changeset/itchy-ants-smoke.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +Fix inventory adjustments diff --git a/packages/medusa/src/api/routes/admin/draft-orders/register-payment.ts b/packages/medusa/src/api/routes/admin/draft-orders/register-payment.ts index 88509b5751..96144e0c77 100644 --- a/packages/medusa/src/api/routes/admin/draft-orders/register-payment.ts +++ b/packages/medusa/src/api/routes/admin/draft-orders/register-payment.ts @@ -3,6 +3,7 @@ import { DraftOrderService, OrderService, PaymentProviderService, + ProductVariantInventoryService, } from "../../../../services" import { defaultAdminOrdersFields as defaultOrderFields, @@ -10,6 +11,8 @@ import { } from "../orders/index" import { EntityManager } from "typeorm" +import { Order } from "../../../../models" +import { MedusaError } from "medusa-core-utils" /** * @oas [post] /draft-orders/{id}/pay @@ -71,43 +74,90 @@ export default async (req, res) => { ) const orderService: OrderService = req.scope.resolve("orderService") const cartService: CartService = req.scope.resolve("cartService") + const productVariantInventoryService: ProductVariantInventoryService = + req.scope.resolve("productVariantInventoryService") const entityManager: EntityManager = req.scope.resolve("manager") - let result - await entityManager.transaction(async (manager) => { - const draftOrder = await draftOrderService - .withTransaction(manager) - .retrieve(id) + const order = await entityManager.transaction(async (manager) => { + const draftOrderServiceTx = draftOrderService.withTransaction(manager) + const orderServiceTx = orderService.withTransaction(manager) + const cartServiceTx = cartService.withTransaction(manager) - const cart = await cartService - .withTransaction(manager) - .retrieveWithTotals(draftOrder.cart_id) + const productVariantInventoryServiceTx = + productVariantInventoryService.withTransaction(manager) + + const draftOrder = await draftOrderServiceTx.retrieve(id) + + const cart = await cartServiceTx.retrieveWithTotals(draftOrder.cart_id) await paymentProviderService .withTransaction(manager) .createSession("system", cart) - await cartService + await cartServiceTx.setPaymentSession(cart.id, "system") + + await cartServiceTx.createTaxLines(cart.id) + + await cartServiceTx.authorizePayment(cart.id) + + let order = await orderServiceTx.createFromCart(cart.id) + + await draftOrderServiceTx.registerCartCompletion(draftOrder.id, order.id) + + await orderServiceTx.capturePayment(order.id) + + order = await orderService .withTransaction(manager) - .setPaymentSession(cart.id, "system") + .retrieveWithTotals(order.id, { + relations: defaultOrderRelations, + select: defaultOrderFields, + }) - await cartService.withTransaction(manager).createTaxLines(cart.id) + await reserveQuantityForDraftOrder(order, { + productVariantInventoryService: productVariantInventoryServiceTx, + }) - await cartService.withTransaction(manager).authorizePayment(cart.id) - - result = await orderService.withTransaction(manager).createFromCart(cart.id) - - await draftOrderService - .withTransaction(manager) - .registerCartCompletion(draftOrder.id, result.id) - - await orderService.withTransaction(manager).capturePayment(result.id) - }) - - const order = await orderService.retrieveWithTotals(result.id, { - relations: defaultOrderRelations, - select: defaultOrderFields, + return order }) res.status(200).json({ order }) } + +export const reserveQuantityForDraftOrder = async ( + order: Order, + context: { + productVariantInventoryService: ProductVariantInventoryService + locationId?: string + } +) => { + const { productVariantInventoryService, locationId } = context + await Promise.all( + order.items.map(async (item) => { + if (item.variant_id) { + const inventoryConfirmed = + await productVariantInventoryService.confirmInventory( + item.variant_id, + item.quantity, + { salesChannelId: order.sales_channel_id } + ) + + if (!inventoryConfirmed) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + `Variant with id: ${item.variant_id} does not have the required inventory`, + MedusaError.Codes.INSUFFICIENT_INVENTORY + ) + } + + await productVariantInventoryService.reserveQuantity( + item.variant_id, + item.quantity, + { + lineItemId: item.id, + salesChannelId: order.sales_channel_id, + } + ) + } + }) + ) +} diff --git a/packages/medusa/src/api/routes/admin/orders/cancel-fulfillment.ts b/packages/medusa/src/api/routes/admin/orders/cancel-fulfillment.ts index f9bd252803..99986f48e7 100644 --- a/packages/medusa/src/api/routes/admin/orders/cancel-fulfillment.ts +++ b/packages/medusa/src/api/routes/admin/orders/cancel-fulfillment.ts @@ -8,6 +8,7 @@ import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "." import { EntityManager } from "typeorm" import { MedusaError } from "medusa-core-utils" import { Fulfillment } from "../../../../models" +import { IInventoryService } from "../../../../interfaces" /** * @oas [post] /orders/{id}/fulfillments/{fulfillment_id}/cancel @@ -63,6 +64,8 @@ export default async (req, res) => { const { id, fulfillment_id } = req.params const orderService: OrderService = req.scope.resolve("orderService") + const inventoryService: IInventoryService = + req.scope.resolve("inventoryService") const productVariantInventoryService: ProductVariantInventoryService = req.scope.resolve("productVariantInventoryService") @@ -88,10 +91,12 @@ export default async (req, res) => { .withTransaction(transactionManager) .retrieve(fulfillment_id, { relations: ["items", "items.item"] }) - await adjustInventoryForCancelledFulfillment(fulfillment, { - productVariantInventoryService: - productVariantInventoryService.withTransaction(transactionManager), - }) + if (fulfillment.location_id && inventoryService) { + await adjustInventoryForCancelledFulfillment(fulfillment, { + productVariantInventoryService: + productVariantInventoryService.withTransaction(transactionManager), + }) + } }) const order = await orderService.retrieve(id, { diff --git a/packages/medusa/src/helpers/test-request.js b/packages/medusa/src/helpers/test-request.js index cb0ee38003..9201a9848c 100644 --- a/packages/medusa/src/helpers/test-request.js +++ b/packages/medusa/src/helpers/test-request.js @@ -11,6 +11,8 @@ import { moduleHelper } from "../loaders/module" import passportLoader from "../loaders/passport" import servicesLoader from "../loaders/services" import strategiesLoader from "../loaders/strategies" +import registerModuleDefinitions from "../loaders/module-definitions" +import moduleLoader from "../loaders/module" const adminSessionOpts = { cookieName: "session", @@ -24,6 +26,7 @@ const clientSessionOpts = { secret: "test", } +const moduleResolutions = registerModuleDefinitions({}) const config = { projectConfig: { jwt_secret: "supersecret", @@ -31,6 +34,7 @@ const config = { admin_cors: "", store_cors: "", }, + moduleResolutions, } const testApp = express() @@ -64,6 +68,7 @@ featureFlagLoader(config) servicesLoader({ container, configModule: config }) strategiesLoader({ container, configModule: config }) passportLoader({ app: testApp, container, configModule: config }) +moduleLoader({ container, configModule: config }) testApp.use((req, res, next) => { req.scope = container.createScope() diff --git a/packages/medusa/src/services/order.ts b/packages/medusa/src/services/order.ts index 548ec0c9b0..fc0a18e7c8 100644 --- a/packages/medusa/src/services/order.ts +++ b/packages/medusa/src/services/order.ts @@ -1146,23 +1146,13 @@ class OrderService extends TransactionBaseService { const inventoryServiceTx = this.productVariantInventoryService_.withTransaction(manager) - const previouslyFulfilledQuantities = order.fulfillments.reduce( - (acc, f) => { - return f.items.reduce((acc, item) => { - acc[item.item_id] = (acc[item.item_id] || 0) + item.quantity - return acc - }, acc) - }, - {} - ) - await Promise.all( order.items.map(async (item) => { if (item.variant_id) { return await inventoryServiceTx.deleteReservationsByLineItem( item.id, item.variant_id, - item.quantity - (previouslyFulfilledQuantities[item.id] || 0) + item.quantity ) } })