fix(medusa): Draft order inventory management (#3040)
* reserve quantities when creating a draft order * only adjust quantities if a location is provided on a fulfillment * initial fix for fulfillments * changeset * add check for inventory service when cancelling fulfillment * update test-request with module loaders * remove unused variable
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix inventory adjustments
|
||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
DraftOrderService,
|
DraftOrderService,
|
||||||
OrderService,
|
OrderService,
|
||||||
PaymentProviderService,
|
PaymentProviderService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
} from "../../../../services"
|
} from "../../../../services"
|
||||||
import {
|
import {
|
||||||
defaultAdminOrdersFields as defaultOrderFields,
|
defaultAdminOrdersFields as defaultOrderFields,
|
||||||
@@ -10,6 +11,8 @@ import {
|
|||||||
} from "../orders/index"
|
} from "../orders/index"
|
||||||
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
import { Order } from "../../../../models"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /draft-orders/{id}/pay
|
* @oas [post] /draft-orders/{id}/pay
|
||||||
@@ -71,43 +74,90 @@ export default async (req, res) => {
|
|||||||
)
|
)
|
||||||
const orderService: OrderService = req.scope.resolve("orderService")
|
const orderService: OrderService = req.scope.resolve("orderService")
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
const entityManager: EntityManager = req.scope.resolve("manager")
|
const entityManager: EntityManager = req.scope.resolve("manager")
|
||||||
|
|
||||||
let result
|
const order = await entityManager.transaction(async (manager) => {
|
||||||
await entityManager.transaction(async (manager) => {
|
const draftOrderServiceTx = draftOrderService.withTransaction(manager)
|
||||||
const draftOrder = await draftOrderService
|
const orderServiceTx = orderService.withTransaction(manager)
|
||||||
.withTransaction(manager)
|
const cartServiceTx = cartService.withTransaction(manager)
|
||||||
.retrieve(id)
|
|
||||||
|
|
||||||
const cart = await cartService
|
const productVariantInventoryServiceTx =
|
||||||
.withTransaction(manager)
|
productVariantInventoryService.withTransaction(manager)
|
||||||
.retrieveWithTotals(draftOrder.cart_id)
|
|
||||||
|
const draftOrder = await draftOrderServiceTx.retrieve(id)
|
||||||
|
|
||||||
|
const cart = await cartServiceTx.retrieveWithTotals(draftOrder.cart_id)
|
||||||
|
|
||||||
await paymentProviderService
|
await paymentProviderService
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
.createSession("system", cart)
|
.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)
|
.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)
|
return order
|
||||||
|
|
||||||
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,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
res.status(200).json({ 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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "."
|
|||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { Fulfillment } from "../../../../models"
|
import { Fulfillment } from "../../../../models"
|
||||||
|
import { IInventoryService } from "../../../../interfaces"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /orders/{id}/fulfillments/{fulfillment_id}/cancel
|
* @oas [post] /orders/{id}/fulfillments/{fulfillment_id}/cancel
|
||||||
@@ -63,6 +64,8 @@ export default async (req, res) => {
|
|||||||
const { id, fulfillment_id } = req.params
|
const { id, fulfillment_id } = req.params
|
||||||
|
|
||||||
const orderService: OrderService = req.scope.resolve("orderService")
|
const orderService: OrderService = req.scope.resolve("orderService")
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
req.scope.resolve("inventoryService")
|
||||||
const productVariantInventoryService: ProductVariantInventoryService =
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
req.scope.resolve("productVariantInventoryService")
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
@@ -88,10 +91,12 @@ export default async (req, res) => {
|
|||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.retrieve(fulfillment_id, { relations: ["items", "items.item"] })
|
.retrieve(fulfillment_id, { relations: ["items", "items.item"] })
|
||||||
|
|
||||||
await adjustInventoryForCancelledFulfillment(fulfillment, {
|
if (fulfillment.location_id && inventoryService) {
|
||||||
productVariantInventoryService:
|
await adjustInventoryForCancelledFulfillment(fulfillment, {
|
||||||
productVariantInventoryService.withTransaction(transactionManager),
|
productVariantInventoryService:
|
||||||
})
|
productVariantInventoryService.withTransaction(transactionManager),
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const order = await orderService.retrieve(id, {
|
const order = await orderService.retrieve(id, {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { moduleHelper } from "../loaders/module"
|
|||||||
import passportLoader from "../loaders/passport"
|
import passportLoader from "../loaders/passport"
|
||||||
import servicesLoader from "../loaders/services"
|
import servicesLoader from "../loaders/services"
|
||||||
import strategiesLoader from "../loaders/strategies"
|
import strategiesLoader from "../loaders/strategies"
|
||||||
|
import registerModuleDefinitions from "../loaders/module-definitions"
|
||||||
|
import moduleLoader from "../loaders/module"
|
||||||
|
|
||||||
const adminSessionOpts = {
|
const adminSessionOpts = {
|
||||||
cookieName: "session",
|
cookieName: "session",
|
||||||
@@ -24,6 +26,7 @@ const clientSessionOpts = {
|
|||||||
secret: "test",
|
secret: "test",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const moduleResolutions = registerModuleDefinitions({})
|
||||||
const config = {
|
const config = {
|
||||||
projectConfig: {
|
projectConfig: {
|
||||||
jwt_secret: "supersecret",
|
jwt_secret: "supersecret",
|
||||||
@@ -31,6 +34,7 @@ const config = {
|
|||||||
admin_cors: "",
|
admin_cors: "",
|
||||||
store_cors: "",
|
store_cors: "",
|
||||||
},
|
},
|
||||||
|
moduleResolutions,
|
||||||
}
|
}
|
||||||
|
|
||||||
const testApp = express()
|
const testApp = express()
|
||||||
@@ -64,6 +68,7 @@ featureFlagLoader(config)
|
|||||||
servicesLoader({ container, configModule: config })
|
servicesLoader({ container, configModule: config })
|
||||||
strategiesLoader({ container, configModule: config })
|
strategiesLoader({ container, configModule: config })
|
||||||
passportLoader({ app: testApp, container, configModule: config })
|
passportLoader({ app: testApp, container, configModule: config })
|
||||||
|
moduleLoader({ container, configModule: config })
|
||||||
|
|
||||||
testApp.use((req, res, next) => {
|
testApp.use((req, res, next) => {
|
||||||
req.scope = container.createScope()
|
req.scope = container.createScope()
|
||||||
|
|||||||
@@ -1146,23 +1146,13 @@ class OrderService extends TransactionBaseService {
|
|||||||
const inventoryServiceTx =
|
const inventoryServiceTx =
|
||||||
this.productVariantInventoryService_.withTransaction(manager)
|
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(
|
await Promise.all(
|
||||||
order.items.map(async (item) => {
|
order.items.map(async (item) => {
|
||||||
if (item.variant_id) {
|
if (item.variant_id) {
|
||||||
return await inventoryServiceTx.deleteReservationsByLineItem(
|
return await inventoryServiceTx.deleteReservationsByLineItem(
|
||||||
item.id,
|
item.id,
|
||||||
item.variant_id,
|
item.variant_id,
|
||||||
item.quantity - (previouslyFulfilledQuantities[item.id] || 0)
|
item.quantity
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user