feat(medusa): Create fulfillment with location (#2931)
* remove duplicate key from oas * changeset * initial suggestion for adding locations to fulfillments * update migration * re-add functionality for removing entire reservations * fix tests * add location when adjusting reserved inventory of a line_item * add changest * handle multiple reservations for a product in the same channel * confirm inventory in stock location previous to creating the fulfillment * fix tests after updating create-fulfillment to confirm inventory prior to creating fulfillment * remove bugged code * initial validation * initial changes for review * chekcpoint * update validate inventory at location * redo some unwanted changes * typing * update snapshots * redo change for eslintrc * add eslint disable * re-order methods in interface * assert no_notification * iterate one time less * add test for validation of correct inventory adjustments in case of no inventory service installation * ensure correct adjustments for order cancellations * remove comment * fix tests * fix but with coalescing * remove location id from confirm inventory * don't throw when adjusting reservations for a line item without reservations * move reservation adjustments to the api * add multiplication for updating a reservation quantity * move inventory adjustments from the service layer to the api * delete reservation if quantity is adjusted to 0 * rename updateReservation to updateReservationItem * update dto fields * reference the correct fields * update with transaction * add jsdocs * force boolean cast * context-ize cancel and create fulfillment transaction methods * undo notification cast * update with changes * refactor withTransaction to variable * use maps * fix service mocks
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import { FulfillmentService, OrderService } from "../../../../services"
|
||||
import {
|
||||
FulfillmentService,
|
||||
OrderService,
|
||||
ProductVariantInventoryService,
|
||||
} from "../../../../services"
|
||||
import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "."
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { Fulfillment } from "../../../../models"
|
||||
|
||||
/**
|
||||
* @oas [post] /orders/{id}/fulfillments/{fulfillment_id}/cancel
|
||||
@@ -61,6 +66,9 @@ export default async (req, res) => {
|
||||
const { id, fulfillment_id } = req.params
|
||||
|
||||
const orderService: OrderService = req.scope.resolve("orderService")
|
||||
const productVariantInventoryService: ProductVariantInventoryService =
|
||||
req.scope.resolve("productVariantInventoryService")
|
||||
|
||||
const fulfillmentService: FulfillmentService =
|
||||
req.scope.resolve("fulfillmentService")
|
||||
|
||||
@@ -75,9 +83,18 @@ export default async (req, res) => {
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
return await orderService
|
||||
await orderService
|
||||
.withTransaction(transactionManager)
|
||||
.cancelFulfillment(fulfillment_id)
|
||||
|
||||
const fulfillment = await fulfillmentService
|
||||
.withTransaction(transactionManager)
|
||||
.retrieve(fulfillment_id, { relations: ["items", "items.item"] })
|
||||
|
||||
await adjustInventoryForCancelledFulfillment(fulfillment, {
|
||||
productVariantInventoryService:
|
||||
productVariantInventoryService.withTransaction(transactionManager),
|
||||
})
|
||||
})
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
@@ -87,3 +104,23 @@ export default async (req, res) => {
|
||||
|
||||
res.json({ order })
|
||||
}
|
||||
|
||||
export const adjustInventoryForCancelledFulfillment = async (
|
||||
fulfillment: Fulfillment,
|
||||
context: {
|
||||
productVariantInventoryService: ProductVariantInventoryService
|
||||
}
|
||||
) => {
|
||||
const { productVariantInventoryService } = context
|
||||
await Promise.all(
|
||||
fulfillment.items.map(async ({ item, quantity }) => {
|
||||
if (item.variant_id) {
|
||||
await productVariantInventoryService.adjustInventory(
|
||||
item.variant_id,
|
||||
fulfillment.location_id!,
|
||||
quantity
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,13 @@ import { Transform, Type } from "class-transformer"
|
||||
import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "."
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
import { OrderService } from "../../../../services"
|
||||
import {
|
||||
OrderService,
|
||||
ProductVariantInventoryService,
|
||||
} from "../../../../services"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean"
|
||||
import { Fulfillment, LineItem } from "../../../../models"
|
||||
|
||||
/**
|
||||
* @oas [post] /orders/{id}/fulfillment
|
||||
@@ -98,15 +102,39 @@ export default async (req, res) => {
|
||||
)
|
||||
|
||||
const orderService: OrderService = req.scope.resolve("orderService")
|
||||
|
||||
const pvInventoryService: ProductVariantInventoryService = req.scope.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
return await orderService
|
||||
const { fulfillments: existingFulfillments } = await orderService
|
||||
.withTransaction(transactionManager)
|
||||
.retrieve(id, {
|
||||
relations: ["fulfillments"],
|
||||
})
|
||||
const existingFulfillmentMap = new Map(
|
||||
existingFulfillments.map((fulfillment) => [fulfillment.id, fulfillment])
|
||||
)
|
||||
|
||||
const { fulfillments } = await orderService
|
||||
.withTransaction(transactionManager)
|
||||
.createFulfillment(id, validated.items, {
|
||||
metadata: validated.metadata,
|
||||
no_notification: validated.no_notification,
|
||||
})
|
||||
|
||||
const pvInventoryServiceTx =
|
||||
pvInventoryService.withTransaction(transactionManager)
|
||||
|
||||
if (validated.location_id) {
|
||||
await updateInventoryAndReservations(
|
||||
fulfillments.filter((f) => !existingFulfillmentMap[f.id]),
|
||||
{
|
||||
inventoryService: pvInventoryServiceTx,
|
||||
locationId: validated.location_id,
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
@@ -117,6 +145,44 @@ export default async (req, res) => {
|
||||
res.json({ order })
|
||||
}
|
||||
|
||||
const updateInventoryAndReservations = async (
|
||||
fulfillments: Fulfillment[],
|
||||
context: {
|
||||
inventoryService: ProductVariantInventoryService
|
||||
locationId: string
|
||||
}
|
||||
) => {
|
||||
const { inventoryService, locationId } = context
|
||||
|
||||
fulfillments.map(async ({ items }) => {
|
||||
await inventoryService.validateInventoryAtLocation(
|
||||
items.map(({ item, quantity }) => ({ ...item, quantity } as LineItem)),
|
||||
locationId
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
items.map(async ({ item, quantity }) => {
|
||||
if (!item.variant_id) {
|
||||
return
|
||||
}
|
||||
|
||||
await inventoryService.adjustReservationsQuantityByLineItem(
|
||||
item.id,
|
||||
item.variant_id,
|
||||
locationId,
|
||||
-quantity
|
||||
)
|
||||
|
||||
await inventoryService.adjustInventory(
|
||||
item.variant_id,
|
||||
locationId,
|
||||
-quantity
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @schema AdminPostOrdersOrderFulfillmentsReq
|
||||
* type: object
|
||||
@@ -150,6 +216,10 @@ export class AdminPostOrdersOrderFulfillmentsReq {
|
||||
@Type(() => Item)
|
||||
items: Item[]
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
location_id?: string
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => optionalBooleanMapper.get(value))
|
||||
|
||||
Reference in New Issue
Block a user