Feat(core-flows, inventory-next, medusa, types): Add create inventory item endpoint (#6693)
* init create inventory item * fix integration tests * pr feedback * rename to validate * pr feedback * bulk delete * undo change w/ naming * update dto body * move integration test * pr fixes * fix for pr review * revert medusa-config changes * pr feedback * fix build
This commit is contained in:
1 parent
0219a8677b
commit
390bc3e72f
11 files changed
+317
-165
No files matched your search
@@ -417,32 +417,12 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip("Create inventory items", () => {
|
describe("Create inventory items", () => {
|
||||||
it("should create inventory items", async () => {
|
it("should create inventory items", async () => {
|
||||||
const createResult = await api.post(
|
|
||||||
`/admin/products`,
|
|
||||||
{
|
|
||||||
title: "Test Product",
|
|
||||||
variants: [
|
|
||||||
{
|
|
||||||
title: "Test Variant w. inventory 2",
|
|
||||||
sku: "MY_SKU1",
|
|
||||||
material: "material",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
const inventoryItems = await service.list()
|
|
||||||
|
|
||||||
expect(inventoryItems).toHaveLength(0)
|
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/inventory-items`,
|
`/admin/inventory-items`,
|
||||||
{
|
{
|
||||||
sku: "test-sku",
|
sku: "test-sku",
|
||||||
variant_id: createResult.data.product.variants[0].id,
|
|
||||||
},
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -454,143 +434,6 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should attach inventory items on creation", async () => {
|
|
||||||
const createResult = await api.post(
|
|
||||||
`/admin/products`,
|
|
||||||
{
|
|
||||||
title: "Test Product",
|
|
||||||
variants: [
|
|
||||||
{
|
|
||||||
title: "Test Variant w. inventory 2",
|
|
||||||
sku: "MY_SKU1",
|
|
||||||
material: "material",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
const inventoryItems = await service.list()
|
|
||||||
|
|
||||||
expect(inventoryItems).toHaveLength(0)
|
|
||||||
|
|
||||||
await api.post(
|
|
||||||
`/admin/inventory-items`,
|
|
||||||
{
|
|
||||||
sku: "test-sku",
|
|
||||||
variant_id: createResult.data.product.variants[0].id,
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
const remoteQuery = appContainer.resolve(
|
|
||||||
ContainerRegistrationKeys.REMOTE_QUERY
|
|
||||||
)
|
|
||||||
|
|
||||||
const query = remoteQueryObjectFromString({
|
|
||||||
entryPoint: "product_variant_inventory_item",
|
|
||||||
variables: {
|
|
||||||
variant_id: createResult.data.product.variants[0].id,
|
|
||||||
},
|
|
||||||
fields: ["inventory_item_id", "variant_id"],
|
|
||||||
})
|
|
||||||
|
|
||||||
const existingItems = await remoteQuery(query)
|
|
||||||
|
|
||||||
expect(existingItems).toHaveLength(1)
|
|
||||||
expect(existingItems[0].variant_id).toEqual(
|
|
||||||
createResult.data.product.variants[0].id
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe.skip("Create inventory items", () => {
|
|
||||||
it("should create inventory items", async () => {
|
|
||||||
const createResult = await api.post(
|
|
||||||
`/admin/products`,
|
|
||||||
{
|
|
||||||
title: "Test Product",
|
|
||||||
variants: [
|
|
||||||
{
|
|
||||||
title: "Test Variant w. inventory 2",
|
|
||||||
sku: "MY_SKU1",
|
|
||||||
material: "material",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
const inventoryItems = await service.list({})
|
|
||||||
|
|
||||||
expect(inventoryItems).toHaveLength(0)
|
|
||||||
|
|
||||||
const response = await api.post(
|
|
||||||
`/admin/inventory-items`,
|
|
||||||
{
|
|
||||||
sku: "test-sku",
|
|
||||||
variant_id: createResult.data.product.variants[0].id,
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.inventory_item).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
sku: "test-sku",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should attach inventory items on creation", async () => {
|
|
||||||
const createResult = await api.post(
|
|
||||||
`/admin/products`,
|
|
||||||
{
|
|
||||||
title: "Test Product",
|
|
||||||
variants: [
|
|
||||||
{
|
|
||||||
title: "Test Variant w. inventory 2",
|
|
||||||
sku: "MY_SKU1",
|
|
||||||
material: "material",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
const inventoryItems = await service.list({})
|
|
||||||
|
|
||||||
expect(inventoryItems).toHaveLength(0)
|
|
||||||
|
|
||||||
await api.post(
|
|
||||||
`/admin/inventory-items`,
|
|
||||||
{
|
|
||||||
sku: "test-sku",
|
|
||||||
variant_id: createResult.data.product.variants[0].id,
|
|
||||||
},
|
|
||||||
adminHeaders
|
|
||||||
)
|
|
||||||
|
|
||||||
const remoteQuery = appContainer.resolve(
|
|
||||||
ContainerRegistrationKeys.REMOTE_QUERY
|
|
||||||
)
|
|
||||||
|
|
||||||
const query = remoteQueryObjectFromString({
|
|
||||||
entryPoint: "product_variant_inventory_item",
|
|
||||||
variables: {
|
|
||||||
variant_id: createResult.data.product.variants[0].id,
|
|
||||||
},
|
|
||||||
fields: ["inventory_item_id", "variant_id"],
|
|
||||||
})
|
|
||||||
|
|
||||||
const existingItems = await remoteQuery(query)
|
|
||||||
|
|
||||||
expect(existingItems).toHaveLength(1)
|
|
||||||
expect(existingItems[0].variant_id).toEqual(
|
|
||||||
createResult.data.product.variants[0].id
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("List inventory items", () => {
|
describe("List inventory items", () => {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
import { ContainerRegistrationKeys } from "@medusajs/utils"
|
||||||
|
import { InventoryItemDTO } from "@medusajs/types"
|
||||||
|
|
||||||
|
export const attachInventoryItemToVariantsStepId =
|
||||||
|
"attach-inventory-items-to-variants-step"
|
||||||
|
export const attachInventoryItemToVariants = createStep(
|
||||||
|
attachInventoryItemToVariantsStepId,
|
||||||
|
async (
|
||||||
|
input: {
|
||||||
|
inventoryItemId: string
|
||||||
|
tag?: string
|
||||||
|
}[],
|
||||||
|
{ container }
|
||||||
|
) => {
|
||||||
|
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
|
||||||
|
|
||||||
|
const linkDefinitions = input
|
||||||
|
.filter(({ tag }) => !!tag)
|
||||||
|
.map(({ inventoryItemId, tag }) => ({
|
||||||
|
productService: {
|
||||||
|
variant_id: tag,
|
||||||
|
},
|
||||||
|
inventoryService: {
|
||||||
|
inventory_item_id: inventoryItemId,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const links = await remoteLink.create(linkDefinitions)
|
||||||
|
|
||||||
|
return new StepResponse(links, linkDefinitions)
|
||||||
|
},
|
||||||
|
async (linkDefinitions, { container }) => {
|
||||||
|
if (!linkDefinitions?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
|
||||||
|
|
||||||
|
await remoteLink.dismiss(linkDefinitions)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
import { CreateInventoryItemInput } from "@medusajs/types"
|
||||||
|
import { IInventoryServiceNext } from "@medusajs/types"
|
||||||
|
import { InventoryNext } from "@medusajs/types"
|
||||||
|
import { ModuleRegistrationName } from "../../../../modules-sdk/dist"
|
||||||
|
import { promiseAll } from "@medusajs/utils"
|
||||||
|
|
||||||
|
export const createInventoryItemsStepId = "create-inventory-items"
|
||||||
|
export const createInventoryItemsStep = createStep(
|
||||||
|
createInventoryItemsStepId,
|
||||||
|
async (data: InventoryNext.CreateInventoryItemInput[], { container }) => {
|
||||||
|
const inventoryService: IInventoryServiceNext = container.resolve(
|
||||||
|
ModuleRegistrationName.INVENTORY
|
||||||
|
)
|
||||||
|
|
||||||
|
const createdItems: InventoryNext.InventoryItemDTO[] =
|
||||||
|
await inventoryService.create(data)
|
||||||
|
|
||||||
|
return new StepResponse(
|
||||||
|
createdItems,
|
||||||
|
createdItems.map((i) => i.id)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async (data: string[] | undefined, { container }) => {
|
||||||
|
if (!data?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventoryService = container.resolve(ModuleRegistrationName.INVENTORY)
|
||||||
|
|
||||||
|
await inventoryService!.delete(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
|
export * from "./attach-inventory-items"
|
||||||
|
export * from "./create-inventory-items"
|
||||||
|
export * from "./validate-singular-inventory-items-for-tags"
|
||||||
export * from "./create-inventory-levels"
|
export * from "./create-inventory-levels"
|
||||||
export * from "./validate-inventory-locations"
|
export * from "./validate-inventory-locations"
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
ContainerRegistrationKeys,
|
||||||
|
MedusaError,
|
||||||
|
remoteQueryObjectFromString,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
import { InventoryNext } from "@medusajs/types"
|
||||||
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
|
export const validateInventoryItemsForCreateStepId =
|
||||||
|
"validate-inventory-items-for-create-step"
|
||||||
|
export const validateInventoryItemsForCreate = createStep(
|
||||||
|
validateInventoryItemsForCreateStepId,
|
||||||
|
async (
|
||||||
|
input: {
|
||||||
|
tag?: string
|
||||||
|
}[],
|
||||||
|
{ container }
|
||||||
|
) => {
|
||||||
|
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
|
||||||
|
|
||||||
|
const linkService = remoteLink.getLinkModule(
|
||||||
|
Modules.PRODUCT,
|
||||||
|
"variant_id",
|
||||||
|
Modules.INVENTORY,
|
||||||
|
"inventory_item_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingItems = await linkService.list(
|
||||||
|
{ variant_id: input.map((i) => i.tag) },
|
||||||
|
{ select: ["variant_id", "inventory_item_id"] }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (existingItems.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Inventory items already exist for variants with ids: " +
|
||||||
|
existingItems.map((i) => i.variant_id).join(", ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StepResponse(input)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
WorkflowData,
|
||||||
|
createWorkflow,
|
||||||
|
transform,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import {
|
||||||
|
attachInventoryItemToVariants,
|
||||||
|
createInventoryItemsStep,
|
||||||
|
validateInventoryItemsForCreate,
|
||||||
|
} from "../steps"
|
||||||
|
|
||||||
|
import { InventoryNext } from "@medusajs/types"
|
||||||
|
|
||||||
|
interface WorkflowInput {
|
||||||
|
items: InventoryNext.CreateInventoryItemInput[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createInventoryItemsWorkflowId = "create-inventory-items-workflow"
|
||||||
|
export const createInventoryItemsWorkflow = createWorkflow(
|
||||||
|
createInventoryItemsWorkflowId,
|
||||||
|
(input: WorkflowData<WorkflowInput>) => {
|
||||||
|
const items = createInventoryItemsStep(input.items)
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
export * from "./create-inventory-items"
|
||||||
export * from "./create-inventory-levels"
|
export * from "./create-inventory-levels"
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
AdminGetInventoryItemsItemParams,
|
AdminGetInventoryItemsItemParams,
|
||||||
AdminGetInventoryItemsParams,
|
AdminGetInventoryItemsParams,
|
||||||
AdminPostInventoryItemsItemLocationLevelsReq,
|
AdminPostInventoryItemsItemLocationLevelsReq,
|
||||||
|
AdminPostInventoryItemsReq,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
{
|
{
|
||||||
method: "ALL",
|
method: "ALL",
|
||||||
matcher: "/admin/inventory-items*",
|
matcher: "/admin/inventory-items*",
|
||||||
middlewares: [authenticate("admin", ["session", "bearer"])],
|
middlewares: [authenticate("admin", ["session", "bearer", "api-key"])],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
@@ -41,4 +42,15 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
matcher: "/admin/inventory-items/:id/location-levels",
|
matcher: "/admin/inventory-items/:id/location-levels",
|
||||||
middlewares: [transformBody(AdminPostInventoryItemsItemLocationLevelsReq)],
|
middlewares: [transformBody(AdminPostInventoryItemsItemLocationLevelsReq)],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/inventory-items",
|
||||||
|
middlewares: [
|
||||||
|
transformBody(AdminPostInventoryItemsReq),
|
||||||
|
transformQuery(
|
||||||
|
AdminGetInventoryItemsItemParams,
|
||||||
|
QueryConfig.retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
import { InventoryNext } from "@medusajs/types"
|
import { InventoryNext } from "@medusajs/types"
|
||||||
|
|
||||||
export const defaultAdminInventoryItemRelations = []
|
|
||||||
export const allowedAdminInventoryItemRelations = []
|
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
export const defaultAdminLocationLevelFields: (keyof InventoryNext.InventoryLevelDTO)[] =
|
export const defaultAdminLocationLevelFields: (keyof InventoryNext.InventoryLevelDTO)[] =
|
||||||
[
|
[
|
||||||
@@ -44,9 +41,8 @@ export const defaultAdminInventoryItemFields = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const retrieveTransformQueryConfig = {
|
export const retrieveTransformQueryConfig = {
|
||||||
defaultFields: defaultAdminInventoryItemFields,
|
defaults: defaultAdminInventoryItemFields,
|
||||||
defaultRelations: defaultAdminInventoryItemRelations,
|
allowed: defaultAdminInventoryItemFields,
|
||||||
allowedRelations: allowedAdminInventoryItemRelations,
|
|
||||||
isList: false,
|
isList: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,33 @@ import {
|
|||||||
remoteQueryObjectFromString,
|
remoteQueryObjectFromString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
|
|
||||||
|
import { AdminPostInventoryItemsReq } from "./validators"
|
||||||
|
import { createInventoryItemsWorkflow } from "@medusajs/core-flows"
|
||||||
|
|
||||||
|
// Create inventory-item
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest<AdminPostInventoryItemsReq>,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||||
|
|
||||||
|
const { result } = await createInventoryItemsWorkflow(req.scope).run({
|
||||||
|
input: { items: [req.validatedBody] },
|
||||||
|
})
|
||||||
|
|
||||||
|
const [inventory_item] = await remoteQuery(
|
||||||
|
remoteQueryObjectFromString({
|
||||||
|
entryPoint: "inventory_items",
|
||||||
|
variables: {
|
||||||
|
id: result[0].id,
|
||||||
|
},
|
||||||
|
fields: req.retrieveConfig.select as string[],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ inventory_item })
|
||||||
|
}
|
||||||
|
|
||||||
// List inventory-items
|
// List inventory-items
|
||||||
export const GET = async (
|
export const GET = async (
|
||||||
req: AuthenticatedMedusaRequest,
|
req: AuthenticatedMedusaRequest,
|
||||||
|
|||||||
@@ -135,3 +135,125 @@ export class AdminPostInventoryItemsItemLocationLevelsReq {
|
|||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
export class AdminPostInventoryItemsItemLocationLevelsParams extends FindParams {}
|
export class AdminPostInventoryItemsItemLocationLevelsParams extends FindParams {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @schema AdminPostInventoryItemsReq
|
||||||
|
* type: object
|
||||||
|
* description: "The details of the inventory item to create."
|
||||||
|
* properties:
|
||||||
|
* sku:
|
||||||
|
* description: The unique SKU of the associated Product Variant.
|
||||||
|
* type: string
|
||||||
|
* ean:
|
||||||
|
* description: The EAN number of the item.
|
||||||
|
* type: string
|
||||||
|
* upc:
|
||||||
|
* description: The UPC number of the item.
|
||||||
|
* type: string
|
||||||
|
* barcode:
|
||||||
|
* description: A generic GTIN field for the Product Variant.
|
||||||
|
* 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
|
||||||
|
* inventory_quantity:
|
||||||
|
* description: The amount of stock kept of the associated Product Variant.
|
||||||
|
* type: integer
|
||||||
|
* default: 0
|
||||||
|
* allow_backorder:
|
||||||
|
* description: Whether the associated Product Variant can be purchased when out of stock.
|
||||||
|
* type: boolean
|
||||||
|
* manage_inventory:
|
||||||
|
* description: Whether Medusa should keep track of the inventory for the associated Product Variant.
|
||||||
|
* type: boolean
|
||||||
|
* default: true
|
||||||
|
* weight:
|
||||||
|
* description: The weight 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
|
||||||
|
* 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
|
||||||
|
* 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
|
||||||
|
* title:
|
||||||
|
* description: The inventory item's title.
|
||||||
|
* type: string
|
||||||
|
* description:
|
||||||
|
* description: The inventory item's description.
|
||||||
|
* type: string
|
||||||
|
* thumbnail:
|
||||||
|
* description: The inventory item's thumbnail.
|
||||||
|
* type: string
|
||||||
|
* metadata:
|
||||||
|
* description: An optional set of key-value pairs with additional information.
|
||||||
|
* type: object
|
||||||
|
* externalDocs:
|
||||||
|
* description: "Learn about the metadata attribute, and how to delete and update it."
|
||||||
|
* url: "https://docs.medusajs.com/development/entities/overview#metadata-attribute"
|
||||||
|
*/
|
||||||
|
export class AdminPostInventoryItemsReq {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
sku?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
hs_code?: string
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
weight?: number
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
length?: number
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
height?: number
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
width?: number
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
origin_country?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
mid_code?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
material?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
title?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
thumbnail?: string
|
||||||
|
|
||||||
|
@IsObject()
|
||||||
|
@IsOptional()
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
Reference in new issue
Block a user