feat: inventory items api (#2971)
What: Admin endpoints to handle inventory items and their stock levels per location FIXES: CORE-975 Co-authored-by: Sebastian Rindom <7554214+srindom@users.noreply.github.com>
This commit is contained in:
1 parent
7152073b00
commit
f65f590a27
21 files changed
+1671
-15
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/inventory": patch
|
||||||
|
"@medusajs/medusa": minor
|
||||||
|
"@medusajs/medusa-js": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Adding inventory items api
|
||||||
@@ -139,7 +139,6 @@ export default class InventoryLevelService extends TransactionBaseService {
|
|||||||
* Updates an existing inventory level.
|
* Updates an existing inventory level.
|
||||||
* @param inventoryLevelId - The ID of the inventory level to update.
|
* @param inventoryLevelId - The ID of the inventory level to update.
|
||||||
* @param data - An object containing the properties to update on the inventory level.
|
* @param data - An object containing the properties to update on the inventory level.
|
||||||
* @param autoSave - A flag indicating whether to save the changes automatically.
|
|
||||||
* @return The updated inventory level.
|
* @return The updated inventory level.
|
||||||
* @throws If the inventory level ID is not defined or the given ID was not found.
|
* @throws If the inventory level ID is not defined or the given ID was not found.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import {
|
||||||
|
AdminGetInventoryItemsParams,
|
||||||
|
AdminInventoryItemsRes,
|
||||||
|
AdminPostInventoryItemsInventoryItemReq,
|
||||||
|
AdminGetInventoryItemsItemLocationLevelsParams,
|
||||||
|
AdminPostInventoryItemsItemLocationLevelsLevelReq,
|
||||||
|
AdminInventoryItemsDeleteRes,
|
||||||
|
AdminGetInventoryItemsItemParams,
|
||||||
|
AdminInventoryItemsListWithVariantsAndLocationLevelsRes,
|
||||||
|
AdminInventoryItemsLocationLevelsRes,
|
||||||
|
} from "@medusajs/medusa"
|
||||||
|
import { ResponsePromise } from "../../typings"
|
||||||
|
import BaseResource from "../base"
|
||||||
|
import qs from "qs"
|
||||||
|
|
||||||
|
class AdminInventoryItemsResource extends BaseResource {
|
||||||
|
/**
|
||||||
|
* Retrieve an Inventory Item
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please install @medusajs/inventory
|
||||||
|
* @description gets an Inventory Item
|
||||||
|
* @returns an Inventory Item
|
||||||
|
*/
|
||||||
|
retrieve(
|
||||||
|
inventoryItemId: string,
|
||||||
|
query?: AdminGetInventoryItemsItemParams,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminInventoryItemsRes> {
|
||||||
|
let path = `/admin/inventory-items/${inventoryItemId}`
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
const queryString = qs.stringify(query)
|
||||||
|
path += `?${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.request("GET", path, undefined, {}, customHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an Inventory Item
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please install @medusajs/inventory
|
||||||
|
* @description updates an Inventory Item
|
||||||
|
* @returns the updated Inventory Item
|
||||||
|
*/
|
||||||
|
update(
|
||||||
|
inventoryItemId: string,
|
||||||
|
payload: AdminPostInventoryItemsInventoryItemReq,
|
||||||
|
query?: AdminGetInventoryItemsItemParams,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminInventoryItemsRes> {
|
||||||
|
let path = `/admin/inventory-items/${inventoryItemId}`
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
const queryString = qs.stringify(query)
|
||||||
|
path += `?${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an Inventory Item
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please install @medusajs/inventory
|
||||||
|
* @description deletes an Inventory Item
|
||||||
|
* @returns the deleted Inventory Item
|
||||||
|
*/
|
||||||
|
delete(
|
||||||
|
inventoryItemId: string,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminInventoryItemsDeleteRes> {
|
||||||
|
const path = `/admin/inventory-items/${inventoryItemId}`
|
||||||
|
return this.client.request("DELETE", path, undefined, {}, customHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of Inventory Items
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please install @medusajs/inventory
|
||||||
|
* @description Retrieve a list of Inventory Items
|
||||||
|
* @returns the list of Inventory Items as well as the pagination properties
|
||||||
|
*/
|
||||||
|
list(
|
||||||
|
query?: AdminGetInventoryItemsParams,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminInventoryItemsListWithVariantsAndLocationLevelsRes> {
|
||||||
|
let path = `/admin/inventory-items`
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
const queryString = qs.stringify(query)
|
||||||
|
path += `?${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.request("GET", path, undefined, {}, customHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an Inventory Item's stock level at a Stock Location
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please install @medusajs/inventory
|
||||||
|
* @description updates an Inventory Item
|
||||||
|
* @returns the updated Inventory Item
|
||||||
|
*/
|
||||||
|
updateLocationLevel(
|
||||||
|
inventoryItemId: string,
|
||||||
|
locationId: string,
|
||||||
|
payload: AdminPostInventoryItemsItemLocationLevelsLevelReq,
|
||||||
|
query?: AdminGetInventoryItemsParams,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminInventoryItemsRes> {
|
||||||
|
let path = `/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
const queryString = qs.stringify(query)
|
||||||
|
path += `?${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an Inventory Item from a Stock Location. This erases trace of any quantity currently at the location.
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please install @medusajs/inventory
|
||||||
|
* @description deletes a location level of an Inventory Item
|
||||||
|
* @returns the Inventory Item
|
||||||
|
*/
|
||||||
|
deleteLocationLevel(
|
||||||
|
inventoryItemId: string,
|
||||||
|
locationId: string,
|
||||||
|
query?: AdminGetInventoryItemsParams,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminInventoryItemsRes> {
|
||||||
|
let path = `/admin/inventory-items/${inventoryItemId}/location-levels/${locationId}`
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
const queryString = qs.stringify(query)
|
||||||
|
path += `?${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.request("DELETE", path, undefined, {}, customHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of Inventory Levels related to an Inventory Item across Stock Locations
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please install @medusajs/inventory
|
||||||
|
* @description Retrieve a list of location levels related to an Inventory Item
|
||||||
|
* @returns the list of inventory levels related to an Inventory Item as well as the pagination properties
|
||||||
|
*/
|
||||||
|
listLocationLevels(
|
||||||
|
inventoryItemId: string,
|
||||||
|
query?: AdminGetInventoryItemsItemLocationLevelsParams,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminInventoryItemsLocationLevelsRes> {
|
||||||
|
let path = `/admin/inventory-items/${inventoryItemId}`
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
const queryString = qs.stringify(query)
|
||||||
|
path += `?${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.request("GET", path, undefined, {}, customHeaders)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminInventoryItemsResource
|
||||||
@@ -4,17 +4,19 @@ import {
|
|||||||
AdminPostStockLocationsLocationReq,
|
AdminPostStockLocationsLocationReq,
|
||||||
AdminPostStockLocationsReq,
|
AdminPostStockLocationsReq,
|
||||||
AdminStockLocationsListRes,
|
AdminStockLocationsListRes,
|
||||||
|
AdminStockLocationsDeleteRes,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { ResponsePromise } from "../../typings"
|
import { ResponsePromise } from "../../typings"
|
||||||
import BaseResource from "../base"
|
import BaseResource from "../base"
|
||||||
import qs from "qs"
|
import qs from "qs"
|
||||||
|
|
||||||
class AdminStockLocationsResource extends BaseResource {
|
class AdminStockLocationsResource extends BaseResource {
|
||||||
/** retrieve an stock location
|
/**
|
||||||
|
* Create a Stock Location
|
||||||
* @experimental This feature is under development and may change in the future.
|
* @experimental This feature is under development and may change in the future.
|
||||||
* To use this feature please install @medusajs/stock-location
|
* To use this feature please install @medusajs/stock-location
|
||||||
* @description gets a medusa stock location
|
* @description gets a medusa Stock Location
|
||||||
* @returns a medusa stock location
|
* @returns a medusa Stock Location
|
||||||
*/
|
*/
|
||||||
create(
|
create(
|
||||||
payload: AdminPostStockLocationsReq,
|
payload: AdminPostStockLocationsReq,
|
||||||
@@ -24,11 +26,12 @@ class AdminStockLocationsResource extends BaseResource {
|
|||||||
return this.client.request("POST", path, payload, {}, customHeaders)
|
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** retrieve an stock location
|
/**
|
||||||
|
* Retrieve a Stock Location
|
||||||
* @experimental This feature is under development and may change in the future.
|
* @experimental This feature is under development and may change in the future.
|
||||||
* To use this feature please install @medusajs/stock-location
|
* To use this feature please install @medusajs/stock-location
|
||||||
* @description gets a medusa stock location
|
* @description gets a medusa Stock Location
|
||||||
* @returns a medusa stock location
|
* @returns a medusa Stock Location
|
||||||
*/
|
*/
|
||||||
retrieve(
|
retrieve(
|
||||||
itemId: string,
|
itemId: string,
|
||||||
@@ -38,11 +41,12 @@ class AdminStockLocationsResource extends BaseResource {
|
|||||||
return this.client.request("GET", path, undefined, {}, customHeaders)
|
return this.client.request("GET", path, undefined, {}, customHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** update an stock location
|
/**
|
||||||
|
* Update a Stock Location
|
||||||
* @experimental This feature is under development and may change in the future.
|
* @experimental This feature is under development and may change in the future.
|
||||||
* To use this feature please install @medusajs/stock-location
|
* To use this feature please install @medusajs/stock-location
|
||||||
* @description updates an stock location
|
* @description updates a Stock Location
|
||||||
* @returns the updated medusa stock location
|
* @returns the updated medusa Stock Location
|
||||||
*/
|
*/
|
||||||
update(
|
update(
|
||||||
stockLocationId: string,
|
stockLocationId: string,
|
||||||
@@ -54,11 +58,25 @@ class AdminStockLocationsResource extends BaseResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a list of stock locations
|
* Delete a Stock Location
|
||||||
* @experimental This feature is under development and may change in the future.
|
* @experimental This feature is under development and may change in the future.
|
||||||
* To use this feature please install @medusajs/stock-location
|
* To use this feature please install @medusajs/stock-location
|
||||||
* @description Retrieve a list of stock locations
|
* @description deletes a Stock Location
|
||||||
* @returns the list of stock locations as well as the pagination properties
|
*/
|
||||||
|
delete(
|
||||||
|
id: string,
|
||||||
|
customHeaders: Record<string, any> = {}
|
||||||
|
): ResponsePromise<AdminStockLocationsDeleteRes> {
|
||||||
|
const path = `/admin/stock-locations/${id}`
|
||||||
|
return this.client.request("DELETE", path, undefined, {}, customHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of Stock Locations
|
||||||
|
* @experimental This feature is under development and may change in the future.
|
||||||
|
* To use this feature please install @medusajs/stock-location
|
||||||
|
* @description Retrieve a list of Stock Locations
|
||||||
|
* @returns the list of Stock Locations as well as the pagination properties
|
||||||
*/
|
*/
|
||||||
list(
|
list(
|
||||||
query?: AdminGetStockLocationsParams,
|
query?: AdminGetStockLocationsParams,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export * from "./routes/admin/customers"
|
|||||||
export * from "./routes/admin/discounts"
|
export * from "./routes/admin/discounts"
|
||||||
export * from "./routes/admin/draft-orders"
|
export * from "./routes/admin/draft-orders"
|
||||||
export * from "./routes/admin/gift-cards"
|
export * from "./routes/admin/gift-cards"
|
||||||
|
export * from "./routes/admin/inventory-items"
|
||||||
export * from "./routes/admin/invites"
|
export * from "./routes/admin/invites"
|
||||||
export * from "./routes/admin/notes"
|
export * from "./routes/admin/notes"
|
||||||
export * from "./routes/admin/notifications"
|
export * from "./routes/admin/notifications"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import customerRoutes from "./customers"
|
|||||||
import discountRoutes from "./discounts"
|
import discountRoutes from "./discounts"
|
||||||
import draftOrderRoutes from "./draft-orders"
|
import draftOrderRoutes from "./draft-orders"
|
||||||
import giftCardRoutes from "./gift-cards"
|
import giftCardRoutes from "./gift-cards"
|
||||||
|
import inventoryItemRoutes from "./inventory-items"
|
||||||
import inviteRoutes, { unauthenticatedInviteRoutes } from "./invites"
|
import inviteRoutes, { unauthenticatedInviteRoutes } from "./invites"
|
||||||
import noteRoutes from "./notes"
|
import noteRoutes from "./notes"
|
||||||
import notificationRoutes from "./notifications"
|
import notificationRoutes from "./notifications"
|
||||||
@@ -84,6 +85,7 @@ export default (app, container, config) => {
|
|||||||
discountRoutes(route)
|
discountRoutes(route)
|
||||||
draftOrderRoutes(route)
|
draftOrderRoutes(route)
|
||||||
giftCardRoutes(route)
|
giftCardRoutes(route)
|
||||||
|
inventoryItemRoutes(route)
|
||||||
inviteRoutes(route)
|
inviteRoutes(route)
|
||||||
noteRoutes(route)
|
noteRoutes(route)
|
||||||
notificationRoutes(route)
|
notificationRoutes(route)
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { Request, Response } from "express"
|
||||||
|
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||||
|
|
||||||
|
import {
|
||||||
|
IInventoryService,
|
||||||
|
IStockLocationService,
|
||||||
|
} from "../../../../interfaces"
|
||||||
|
import { FindParams } from "../../../../types/common"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [post] /inventory-items/{id}/location-levels
|
||||||
|
* operationId: "PostInventoryItemsInventoryItemLocationLevels"
|
||||||
|
* summary: "Create an Inventory Location Level for a given Inventory Item."
|
||||||
|
* description: "Creates an Inventory Location Level for a given Inventory Item."
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The ID of the Inventory Item.
|
||||||
|
* - (query) expand {string} Comma separated list of relations to include in the results.
|
||||||
|
* - (query) fields {string} Comma separated list of fields to include in the results.
|
||||||
|
* requestBody:
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminPostInventoryItemsItemLocationLevelsReq"
|
||||||
|
* 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.inventoryItems.createLocationLevel(inventoryItemId, {
|
||||||
|
* location_id: 'sloc',
|
||||||
|
* stocked_quantity: 10,
|
||||||
|
* })
|
||||||
|
* .then(({ inventory_item }) => {
|
||||||
|
* console.log(inventory_item.id);
|
||||||
|
* });
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request POST 'https://medusa-url.com/admin/inventory-items/{id}/location-levels' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}' \
|
||||||
|
* --header 'Content-Type: application/json' \
|
||||||
|
* --data-raw '{
|
||||||
|
* "location_id": "sloc",
|
||||||
|
* "stocked_quantity": 10
|
||||||
|
* }'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - Inventory Items
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminInventoryItemsRes"
|
||||||
|
* "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: Request, res: Response) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
req.scope.resolve("inventoryService")
|
||||||
|
|
||||||
|
const stockLocationService: IStockLocationService | undefined =
|
||||||
|
req.scope.resolve("stockLocationService")
|
||||||
|
|
||||||
|
const validatedBody =
|
||||||
|
req.validatedBody as AdminPostInventoryItemsItemLocationLevelsReq
|
||||||
|
|
||||||
|
const location_id = validatedBody.location_id
|
||||||
|
if (stockLocationService) {
|
||||||
|
// will throw an error if not found
|
||||||
|
await stockLocationService.retrieve(location_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventoryService.createInventoryLevel({
|
||||||
|
inventory_item_id: id,
|
||||||
|
location_id,
|
||||||
|
stocked_quantity: validatedBody.stocked_quantity,
|
||||||
|
incoming_quantity: validatedBody.incoming_quantity,
|
||||||
|
})
|
||||||
|
|
||||||
|
const inventoryItem = await inventoryService.retrieveInventoryItem(
|
||||||
|
id,
|
||||||
|
req.retrieveConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ inventory_item: inventoryItem })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminPostInventoryItemsItemLocationLevelsReq
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - location_id
|
||||||
|
* - stocked_quantity
|
||||||
|
* properties:
|
||||||
|
* location_id:
|
||||||
|
* description: the item location ID
|
||||||
|
* type: string
|
||||||
|
* stocked_quantity:
|
||||||
|
* description: the stock quantity of an inventory item at the given location ID
|
||||||
|
* type: number
|
||||||
|
* incoming_quantity:
|
||||||
|
* description: the incoming stock quantity of an inventory item at the given location ID
|
||||||
|
* type: number
|
||||||
|
*/
|
||||||
|
export class AdminPostInventoryItemsItemLocationLevelsReq {
|
||||||
|
@IsString()
|
||||||
|
location_id: string
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
stocked_quantity: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
incoming_quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export class AdminPostInventoryItemsItemLocationLevelsParams extends FindParams {}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Request, Response } from "express"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { IInventoryService } from "../../../../interfaces"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [delete] /inventory-items/{id}
|
||||||
|
* operationId: "DeleteInventoryItemsInventoryItem"
|
||||||
|
* summary: "Delete an Inventory Item"
|
||||||
|
* description: "Delete an Inventory Item"
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The ID of the Inventory Item to delete.
|
||||||
|
* 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.inventoryItems.delete(inventoryItemId)
|
||||||
|
* .then(({ id, object, deleted }) => {
|
||||||
|
* console.log(id)
|
||||||
|
* })
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request DELETE 'https://medusa-url.com/admin/inventory-items/{id}' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - InventoryItem
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminInventoryItemsDeleteRes"
|
||||||
|
* "400":
|
||||||
|
* $ref: "#/components/responses/400_error"
|
||||||
|
*/
|
||||||
|
export default async (req: Request, res: Response) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
req.scope.resolve("inventoryService")
|
||||||
|
|
||||||
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
await manager.transaction(async (transactionManager) => {
|
||||||
|
await inventoryService
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.deleteInventoryItem(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(200).send({
|
||||||
|
id,
|
||||||
|
object: "inventory_item",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { Request, Response } from "express"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { IInventoryService } from "../../../../interfaces"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [delete] /inventory-items/{id}/location-levels/{location_id}
|
||||||
|
* operationId: "DeleteInventoryItemsInventoryIteLocationLevelsLocation"
|
||||||
|
* summary: "Delete a location level of an Inventory Item."
|
||||||
|
* description: "Delete a location level of an Inventory Item."
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The ID of the Inventory Item.
|
||||||
|
* - (path) location_id=* {string} The ID of the location.
|
||||||
|
* - (query) expand {string} Comma separated list of relations to include in the results.
|
||||||
|
* - (query) fields {string} Comma separated list of fields to include in the results.
|
||||||
|
* 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.inventoryItems.deleteLocationLevel(inventoryItemId, locationId)
|
||||||
|
* .then(({ inventory_item }) => {
|
||||||
|
* console.log(inventory_item.id);
|
||||||
|
* });
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request DELETE 'https://medusa-url.com/admin/inventory-items/{id}/location-levels/{location_id}' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}' \
|
||||||
|
* --header 'Content-Type: application/json'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - Inventory Items
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminInventoryItemsRes"
|
||||||
|
* "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: Request, res: Response) => {
|
||||||
|
const { id, location_id } = req.params
|
||||||
|
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
req.scope.resolve("inventoryService")
|
||||||
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
|
||||||
|
const reservedQuantity = await inventoryService.retrieveReservedQuantity(id, [
|
||||||
|
location_id,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (reservedQuantity > 0) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Cannot remove Inventory Level ${id} at Location ${location_id} because there are reserved items.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await manager.transaction(async (transactionManager) => {
|
||||||
|
await inventoryService
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.deleteInventoryLevel(id, location_id)
|
||||||
|
})
|
||||||
|
|
||||||
|
const inventoryItem = await inventoryService.retrieveInventoryItem(
|
||||||
|
id,
|
||||||
|
req.retrieveConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ inventory_item: inventoryItem })
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { IInventoryService } from "../../../../interfaces"
|
||||||
|
import { Request, Response } from "express"
|
||||||
|
import { FindParams } from "../../../../types/common"
|
||||||
|
import { joinLevels } from "./utils/join-levels"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [get] /inventory-items/{id}
|
||||||
|
* operationId: "GetInventoryItemsInventoryItem"
|
||||||
|
* summary: "Retrive an Inventory Item."
|
||||||
|
* description: "Retrives an Inventory Item."
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The ID of the Inventory Item.
|
||||||
|
* - (query) expand {string} Comma separated list of relations to include in the results.
|
||||||
|
* - (query) fields {string} Comma separated list of fields to include in the results.
|
||||||
|
* 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.inventoryItems.retrieve(inventoryItemId)
|
||||||
|
* .then(({ inventory_item }) => {
|
||||||
|
* console.log(inventory_item.id);
|
||||||
|
* });
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request GET 'https://medusa-url.com/admin/inventory-items/{id}' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}' \
|
||||||
|
* --header 'Content-Type: application/json'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - Inventory Items
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminInventoryItemsRes"
|
||||||
|
* "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: Request, res: Response) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
req.scope.resolve("inventoryService")
|
||||||
|
|
||||||
|
const inventoryItem = await inventoryService.retrieveInventoryItem(
|
||||||
|
id,
|
||||||
|
req.retrieveConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
const [data] = await joinLevels([inventoryItem], [], inventoryService)
|
||||||
|
|
||||||
|
res.status(200).json({ inventory_item: data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminGetInventoryItemsItemParams extends FindParams {}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
import { Router } from "express"
|
||||||
|
import "reflect-metadata"
|
||||||
|
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||||
|
import {
|
||||||
|
InventoryItemDTO,
|
||||||
|
InventoryLevelDTO,
|
||||||
|
} from "../../../../types/inventory"
|
||||||
|
import middlewares, {
|
||||||
|
transformBody,
|
||||||
|
transformQuery,
|
||||||
|
} from "../../../middlewares"
|
||||||
|
import { AdminGetInventoryItemsParams } from "./list-inventory-items"
|
||||||
|
import { AdminGetInventoryItemsItemParams } from "./get-inventory-item"
|
||||||
|
import { AdminPostInventoryItemsInventoryItemReq } from "./update-inventory-item"
|
||||||
|
import { AdminGetInventoryItemsItemLocationLevelsParams } from "./list-location-levels"
|
||||||
|
import {
|
||||||
|
AdminPostInventoryItemsItemLocationLevelsReq,
|
||||||
|
AdminPostInventoryItemsItemLocationLevelsParams,
|
||||||
|
} from "./create-location-level"
|
||||||
|
import {
|
||||||
|
AdminPostInventoryItemsItemLocationLevelsLevelReq,
|
||||||
|
AdminPostInventoryItemsItemLocationLevelsLevelParams,
|
||||||
|
} from "./update-location-level"
|
||||||
|
import { checkRegisteredModules } from "../../../middlewares/check-registered-modules"
|
||||||
|
import { ProductVariant } from "../../../../models"
|
||||||
|
|
||||||
|
const route = Router()
|
||||||
|
|
||||||
|
export default (app) => {
|
||||||
|
app.use(
|
||||||
|
"/inventory-items",
|
||||||
|
checkRegisteredModules({
|
||||||
|
inventoryService:
|
||||||
|
"Inventory is not enabled. Please add an Inventory module to enable this functionality.",
|
||||||
|
}),
|
||||||
|
route
|
||||||
|
)
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
"/",
|
||||||
|
transformQuery(AdminGetInventoryItemsParams, {
|
||||||
|
defaultFields: defaultAdminInventoryItemFields,
|
||||||
|
defaultRelations: defaultAdminInventoryItemRelations,
|
||||||
|
isList: true,
|
||||||
|
}),
|
||||||
|
middlewares.wrap(require("./list-inventory-items").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id",
|
||||||
|
transformQuery(AdminGetInventoryItemsItemParams, {
|
||||||
|
defaultFields: defaultAdminInventoryItemFields,
|
||||||
|
defaultRelations: defaultAdminInventoryItemRelations,
|
||||||
|
isList: false,
|
||||||
|
}),
|
||||||
|
transformBody(AdminPostInventoryItemsInventoryItemReq),
|
||||||
|
middlewares.wrap(require("./update-inventory-item").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
"/:id",
|
||||||
|
middlewares.wrap(require("./delete-inventory-item").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/location-levels",
|
||||||
|
transformQuery(AdminPostInventoryItemsItemLocationLevelsParams, {
|
||||||
|
defaultFields: defaultAdminInventoryItemFields,
|
||||||
|
defaultRelations: defaultAdminInventoryItemRelations,
|
||||||
|
isList: false,
|
||||||
|
}),
|
||||||
|
transformBody(AdminPostInventoryItemsItemLocationLevelsReq),
|
||||||
|
middlewares.wrap(require("./create-location-level").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
"/:id/location-levels",
|
||||||
|
transformQuery(AdminGetInventoryItemsItemLocationLevelsParams, {
|
||||||
|
defaultFields: defaultAdminInventoryItemFields,
|
||||||
|
defaultRelations: defaultAdminInventoryItemRelations,
|
||||||
|
isList: false,
|
||||||
|
}),
|
||||||
|
middlewares.wrap(require("./list-location-levels").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
"/:id/location-levels/:location_id",
|
||||||
|
middlewares.wrap(require("./delete-location-level").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/location-levels/:location_id",
|
||||||
|
transformQuery(AdminPostInventoryItemsItemLocationLevelsLevelParams, {
|
||||||
|
defaultFields: defaultAdminInventoryItemFields,
|
||||||
|
defaultRelations: defaultAdminInventoryItemRelations,
|
||||||
|
isList: false,
|
||||||
|
}),
|
||||||
|
transformBody(AdminPostInventoryItemsItemLocationLevelsLevelReq),
|
||||||
|
middlewares.wrap(require("./update-location-level").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
"/:id",
|
||||||
|
transformQuery(AdminGetInventoryItemsItemParams, {
|
||||||
|
defaultFields: defaultAdminInventoryItemFields,
|
||||||
|
defaultRelations: defaultAdminInventoryItemRelations,
|
||||||
|
isList: false,
|
||||||
|
}),
|
||||||
|
middlewares.wrap(require("./get-inventory-item").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultAdminInventoryItemFields: (keyof InventoryItemDTO)[] = [
|
||||||
|
"id",
|
||||||
|
"sku",
|
||||||
|
"origin_country",
|
||||||
|
"hs_code",
|
||||||
|
"requires_shipping",
|
||||||
|
"mid_code",
|
||||||
|
"material",
|
||||||
|
"weight",
|
||||||
|
"length",
|
||||||
|
"height",
|
||||||
|
"width",
|
||||||
|
"metadata",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const defaultAdminInventoryItemRelations = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminInventoryItemsRes
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* inventory_item:
|
||||||
|
* $ref: "#/components/schemas/InventoryItemDTO"
|
||||||
|
*/
|
||||||
|
export type AdminInventoryItemsRes = {
|
||||||
|
inventory_item: InventoryItemDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminInventoryItemsDeleteRes
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: string
|
||||||
|
* description: The ID of the deleted Inventory Item.
|
||||||
|
* object:
|
||||||
|
* type: string
|
||||||
|
* description: The type of the object that was deleted.
|
||||||
|
* format: inventory_item
|
||||||
|
* deleted:
|
||||||
|
* type: boolean
|
||||||
|
* description: Whether or not the Inventory Item was deleted.
|
||||||
|
* default: true
|
||||||
|
*/
|
||||||
|
export type AdminInventoryItemsDeleteRes = DeleteResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminInventoryItemsListRes
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* inventory_items:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* $ref: "#/components/schemas/InventoryItemDTO"
|
||||||
|
* count:
|
||||||
|
* type: integer
|
||||||
|
* description: The total number of items available
|
||||||
|
* offset:
|
||||||
|
* type: integer
|
||||||
|
* description: The number of items skipped before these items
|
||||||
|
* limit:
|
||||||
|
* type: integer
|
||||||
|
* description: The number of items per page
|
||||||
|
*/
|
||||||
|
export type AdminInventoryItemsListRes = PaginatedResponse & {
|
||||||
|
inventory_items: InventoryItemDTO[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminInventoryItemsListWithVariantsAndLocationLevelsRes
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* inventory_items:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* allOf:
|
||||||
|
* - $ref: "#/components/schemas/InventoryItemDTO"
|
||||||
|
* - type: object
|
||||||
|
* properties:
|
||||||
|
* location_levels:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* allOf:
|
||||||
|
* - $ref: "#/components/schemas/InventoryLevelDTO"
|
||||||
|
* variants:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* allOf:
|
||||||
|
* - $ref: "#/components/schemas/ProductVariant"
|
||||||
|
* count:
|
||||||
|
* type: integer
|
||||||
|
* description: The total number of items available
|
||||||
|
* offset:
|
||||||
|
* type: integer
|
||||||
|
* description: The number of items skipped before these items
|
||||||
|
* limit:
|
||||||
|
* type: integer
|
||||||
|
* description: The number of items per page
|
||||||
|
*/
|
||||||
|
export type AdminInventoryItemsListWithVariantsAndLocationLevelsRes =
|
||||||
|
Partial<InventoryItemDTO> & {
|
||||||
|
location_levels?: InventoryLevelDTO[]
|
||||||
|
variants?: ProductVariant[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminInventoryItemsLocationLevelsRes
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* description: The id of the location
|
||||||
|
* location_levels:
|
||||||
|
* description: List of stock levels at a given location
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* $ref: "#/components/schemas/InventoryLevelDTO"
|
||||||
|
*/
|
||||||
|
export type AdminInventoryItemsLocationLevelsRes = {
|
||||||
|
inventory_item: {
|
||||||
|
id
|
||||||
|
location_levels: InventoryLevelDTO[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from "./list-inventory-items"
|
||||||
|
export * from "./get-inventory-item"
|
||||||
|
export * from "./update-inventory-item"
|
||||||
|
export * from "./list-location-levels"
|
||||||
|
export * from "./create-location-level"
|
||||||
|
export * from "./update-location-level"
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
import { Request, Response } from "express"
|
||||||
|
import { IsString, IsBoolean, IsOptional } from "class-validator"
|
||||||
|
import { Transform } from "class-transformer"
|
||||||
|
import { IsType } from "../../../../utils/validators/is-type"
|
||||||
|
import { getLevelsByInventoryItemId } from "./utils/join-levels"
|
||||||
|
import {
|
||||||
|
getVariantsByInventoryItemId,
|
||||||
|
InventoryItemsWithVariants,
|
||||||
|
} from "./utils/join-variants"
|
||||||
|
import {
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
ProductVariantService,
|
||||||
|
} from "../../../../services"
|
||||||
|
import { IInventoryService } from "../../../../interfaces"
|
||||||
|
import {
|
||||||
|
extendedFindParamsMixin,
|
||||||
|
StringComparisonOperator,
|
||||||
|
NumericalComparisonOperator,
|
||||||
|
} from "../../../../types/common"
|
||||||
|
import { AdminInventoryItemsListWithVariantsAndLocationLevelsRes } from "."
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [get] /inventory-items
|
||||||
|
* operationId: "GetInventoryItems"
|
||||||
|
* summary: "List inventory items."
|
||||||
|
* description: "Lists inventory items."
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (query) offset=0 {integer} How many inventory items to skip in the result.
|
||||||
|
* - (query) limit=20 {integer} Limit the number of inventory items returned.
|
||||||
|
* - (query) expand {string} Comma separated list of relations to include in the results.
|
||||||
|
* - (query) fields {string} Comma separated list of fields to include in the results.
|
||||||
|
* - (query) q {string} Query used for searching product inventory items and their properties.
|
||||||
|
* - in: query
|
||||||
|
* name: location_id
|
||||||
|
* style: form
|
||||||
|
* explode: false
|
||||||
|
* description: Locations ids to search for.
|
||||||
|
* schema:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: string
|
||||||
|
* - (query) id {string} id to search for.
|
||||||
|
* - (query) sku {string} sku to search for.
|
||||||
|
* - (query) origin_country {string} origin_country to search for.
|
||||||
|
* - (query) mid_code {string} mid_code to search for.
|
||||||
|
* - (query) material {string} material to search for.
|
||||||
|
* - (query) hs_code {string} hs_code to search for.
|
||||||
|
* - (query) weight {string} weight to search for.
|
||||||
|
* - (query) length {string} length to search for.
|
||||||
|
* - (query) height {string} height to search for.
|
||||||
|
* - (query) width {string} width to search for.
|
||||||
|
* - (query) requires_shipping {string} requires_shipping to search 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.inventoryItems.list()
|
||||||
|
* .then(({ inventory_items }) => {
|
||||||
|
* console.log(inventory_items.length);
|
||||||
|
* });
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request GET 'https://medusa-url.com/admin/inventory-items' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}' \
|
||||||
|
* --header 'Content-Type: application/json'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - Inventory Items
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminInventoryItemsListWithVariantsAndLocationLevelsRes"
|
||||||
|
* "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: Request, res: Response) => {
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
req.scope.resolve("inventoryService")
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
const productVariantService: ProductVariantService = req.scope.resolve(
|
||||||
|
"productVariantService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const { filterableFields, listConfig } = req
|
||||||
|
const { skip, take } = listConfig
|
||||||
|
|
||||||
|
let locationIds: string[] = []
|
||||||
|
|
||||||
|
if (filterableFields.location_id) {
|
||||||
|
locationIds = Array.isArray(filterableFields.location_id)
|
||||||
|
? filterableFields.location_id
|
||||||
|
: [filterableFields.location_id]
|
||||||
|
}
|
||||||
|
|
||||||
|
const [inventoryItems, count] = await inventoryService.listInventoryItems(
|
||||||
|
filterableFields,
|
||||||
|
listConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
const levelsByItemId = await getLevelsByInventoryItemId(
|
||||||
|
inventoryItems,
|
||||||
|
locationIds,
|
||||||
|
inventoryService
|
||||||
|
)
|
||||||
|
|
||||||
|
const variantsByInventoryItemId: InventoryItemsWithVariants =
|
||||||
|
await getVariantsByInventoryItemId(
|
||||||
|
inventoryItems,
|
||||||
|
productVariantInventoryService,
|
||||||
|
productVariantService
|
||||||
|
)
|
||||||
|
|
||||||
|
const inventoryItemsWithVariantsAndLocationLevels = inventoryItems.map(
|
||||||
|
(
|
||||||
|
inventoryItem
|
||||||
|
): AdminInventoryItemsListWithVariantsAndLocationLevelsRes => {
|
||||||
|
return {
|
||||||
|
...inventoryItem,
|
||||||
|
variants: variantsByInventoryItemId[inventoryItem.id] ?? [],
|
||||||
|
location_levels: levelsByItemId[inventoryItem.id] ?? [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
inventory_items: inventoryItemsWithVariantsAndLocationLevels,
|
||||||
|
count,
|
||||||
|
offset: skip,
|
||||||
|
limit: take,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminGetInventoryItemsParams extends extendedFindParamsMixin({
|
||||||
|
limit: 20,
|
||||||
|
offset: 0,
|
||||||
|
}) {
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
id?: string | string[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
q?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
location_id?: string | string[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
sku?: string | string[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
origin_country?: string | string[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
mid_code?: string | string[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String]])
|
||||||
|
material?: string | string[]
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([String, [String], StringComparisonOperator])
|
||||||
|
hs_code?: string | string[] | StringComparisonOperator
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([Number, NumericalComparisonOperator])
|
||||||
|
weight?: number | NumericalComparisonOperator
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([Number, NumericalComparisonOperator])
|
||||||
|
length?: number | NumericalComparisonOperator
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([Number, NumericalComparisonOperator])
|
||||||
|
height?: number | NumericalComparisonOperator
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsType([Number, NumericalComparisonOperator])
|
||||||
|
width?: number | NumericalComparisonOperator
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
@Transform(({ value }) => value === "true")
|
||||||
|
requires_shipping?: boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { Request, Response } from "express"
|
||||||
|
|
||||||
|
import { IInventoryService } from "../../../../interfaces"
|
||||||
|
import { FindParams } from "../../../../types/common"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [get] /inventory-items/{id}/location-levels
|
||||||
|
* operationId: "GetInventoryItemsInventoryItemLocationLevels"
|
||||||
|
* summary: "List stock levels of a given location."
|
||||||
|
* description: "Lists stock levels of a given location."
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The ID of the Inventory Item.
|
||||||
|
* - (query) offset=0 {integer} How many stock locations levels to skip in the result.
|
||||||
|
* - (query) limit=20 {integer} Limit the number of stock locations levels returned.
|
||||||
|
* - (query) expand {string} Comma separated list of relations to include in the results.
|
||||||
|
* - (query) fields {string} Comma separated list of fields to include in the results.
|
||||||
|
* 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.inventoryItems.listLocationLevels(inventoryItemId)
|
||||||
|
* .then(({ inventory_item }) => {
|
||||||
|
* console.log(inventory_item.location_levels);
|
||||||
|
* });
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request GET 'https://medusa-url.com/admin/inventory-items/{id}/location-levels' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}' \
|
||||||
|
* --header 'Content-Type: application/json'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - Inventory Items
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminInventoryItemsLocationLevelsRes"
|
||||||
|
* "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: Request, res: Response) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
req.scope.resolve("inventoryService")
|
||||||
|
|
||||||
|
const [levels] = await inventoryService.listInventoryLevels(
|
||||||
|
{
|
||||||
|
inventory_item_id: id,
|
||||||
|
},
|
||||||
|
req.retrieveConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
inventory_item: {
|
||||||
|
id,
|
||||||
|
location_levels: levels,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
export class AdminGetInventoryItemsItemLocationLevelsParams extends FindParams {}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import { Request, Response } from "express"
|
||||||
|
import { IsBoolean, IsNumber, IsOptional, IsString } from "class-validator"
|
||||||
|
|
||||||
|
import { IInventoryService } from "../../../../interfaces"
|
||||||
|
import { FindParams } from "../../../../types/common"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [post] /inventory-items/{id}
|
||||||
|
* operationId: "PostInventoryItemsInventoryItem"
|
||||||
|
* summary: "Update an Inventory Item."
|
||||||
|
* description: "Updates an Inventory Item."
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The ID of the Inventory Item.
|
||||||
|
* - (query) expand {string} Comma separated list of relations to include in the results.
|
||||||
|
* - (query) fields {string} Comma separated list of fields to include in the results.
|
||||||
|
* requestBody:
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminPostInventoryItemsInventoryItemReq"
|
||||||
|
* 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.inventoryItems.update(inventoryItemId, {
|
||||||
|
* origin_country: "US",
|
||||||
|
* })
|
||||||
|
* .then(({ inventory_item }) => {
|
||||||
|
* console.log(inventory_item.id);
|
||||||
|
* });
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request POST 'https://medusa-url.com/admin/inventory-items/{id}' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}' \
|
||||||
|
* --header 'Content-Type: application/json' \
|
||||||
|
* --data-raw '{
|
||||||
|
* "origin_country": "US"
|
||||||
|
* }'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - Inventory Items
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminInventoryItemsRes"
|
||||||
|
* "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: Request, res: Response) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
req.scope.resolve("inventoryService")
|
||||||
|
|
||||||
|
await inventoryService.updateInventoryItem(
|
||||||
|
id,
|
||||||
|
req.validatedBody as AdminPostInventoryItemsInventoryItemReq
|
||||||
|
)
|
||||||
|
|
||||||
|
const inventoryItem = await inventoryService.retrieveInventoryItem(
|
||||||
|
id,
|
||||||
|
req.retrieveConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ inventory_item: inventoryItem })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminPostInventoryItemsInventoryItemReq
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* hs_code:
|
||||||
|
* description: The Harmonized System code of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* origin_country:
|
||||||
|
* description: The country in which the Inventory Item was produced. May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* mid_code:
|
||||||
|
* description: The Manufacturers Identification code that identifies the manufacturer of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* material:
|
||||||
|
* description: The material and composition that the Inventory Item is made of, May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* weight:
|
||||||
|
* description: The weight of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* height:
|
||||||
|
* description: The height of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* width:
|
||||||
|
* description: The width of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* length:
|
||||||
|
* description: The length of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* requires_shipping:
|
||||||
|
* description: Whether the item requires shipping.
|
||||||
|
* type: boolean
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class AdminPostInventoryItemsInventoryItemReq {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
sku?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
origin_country?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
hs_code?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
mid_code?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
material?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
weight?: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
height?: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
length?: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
width?: number
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
requires_shipping?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostInventoryItemsInventoryItemParams extends FindParams {}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { Request, Response } from "express"
|
||||||
|
import { IsOptional, IsNumber } from "class-validator"
|
||||||
|
|
||||||
|
import { IInventoryService } from "../../../../interfaces"
|
||||||
|
import { FindParams } from "../../../../types/common"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [post] /inventory-items/{id}/location-levels/{location_id}
|
||||||
|
* operationId: "PostInventoryItemsInventoryItemLocationLevelsLocationLevel"
|
||||||
|
* summary: "Update an Inventory Location Level for a given Inventory Item."
|
||||||
|
* description: "Updates an Inventory Location Level for a given Inventory Item."
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The ID of the Inventory Item.
|
||||||
|
* - (path) location_id=* {string} The ID of the Location.
|
||||||
|
* - (query) expand {string} Comma separated list of relations to include in the results.
|
||||||
|
* - (query) fields {string} Comma separated list of fields to include in the results.
|
||||||
|
* requestBody:
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminPostInventoryItemsItemLocationLevelsLevelReq"
|
||||||
|
* 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.inventoryItems.updateLocationLevel(inventoryItemId, locationId, {
|
||||||
|
* stocked_quantity: 15,
|
||||||
|
* })
|
||||||
|
* .then(({ inventory_item }) => {
|
||||||
|
* console.log(inventory_item.id);
|
||||||
|
* });
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request POST 'https://medusa-url.com/admin/inventory-items/{id}/location-levels/{location_id}' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}' \
|
||||||
|
* --header 'Content-Type: application/json' \
|
||||||
|
* --data-raw '{
|
||||||
|
* "stocked_quantity": 15
|
||||||
|
* }'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - Inventory Items
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/AdminInventoryItemsRes"
|
||||||
|
* "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: Request, res: Response) => {
|
||||||
|
const { id, location_id } = req.params
|
||||||
|
|
||||||
|
const inventoryService: IInventoryService =
|
||||||
|
req.scope.resolve("inventoryService")
|
||||||
|
|
||||||
|
const validatedBody =
|
||||||
|
req.validatedBody as AdminPostInventoryItemsItemLocationLevelsLevelReq
|
||||||
|
|
||||||
|
await inventoryService.updateInventoryLevel(id, location_id, validatedBody)
|
||||||
|
|
||||||
|
const inventoryItem = await inventoryService.retrieveInventoryItem(
|
||||||
|
id,
|
||||||
|
req.retrieveConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ inventory_item: inventoryItem })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminPostInventoryItemsItemLocationLevelsLevelReq
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* stocked_quantity:
|
||||||
|
* description: the total stock quantity of an inventory item at the given location ID
|
||||||
|
* type: number
|
||||||
|
* incoming_quantity:
|
||||||
|
* description: the incoming stock quantity of an inventory item at the given location ID
|
||||||
|
* type: number
|
||||||
|
*/
|
||||||
|
export class AdminPostInventoryItemsItemLocationLevelsLevelReq {
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
incoming_quantity?: number
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
stocked_quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export class AdminPostInventoryItemsItemLocationLevelsLevelParams extends FindParams {}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { IInventoryService } from "../../../../../interfaces"
|
||||||
|
import {
|
||||||
|
InventoryItemDTO,
|
||||||
|
InventoryLevelDTO,
|
||||||
|
} from "../../../../../types/inventory"
|
||||||
|
|
||||||
|
type LevelWithAvailability = InventoryLevelDTO & {
|
||||||
|
available_quantity: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildLevelsByInventoryItemId = (
|
||||||
|
inventoryLevels: InventoryLevelDTO[],
|
||||||
|
locationIds: string[]
|
||||||
|
) => {
|
||||||
|
const filteredLevels = inventoryLevels.filter((level) =>
|
||||||
|
locationIds?.includes(level.location_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
return filteredLevels.reduce((acc, level) => {
|
||||||
|
acc[level.inventory_item_id] = acc[level.inventory_item_id] ?? []
|
||||||
|
acc[level.inventory_item_id].push(level)
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLevelsByInventoryItemId = async (
|
||||||
|
items: InventoryItemDTO[],
|
||||||
|
locationIds: string[],
|
||||||
|
inventoryService: IInventoryService
|
||||||
|
) => {
|
||||||
|
const [levels] = await inventoryService.listInventoryLevels({
|
||||||
|
inventory_item_id: items.map((inventoryItem) => inventoryItem.id),
|
||||||
|
})
|
||||||
|
|
||||||
|
const levelsWithAvailability: LevelWithAvailability[] = await Promise.all(
|
||||||
|
levels.map(async (level) => {
|
||||||
|
const availability = await inventoryService.retrieveAvailableQuantity(
|
||||||
|
level.inventory_item_id,
|
||||||
|
[level.location_id]
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
...level,
|
||||||
|
available_quantity: availability,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return buildLevelsByInventoryItemId(levelsWithAvailability, locationIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const joinLevels = async (
|
||||||
|
inventoryItems: InventoryItemDTO[],
|
||||||
|
locationIds: string[],
|
||||||
|
inventoryService: IInventoryService
|
||||||
|
) => {
|
||||||
|
const levelsByItemId = await getLevelsByInventoryItemId(
|
||||||
|
inventoryItems,
|
||||||
|
locationIds,
|
||||||
|
inventoryService
|
||||||
|
)
|
||||||
|
return inventoryItems.map((inventoryItem) => ({
|
||||||
|
...inventoryItem,
|
||||||
|
location_levels: levelsByItemId[inventoryItem.id] || [],
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import {
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
ProductVariantService,
|
||||||
|
} from "../../../../../services"
|
||||||
|
import { InventoryItemDTO } from "../../../../../types/inventory"
|
||||||
|
import { ProductVariant } from "../../../../../models"
|
||||||
|
|
||||||
|
export type InventoryItemsWithVariants = Partial<InventoryItemDTO> & {
|
||||||
|
variants?: ProductVariant[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getVariantsByInventoryItemId = async (
|
||||||
|
inventoryItems: InventoryItemDTO[],
|
||||||
|
productVariantInventoryService: ProductVariantInventoryService,
|
||||||
|
productVariantService: ProductVariantService
|
||||||
|
): Promise<Record<string, InventoryItemsWithVariants>> => {
|
||||||
|
const variantInventory = await productVariantInventoryService.listByItem(
|
||||||
|
inventoryItems.map((item) => item.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
const variants = await productVariantService.list({
|
||||||
|
id: variantInventory.map((varInventory) => varInventory.variant_id),
|
||||||
|
})
|
||||||
|
const variantMap = new Map(variants.map((variant) => [variant.id, variant]))
|
||||||
|
|
||||||
|
return variantInventory.reduce((acc, cur) => {
|
||||||
|
acc[cur.inventory_item_id] = acc[cur.inventory_item_id] ?? []
|
||||||
|
acc[cur.inventory_item_id].push(variantMap.get(cur.variant_id))
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { IStockLocationService } from "../../../../interfaces"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @oas [delete] /stock-locations/{id}
|
||||||
|
* operationId: "DeleteStockLocationsStockLocation"
|
||||||
|
* summary: "Delete a Stock Location"
|
||||||
|
* description: "Delete a Stock Location"
|
||||||
|
* x-authenticated: true
|
||||||
|
* parameters:
|
||||||
|
* - (path) id=* {string} The ID of the Stock Location to delete.
|
||||||
|
* 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.stockLocations.delete(stock_location_id)
|
||||||
|
* .then(({ id, object, deleted }) => {
|
||||||
|
* console.log(id)
|
||||||
|
* })
|
||||||
|
* - lang: Shell
|
||||||
|
* label: cURL
|
||||||
|
* source: |
|
||||||
|
* curl --location --request DELETE 'https://medusa-url.com/admin/stock-locations/{id}' \
|
||||||
|
* --header 'Authorization: Bearer {api_token}'
|
||||||
|
* security:
|
||||||
|
* - api_token: []
|
||||||
|
* - cookie_auth: []
|
||||||
|
* tags:
|
||||||
|
* - StockLocation
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: string
|
||||||
|
* description: The ID of the deleted Stock Location.
|
||||||
|
* object:
|
||||||
|
* type: string
|
||||||
|
* description: The type of the object that was deleted.
|
||||||
|
* format: stock_location
|
||||||
|
* deleted:
|
||||||
|
* type: boolean
|
||||||
|
* description: Whether or not the Stock Location was deleted.
|
||||||
|
* default: true
|
||||||
|
* "400":
|
||||||
|
* $ref: "#/components/responses/400_error"
|
||||||
|
*/
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const stockLocationService: IStockLocationService = req.scope.resolve(
|
||||||
|
"stockLocationService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
await manager.transaction(async (transactionManager) => {
|
||||||
|
await stockLocationService.withTransaction(transactionManager).delete(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(200).send({
|
||||||
|
id,
|
||||||
|
object: "stock_location",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
import "reflect-metadata"
|
import "reflect-metadata"
|
||||||
import { PaginatedResponse } from "../../../../types/common"
|
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||||
import { StockLocationDTO } from "../../../../types/stock-location"
|
import { StockLocationDTO } from "../../../../types/stock-location"
|
||||||
import middlewares, {
|
import middlewares, {
|
||||||
transformBody,
|
transformBody,
|
||||||
@@ -71,6 +71,11 @@ export default (app) => {
|
|||||||
middlewares.wrap(require("./update-stock-location").default)
|
middlewares.wrap(require("./update-stock-location").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
"/:id",
|
||||||
|
middlewares.wrap(require("./delete-stock-location").default)
|
||||||
|
)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +90,24 @@ export const defaultAdminStockLocationFields: (keyof StockLocationDTO)[] = [
|
|||||||
|
|
||||||
export const defaultAdminStockLocationRelations = []
|
export const defaultAdminStockLocationRelations = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminStockLocationsDeleteRes
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: string
|
||||||
|
* description: The ID of the deleted Stock Location.
|
||||||
|
* object:
|
||||||
|
* type: string
|
||||||
|
* description: The type of the object that was deleted.
|
||||||
|
* default: stock_location
|
||||||
|
* deleted:
|
||||||
|
* type: boolean
|
||||||
|
* description: Whether or not the items were deleted.
|
||||||
|
* default: true
|
||||||
|
*/
|
||||||
|
export type AdminStockLocationsDeleteRes = DeleteResponse
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @schema AdminStockLocationsRes
|
* @schema AdminStockLocationsRes
|
||||||
* type: object
|
* type: object
|
||||||
|
|||||||
@@ -28,4 +28,6 @@ export interface IStockLocationService {
|
|||||||
create(input: CreateStockLocationInput): Promise<StockLocationDTO>
|
create(input: CreateStockLocationInput): Promise<StockLocationDTO>
|
||||||
|
|
||||||
update(id: string, input: UpdateStockLocationInput): Promise<StockLocationDTO>
|
update(id: string, input: UpdateStockLocationInput): Promise<StockLocationDTO>
|
||||||
|
|
||||||
|
delete(id: string): Promise<void>
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,58 @@
|
|||||||
import { NumericalComparisonOperator, StringComparisonOperator } from "./common"
|
import { NumericalComparisonOperator, StringComparisonOperator } from "./common"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema InventoryItemDTO
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - sku
|
||||||
|
* properties:
|
||||||
|
* sku:
|
||||||
|
* description: The Stock Keeping Unit (SKU) code of the Inventory Item.
|
||||||
|
* type: string
|
||||||
|
* hs_code:
|
||||||
|
* description: The Harmonized System code of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* origin_country:
|
||||||
|
* description: The country in which the Inventory Item was produced. May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* mid_code:
|
||||||
|
* description: The Manufacturers Identification code that identifies the manufacturer of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* material:
|
||||||
|
* description: The material and composition that the Inventory Item is made of, May be used by Fulfillment Providers to pass customs information to shipping carriers.
|
||||||
|
* type: string
|
||||||
|
* weight:
|
||||||
|
* description: The weight of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* height:
|
||||||
|
* description: The height of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* width:
|
||||||
|
* description: The width of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* length:
|
||||||
|
* description: The length of the Inventory Item. May be used in shipping rate calculations.
|
||||||
|
* type: number
|
||||||
|
* requires_shipping:
|
||||||
|
* description: Whether the item requires shipping.
|
||||||
|
* type: boolean
|
||||||
|
* metadata:
|
||||||
|
* type: object
|
||||||
|
* description: An optional key-value map with additional details
|
||||||
|
* example: {car: "white"}
|
||||||
|
* created_at:
|
||||||
|
* type: string
|
||||||
|
* description: "The date with timezone at which the resource was created."
|
||||||
|
* format: date-time
|
||||||
|
* updated_at:
|
||||||
|
* type: string
|
||||||
|
* description: "The date with timezone at which the resource was updated."
|
||||||
|
* format: date-time
|
||||||
|
* deleted_at:
|
||||||
|
* type: string
|
||||||
|
* description: "The date with timezone at which the resource was deleted."
|
||||||
|
* format: date-time
|
||||||
|
*/
|
||||||
export type InventoryItemDTO = {
|
export type InventoryItemDTO = {
|
||||||
id: string
|
id: string
|
||||||
sku?: string | null
|
sku?: string | null
|
||||||
@@ -12,7 +65,7 @@ export type InventoryItemDTO = {
|
|||||||
length?: number | null
|
length?: number | null
|
||||||
height?: number | null
|
height?: number | null
|
||||||
width?: number | null
|
width?: number | null
|
||||||
metadata: Record<string, unknown> | null
|
metadata?: Record<string, unknown> | null
|
||||||
created_at: string | Date
|
created_at: string | Date
|
||||||
updated_at: string | Date
|
updated_at: string | Date
|
||||||
deleted_at: string | Date | null
|
deleted_at: string | Date | null
|
||||||
@@ -70,6 +123,45 @@ export type ReservationItemDTO = {
|
|||||||
deleted_at: string | Date | null
|
deleted_at: string | Date | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema InventoryLevelDTO
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - inventory_item_id
|
||||||
|
* - location_id
|
||||||
|
* - stocked_quantity
|
||||||
|
* - reserved_quantity
|
||||||
|
* - incoming_quantity
|
||||||
|
* properties:
|
||||||
|
* location_id:
|
||||||
|
* description: the item location ID
|
||||||
|
* type: string
|
||||||
|
* stocked_quantity:
|
||||||
|
* description: the total stock quantity of an inventory item at the given location ID
|
||||||
|
* type: number
|
||||||
|
* reserved_quantity:
|
||||||
|
* description: the reserved stock quantity of an inventory item at the given location ID
|
||||||
|
* type: number
|
||||||
|
* incoming_quantity:
|
||||||
|
* description: the incoming stock quantity of an inventory item at the given location ID
|
||||||
|
* type: number
|
||||||
|
* metadata:
|
||||||
|
* type: object
|
||||||
|
* description: An optional key-value map with additional details
|
||||||
|
* example: {car: "white"}
|
||||||
|
* created_at:
|
||||||
|
* type: string
|
||||||
|
* description: "The date with timezone at which the resource was created."
|
||||||
|
* format: date-time
|
||||||
|
* updated_at:
|
||||||
|
* type: string
|
||||||
|
* description: "The date with timezone at which the resource was updated."
|
||||||
|
* format: date-time
|
||||||
|
* deleted_at:
|
||||||
|
* type: string
|
||||||
|
* description: "The date with timezone at which the resource was deleted."
|
||||||
|
* format: date-time
|
||||||
|
*/
|
||||||
export type InventoryLevelDTO = {
|
export type InventoryLevelDTO = {
|
||||||
id: string
|
id: string
|
||||||
inventory_item_id: string
|
inventory_item_id: string
|
||||||
|
|||||||
Reference in new issue
Block a user