feat(medusa): Add inventory for variants (#2970)

* initial get-inventory impl

* add inventory management to create-variant

* update create-variant endpoint

* move if statement

* use middleware for module checking

* add endpoint to medusa-js

* export return type from get-inventory

* add jsdoc

* revert create variant

* rename variable

* initial setInventoryPattern for variant and product endpoints

* remove cache

* sort imports

* add sales channel info to inventory calculations

* add missing import

* remove promise.all from single promise

* update oas

* initial feedback

* checkout joinLevels from develop

* add variant middleware

* add comments
This commit is contained in:
Philip Korsholm
2023-01-24 11:10:33 +01:00
committed by GitHub
parent 09dc9c6677
commit 8f4c84121b
13 changed files with 495 additions and 17 deletions
@@ -0,0 +1,180 @@
import {
InventoryItemDTO,
InventoryLevelDTO,
} from "../../../../types/inventory"
import ProductVariantInventoryService from "../../../../services/product-variant-inventory"
import {
SalesChannelLocationService,
SalesChannelService,
} from "../../../../services"
import { SalesChannel } from "../../../../models"
import { IInventoryService } from "../../../../interfaces"
import ProductVariantService from "../../../../services/product-variant"
import { joinLevels } from "../inventory-items/utils/join-levels"
/**
* @oas [get] /variants/{id}/inventory
* operationId: "GetVariantsVariantInventory"
* summary: "Get inventory of Variant."
* description: "Returns the available inventory of a Variant."
* x-authenticated: true
* parameters:
* - (path) id {string} The Product Variant id to get inventory for.
* x-codeSamples:
* - lang: JavaScript
* label: JS Client
* source: |
* import Medusa from "@medusajs/medusa-js"
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
* // must be previously logged in or use api token
* medusa.admin.variants.list()
* .then(({ variants, limit, offset, count }) => {
* console.log(variants.length)
* })
* - lang: Shell
* label: cURL
* source: |
* curl --location --request GET 'https://medusa-url.com/admin/variants' \
* --header 'Authorization: Bearer {api_token}'
* security:
* - api_token: []
* - cookie_auth: []
* tags:
* - Product Variant
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* variant:
* type: object
* $ref: "#/components/schemas/AdminGetVariantsVariantInventoryRes"
* "400":
* $ref: "#/components/responses/400_error"
* "401":
* $ref: "#/components/responses/unauthorized"
* "404":
* $ref: "#/components/responses/not_found_error"
* "409":
* $ref: "#/components/responses/invalid_state_error"
* "422":
* $ref: "#/components/responses/invalid_request_error"
* "500":
* $ref: "#/components/responses/500_error"
*/
export default async (req, res) => {
const { id } = req.params
const inventoryService: IInventoryService =
req.scope.resolve("inventoryService")
const channelLocationService: SalesChannelLocationService = req.scope.resolve(
"salesChannelLocationService"
)
const channelService: SalesChannelService = req.scope.resolve(
"salesChannelService"
)
const productVariantInventoryService: ProductVariantInventoryService =
req.scope.resolve("productVariantInventoryService")
const variantService: ProductVariantService = req.scope.resolve(
"productVariantService"
)
const variant = await variantService.retrieve(id, { select: ["id"] })
const responseVariant: AdminGetVariantsVariantInventoryRes = {
id: variant.id,
inventory: [],
sales_channel_availability: [],
}
const [rawChannels] = await channelService.listAndCount({})
const channels: SalesChannelDTO[] = await Promise.all(
rawChannels.map(async (channel) => {
const locations = await channelLocationService.listLocations(channel.id)
return {
...channel,
locations,
}
})
)
const inventory =
await productVariantInventoryService.listInventoryItemsByVariant(variant.id)
responseVariant.inventory = await joinLevels(inventory, [], inventoryService)
// TODO: adjust for required quantity
if (inventory.length) {
responseVariant.sales_channel_availability = await Promise.all(
channels.map(async (channel) => {
if (!channel.locations.length) {
return {
channel_name: channel.name as string,
channel_id: channel.id as string,
available_quantity: 0,
}
}
const quantity = await inventoryService.retrieveAvailableQuantity(
inventory[0].id,
channel.locations
)
return {
channel_name: channel.name as string,
channel_id: channel.id as string,
available_quantity: quantity,
}
})
)
}
res.json({
variant: responseVariant,
})
}
type SalesChannelDTO = Omit<SalesChannel, "beforeInsert"> & {
locations: string[]
}
type ResponseInventoryItem = Partial<InventoryItemDTO> & {
location_levels?: InventoryLevelDTO[]
}
/**
* @schema AdminGetVariantsVariantInventoryRes
* type: object
* properties:
* id:
* description: the id of the variant
* type: string
* inventory:
* description: the stock location address ID
* type: string
* sales_channel_availability:
* type: object
* description: An optional key-value map with additional details
* properties:
* channel_name:
* description: Sales channel name
* type: string
* channel_id:
* description: Sales channel id
* type: string
* available_quantity:
* description: Available quantity in sales channel
* type: number
*/
export type AdminGetVariantsVariantInventoryRes = {
id: string
inventory: ResponseInventoryItem[]
sales_channel_availability: {
channel_name: string
channel_id: string
available_quantity: number
}[]
}
@@ -4,6 +4,7 @@ import { ProductVariant } from "../../../../models/product-variant"
import { PaginatedResponse } from "../../../../types/common"
import { PricedVariant } from "../../../../types/pricing"
import middlewares, { transformQuery } from "../../../middlewares"
import { checkRegisteredModules } from "../../../middlewares/check-registered-modules"
import { AdminGetVariantsParams } from "./list-variants"
const route = Router()
@@ -21,6 +22,15 @@ export default (app) => {
middlewares.wrap(require("./list-variants").default)
)
route.get(
"/:id/inventory",
checkRegisteredModules({
inventoryService:
"Inventory is not enabled. Please add an Inventory module to enable this functionality.",
}),
middlewares.wrap(require("./get-inventory").default)
)
return app
}
@@ -72,3 +82,4 @@ export type AdminVariantsListRes = PaginatedResponse & {
}
export * from "./list-variants"
export * from "./get-inventory"