feat(medusa): Add purchasable prop on variants when setting availability (#3811)

* write integration tests

* update variant inventory decorator

* update types

* add changeset

* feedback comments

* add yaml schemas

* different oas approach

* pr feedback

* update oas

---------

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Philip Korsholm
2023-04-20 08:54:26 +02:00
committed by GitHub
co-authored by Oliver Windall Juhl
parent 7d1a78a84f
commit 2be144ff05
27 changed files with 395 additions and 70 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
fix(medusa): add purchasable property
@@ -0,0 +1,10 @@
title: Product with decorated variants
type: object
allOf:
- $ref: ./Product.yaml
- type: object
properties:
variants:
type: array
items:
$ref: ./DecoratedVariant.yaml
@@ -0,0 +1,9 @@
title: Decorated Product Variant
type: object
allOf:
- $ref: ./PricedVariant.yaml
- type: object
properties:
purchasable:
type: boolean
description: Boolean indicating if variant is purchasable.
@@ -0,0 +1,10 @@
title: Product with decorated variants
type: object
allOf:
- $ref: ./Product.yaml
- type: object
properties:
variants:
type: array
items:
$ref: ./DecoratedVariant.yaml
@@ -0,0 +1,9 @@
title: Decorated Product Variant
type: object
allOf:
- $ref: ./PricedVariant.yaml
- type: object
properties:
purchasable:
type: boolean
description: Boolean indicating if variant is purchasable.
@@ -50,6 +50,7 @@ describe("Create Variant", () => {
describe("list-products", () => { describe("list-products", () => {
const productId = "test-product" const productId = "test-product"
const variantId = "test-variant" const variantId = "test-variant"
let sc2
beforeEach(async () => { beforeEach(async () => {
await adminSeeder(dbConnection) await adminSeeder(dbConnection)
@@ -66,13 +67,14 @@ describe("Create Variant", () => {
dbConnection, dbConnection,
{ {
id: productId, id: productId,
status: "published",
variants: [{ id: variantId }], variants: [{ id: variantId }],
}, },
100 100
) )
const sc1 = await simpleSalesChannelFactory(dbConnection, {}) const sc1 = await simpleSalesChannelFactory(dbConnection, {})
const sc2 = await simpleSalesChannelFactory(dbConnection, {}) sc2 = await simpleSalesChannelFactory(dbConnection, {})
const sc3 = await simpleSalesChannelFactory(dbConnection, {}) const sc3 = await simpleSalesChannelFactory(dbConnection, {})
const sl1 = await stockLocationService.create({ name: "sl1" }) const sl1 = await stockLocationService.create({ name: "sl1" })
@@ -120,5 +122,203 @@ describe("Create Variant", () => {
}), }),
]) ])
}) })
describe("/store/products", () => {
beforeEach(async () => {
const inventoryService = appContainer.resolve("inventoryService")
const productVariantInventoryService = appContainer.resolve(
"productVariantInventoryService"
)
await simpleProductFactory(
dbConnection,
{
id: `${productId}-1`,
status: "published",
variants: [
{
id: `${variantId}-1`,
manage_inventory: false,
},
],
},
101
)
await simpleProductFactory(
dbConnection,
{
id: `${productId}-2`,
status: "published",
variants: [{ id: `${variantId}-2`, manage_inventory: true }],
},
102
)
await simpleProductFactory(
dbConnection,
{
id: `${productId}-3`,
status: "published",
variants: [
{
id: `${variantId}-3`,
manage_inventory: true,
allow_backorder: true,
},
],
},
103
)
const invItem = await inventoryService.createInventoryItem({
sku: "test-sku-1",
})
await productVariantInventoryService.attachInventoryItem(
`${variantId}-3`,
invItem.id
)
await simpleProductFactory(
dbConnection,
{
id: `${productId}-4`,
status: "published",
variants: [
{
id: `${variantId}-4`,
manage_inventory: true,
allow_backorder: false,
},
],
},
104
)
const invItem1 = await inventoryService.createInventoryItem({
sku: "test-sku-2",
})
await productVariantInventoryService.attachInventoryItem(
`${variantId}-4`,
invItem1.id
)
})
it("lists location availability correctly for store", async () => {
const api = useApi()
const res = await api.get(`/store/products`)
expect(res.status).toEqual(200)
expect(res.data.products).toEqual([
expect.objectContaining({
id: `${productId}-4`,
variants: [
expect.objectContaining({
purchasable: false,
}),
],
}),
expect.objectContaining({
id: `${productId}-3`,
variants: [
expect.objectContaining({
purchasable: false,
}),
],
}),
expect.objectContaining({
id: `${productId}-2`,
variants: [
expect.objectContaining({
purchasable: true,
}),
],
}),
expect.objectContaining({
id: `${productId}-1`,
variants: [
expect.objectContaining({
inventory_quantity: 10,
purchasable: true,
}),
],
}),
expect.objectContaining({
id: productId,
variants: [
expect.objectContaining({
purchasable: false,
}),
],
}),
])
})
it("lists location availability correctly for store with sales channel id", async () => {
const api = useApi()
const productService = appContainer.resolve("productService")
const ids = [
`${productId}`,
`${productId}-1`,
`${productId}-2`,
`${productId}-3`,
`${productId}-4`,
]
for (const id of ids) {
await productService.update(id, {
sales_channels: [{ id: sc2.id }],
})
}
const res = await api.get(
`/store/products?sales_channel_id[]=${sc2.id}`
)
expect(res.status).toEqual(200)
expect(res.data.products).toEqual([
expect.objectContaining({
id: `${productId}-4`,
variants: [
expect.objectContaining({
purchasable: false,
}),
],
}),
expect.objectContaining({
id: `${productId}-3`,
variants: [
expect.objectContaining({
purchasable: true,
}),
],
}),
expect.objectContaining({
id: `${productId}-2`,
variants: [
expect.objectContaining({
purchasable: true,
}),
],
}),
expect.objectContaining({
id: `${productId}-1`,
variants: [
expect.objectContaining({
inventory_quantity: 10,
purchasable: true,
}),
],
}),
expect.objectContaining({
id: productId,
variants: [
expect.objectContaining({
purchasable: true,
inventory_quantity: 4,
}),
],
}),
])
})
})
}) })
}) })
@@ -1,14 +1,15 @@
import { Product, ProductOption, ProductStatus, ProductType, ShippingProfile, ShippingProfileType, } from "@medusajs/medusa"
import { ProductVariantFactoryData, simpleProductVariantFactory, } from "./simple-product-variant-factory"
import { Connection } from "typeorm" import { Connection } from "typeorm"
import faker from "faker" import faker from "faker"
import { Product, ProductOption, ProductType, ShippingProfile, ShippingProfileType, } from "@medusajs/medusa"
import { ProductVariantFactoryData, simpleProductVariantFactory, } from "./simple-product-variant-factory"
export type ProductFactoryData = { export type ProductFactoryData = {
id?: string id?: string
is_giftcard?: boolean is_giftcard?: boolean
title?: string title?: string
type?: string type?: string
status?: ProductStatus
options?: { id: string; title: string }[] options?: { id: string; title: string }[]
variants?: ProductVariantFactoryData[] variants?: ProductVariantFactoryData[]
} }
@@ -45,6 +46,7 @@ export const simpleProductFactory = async (
const toSave = manager.create(Product, { const toSave = manager.create(Product, {
id: prodId, id: prodId,
type_id: typeId, type_id: typeId,
status: data.status || ProductStatus.DRAFT,
title: data.title || faker.commerce.productName(), title: data.title || faker.commerce.productName(),
is_giftcard: data.is_giftcard || false, is_giftcard: data.is_giftcard || false,
discountable: !data.is_giftcard, discountable: !data.is_giftcard,
@@ -1,17 +1,20 @@
import { Connection } from "typeorm"
import faker from "faker"
import { import {
MoneyAmount,
ProductOptionValue, ProductOptionValue,
ProductVariant, ProductVariant,
MoneyAmount,
} from "@medusajs/medusa" } from "@medusajs/medusa"
import { Connection } from "typeorm"
import faker from "faker"
export type ProductVariantFactoryData = { export type ProductVariantFactoryData = {
product_id: string product_id: string
id?: string id?: string
is_giftcard?: boolean is_giftcard?: boolean
inventory_quantity?: number inventory_quantity?: number
title?: string title?: string
allow_backorder?: boolean
manage_inventory?: boolean
options?: { option_id: string; value: string }[] options?: { option_id: string; value: string }[]
prices?: { currency: string; amount: number }[] prices?: { currency: string; amount: number }[]
} }
@@ -31,6 +34,8 @@ export const simpleProductVariantFactory = async (
const toSave = manager.create(ProductVariant, { const toSave = manager.create(ProductVariant, {
id, id,
product_id: data.product_id, product_id: data.product_id,
allow_backorder: data.allow_backorder || false,
manage_inventory: typeof data.manage_inventory !== 'undefined' ? data.manage_inventory : true,
inventory_quantity: inventory_quantity:
typeof data.inventory_quantity !== "undefined" typeof data.inventory_quantity !== "undefined"
? data.inventory_quantity ? data.inventory_quantity
@@ -14,7 +14,9 @@ export interface AdminProductsListRes {
"collection" | "images" | "options" | "tags" | "type" | "variants" "collection" | "images" | "options" | "tags" | "type" | "variants"
>, >,
{ {
variants: Array<SetRelation<ProductVariant, "options" | "prices">> variants: Array<
SetRelation<ProductVariant, "options" | "prices" | "purchasable">
>
} }
> >
> >
@@ -6,7 +6,9 @@ import { SetRelation, Merge } from "../core/ModelUtils"
import type { PricedVariant } from "./PricedVariant" import type { PricedVariant } from "./PricedVariant"
export interface AdminVariantsListRes { export interface AdminVariantsListRes {
variants: Array<SetRelation<PricedVariant, "options" | "prices" | "product">> variants: Array<
SetRelation<PricedVariant, "options" | "prices" | "product" | "purchasable">
>
/** /**
* The total number of items available * The total number of items available
*/ */
@@ -120,4 +120,15 @@ export interface ProductVariant {
* An optional key-value map with additional details * An optional key-value map with additional details
*/ */
metadata: Record<string, any> | null metadata: Record<string, any> | null
/**
* Only used with the inventory modules.
* A boolean value indicating whether the Product Variant is purchasable.
* A variant is purchasable if:
* - inventory is not managed
* - it has no inventory items
* - it is in stock
* - it is backorderable.
*
*/
purchasable?: boolean
} }
@@ -16,7 +16,9 @@ export interface StoreProductsListRes {
>, >,
{ {
options: Array<SetRelation<ProductOption, "values">> options: Array<SetRelation<ProductOption, "values">>
variants: Array<SetRelation<ProductVariant, "options" | "prices">> variants: Array<
SetRelation<ProductVariant, "options" | "prices" | "purchasable">
>
} }
> >
> >
@@ -15,7 +15,9 @@ export interface StoreProductsRes {
>, >,
{ {
options: Array<SetRelation<ProductOption, "values">> options: Array<SetRelation<ProductOption, "values">>
variants: Array<SetRelation<ProductVariant, "options" | "prices">> variants: Array<
SetRelation<ProductVariant, "options" | "prices" | "purchasable">
>
} }
> >
} }
@@ -6,5 +6,7 @@ import { SetRelation, Merge } from "../core/ModelUtils"
import type { PricedVariant } from "./PricedVariant" import type { PricedVariant } from "./PricedVariant"
export interface StoreVariantsListRes { export interface StoreVariantsListRes {
variants: Array<SetRelation<PricedVariant, "prices" | "options" | "product">> variants: Array<
SetRelation<PricedVariant, "prices" | "options" | "product" | "purchasable">
>
} }
@@ -6,5 +6,8 @@ import { SetRelation, Merge } from "../core/ModelUtils"
import type { PricedVariant } from "./PricedVariant" import type { PricedVariant } from "./PricedVariant"
export interface StoreVariantsRes { export interface StoreVariantsRes {
variant: SetRelation<PricedVariant, "prices" | "options" | "product"> variant: SetRelation<
PricedVariant,
"prices" | "options" | "product" | "purchasable"
>
} }
@@ -114,13 +114,6 @@ export default async (req, res) => {
select: defaultOrderFields, select: defaultOrderFields,
}) })
// TODO: Re-enable when we have a way to handle inventory for draft orders on creation
if (!inventoryService) {
await reserveQuantityForDraftOrder(order, {
productVariantInventoryService,
})
}
return order return order
}) })
@@ -1,12 +1,14 @@
import { Router } from "express"
import "reflect-metadata" import "reflect-metadata"
import { Product, ProductTag, ProductType, ProductVariant } from "../../../.."
import { FindParams, PaginatedResponse } from "../../../../types/common" import { FindParams, PaginatedResponse } from "../../../../types/common"
import { PricedProduct } from "../../../../types/pricing" import { Product, ProductTag, ProductType, ProductVariant } from "../../../.."
import { FlagRouter } from "../../../../utils/flag-router"
import middlewares, { transformQuery } from "../../../middlewares" import middlewares, { transformQuery } from "../../../middlewares"
import { validateSalesChannelsExist } from "../../../middlewares/validators/sales-channel-existence"
import { AdminGetProductsParams } from "./list-products" import { AdminGetProductsParams } from "./list-products"
import { FlagRouter } from "../../../../utils/flag-router"
import { Router } from "express"
import { validateSalesChannelsExist } from "../../../middlewares/validators/sales-channel-existence"
import { PricedProduct } from "../../../../types/pricing"
const route = Router() const route = Router()
@@ -258,6 +260,8 @@ export type AdminProductsDeleteRes = {
* - variants * - variants
* - variants.options * - variants.options
* - variants.prices * - variants.prices
* totals:
* - variants.purchasable
* required: * required:
* - products * - products
* - count * - count
@@ -7,10 +7,10 @@ import {
} from "../../../../services" } from "../../../../services"
import { FilterableProductProps } from "../../../../types/product" import { FilterableProductProps } from "../../../../types/product"
import { IInventoryService } from "@medusajs/types"
import { PricedProduct } from "../../../../types/pricing" import { PricedProduct } from "../../../../types/pricing"
import { Product } from "../../../../models" import { Product } from "../../../../models"
import { Type } from "class-transformer" import { Type } from "class-transformer"
import { IInventoryService } from "@medusajs/types"
/** /**
* @oas [get] /admin/products * @oas [get] /admin/products
@@ -1,4 +1,5 @@
import { PricingService, ProductVariantService } from "../../../../services" import { PricingService, ProductVariantService } from "../../../../services"
import { FindParams } from "../../../../types/common" import { FindParams } from "../../../../types/common"
/** /**
@@ -1,12 +1,12 @@
import { Router } from "express"
import { ProductVariant } from "../../../../models/product-variant"
import { PaginatedResponse } from "../../../../types/common"
import { PricedVariant } from "../../../../types/pricing"
import middlewares, { transformQuery } from "../../../middlewares" import middlewares, { transformQuery } from "../../../middlewares"
import { checkRegisteredModules } from "../../../middlewares/check-registered-modules"
import { AdminGetVariantParams } from "./get-variant" import { AdminGetVariantParams } from "./get-variant"
import { AdminGetVariantsParams } from "./list-variants" import { AdminGetVariantsParams } from "./list-variants"
import { PaginatedResponse } from "../../../../types/common"
import { PricedVariant } from "../../../../types/pricing"
import { ProductVariant } from "../../../../models/product-variant"
import { Router } from "express"
import { checkRegisteredModules } from "../../../middlewares/check-registered-modules"
const route = Router() const route = Router()
@@ -81,6 +81,8 @@ export const defaultAdminVariantFields: (keyof ProductVariant)[] = [
* - options * - options
* - prices * - prices
* - product * - product
* totals:
* - purchasable
* required: * required:
* - variants * - variants
* - count * - count
@@ -1,15 +1,17 @@
import { Router } from "express"
import "reflect-metadata" import "reflect-metadata"
import { Product } from "../../../.."
import { PaginatedResponse } from "../../../../types/common"
import { FlagRouter } from "../../../../utils/flag-router"
import middlewares, { transformStoreQuery } from "../../../middlewares" import middlewares, { transformStoreQuery } from "../../../middlewares"
import { FlagRouter } from "../../../../utils/flag-router"
import { PaginatedResponse } from "../../../../types/common"
import { PricedProduct } from "../../../../types/pricing"
import { Product } from "../../../.."
import { Router } from "express"
import { StoreGetProductsParams } from "./list-products"
import { StoreGetProductsProductParams } from "./get-product"
import { extendRequestParams } from "../../../middlewares/publishable-api-key/extend-request-params" import { extendRequestParams } from "../../../middlewares/publishable-api-key/extend-request-params"
import { validateProductSalesChannelAssociation } from "../../../middlewares/publishable-api-key/validate-product-sales-channel-association" import { validateProductSalesChannelAssociation } from "../../../middlewares/publishable-api-key/validate-product-sales-channel-association"
import { validateSalesChannelParam } from "../../../middlewares/publishable-api-key/validate-sales-channel-param" import { validateSalesChannelParam } from "../../../middlewares/publishable-api-key/validate-sales-channel-param"
import { StoreGetProductsProductParams } from "./get-product"
import { StoreGetProductsParams } from "./list-products"
const route = Router() const route = Router()
@@ -122,6 +124,8 @@ export * from "./search"
* - variants * - variants
* - variants.options * - variants.options
* - variants.prices * - variants.prices
* totals:
* - variants.purchasable
* required: * required:
* - product * - product
* properties: * properties:
@@ -129,7 +133,7 @@ export * from "./search"
* $ref: "#/components/schemas/PricedProduct" * $ref: "#/components/schemas/PricedProduct"
*/ */
export type StoreProductsRes = { export type StoreProductsRes = {
product: Product product: PricedProduct
} }
/** /**
@@ -163,6 +167,8 @@ export type StorePostSearchRes = {
* - variants * - variants
* - variants.options * - variants.options
* - variants.prices * - variants.prices
* totals:
* - variants.purchasable
* required: * required:
* - products * - products
* - count * - count
@@ -184,5 +190,5 @@ export type StorePostSearchRes = {
* description: The number of items per page * description: The number of items per page
*/ */
export type StoreProductsListRes = PaginatedResponse & { export type StoreProductsListRes = PaginatedResponse & {
products: Product[] products: PricedProduct[]
} }
@@ -1,9 +1,9 @@
import { Router } from "express" import { Router } from "express"
import { PricedVariant } from "../../../../types/pricing"
import middlewares from "../../../middlewares"
import { extendRequestParams } from "../../../middlewares/publishable-api-key/extend-request-params" import { extendRequestParams } from "../../../middlewares/publishable-api-key/extend-request-params"
import { validateSalesChannelParam } from "../../../middlewares/publishable-api-key/validate-sales-channel-param" import middlewares from "../../../middlewares"
import { validateProductVariantSalesChannelAssociation } from "../../../middlewares/publishable-api-key/validate-variant-sales-channel-association" import { validateProductVariantSalesChannelAssociation } from "../../../middlewares/publishable-api-key/validate-variant-sales-channel-association"
import { validateSalesChannelParam } from "../../../middlewares/publishable-api-key/validate-sales-channel-param"
import { PricedVariant } from "../../../../types/pricing"
const route = Router() const route = Router()
@@ -29,6 +29,8 @@ export const defaultStoreVariantRelations = ["prices", "options", "product"]
* - prices * - prices
* - options * - options
* - product * - product
* totals:
* - purchasable
* required: * required:
* - variant * - variant
* properties: * properties:
@@ -48,6 +50,8 @@ export type StoreVariantsRes = {
* - prices * - prices
* - options * - options
* - product * - product
* totals:
* - purchasable
* required: * required:
* - variants * - variants
* properties: * properties:
+18 -6
View File
@@ -1,8 +1,3 @@
import { DbAwareColumn, generateEntityId } from "../utils"
import { MoneyAmount } from "./money-amount"
import { Product } from "./product"
import { ProductOptionValue } from "./product-option-value"
import { SoftDeletableEntity } from "../interfaces"
import { import {
BeforeInsert, BeforeInsert,
Column, Column,
@@ -10,10 +5,15 @@ import {
Index, Index,
JoinColumn, JoinColumn,
ManyToOne, ManyToOne,
OneToMany OneToMany,
} from "typeorm" } from "typeorm"
import { DbAwareColumn, generateEntityId } from "../utils"
import { MoneyAmount } from "./money-amount"
import { Product } from "./product"
import { ProductOptionValue } from "./product-option-value"
import { ProductVariantInventoryItem } from "./product-variant-inventory-item" import { ProductVariantInventoryItem } from "./product-variant-inventory-item"
import { SoftDeletableEntity } from "../interfaces"
@Entity() @Entity()
export class ProductVariant extends SoftDeletableEntity { export class ProductVariant extends SoftDeletableEntity {
@@ -103,6 +103,8 @@ export class ProductVariant extends SoftDeletableEntity {
@DbAwareColumn({ type: "jsonb", nullable: true }) @DbAwareColumn({ type: "jsonb", nullable: true })
metadata: Record<string, unknown> metadata: Record<string, unknown>
purchasable?: boolean
@BeforeInsert() @BeforeInsert()
private beforeInsert(): void { private beforeInsert(): void {
this.id = generateEntityId(this.id, "variant") this.id = generateEntityId(this.id, "variant")
@@ -264,4 +266,14 @@ export class ProductVariant extends SoftDeletableEntity {
* nullable: true * nullable: true
* type: object * type: object
* example: {car: "white"} * example: {car: "white"}
* purchasable:
* description: |
* Only used with the inventory modules.
* A boolean value indicating whether the Product Variant is purchasable.
* A variant is purchasable if:
* - inventory is not managed
* - it has no inventory items
* - it is in stock
* - it is backorderable.
* type: boolean
*/ */
@@ -638,28 +638,50 @@ class ProductVariantInventoryService extends TransactionBaseService {
async setVariantAvailability( async setVariantAvailability(
variants: ProductVariant[] | PricedVariant[], variants: ProductVariant[] | PricedVariant[],
salesChannelId: string | string[] | undefined salesChannelId: string | string[] | undefined,
variantInventoryMap: Map<string, ProductVariantInventoryItem[]> = new Map()
): Promise<ProductVariant[] | PricedVariant[]> { ): Promise<ProductVariant[] | PricedVariant[]> {
if (!this.inventoryService_) { if (!this.inventoryService_) {
return variants return variants
} }
if (!variantInventoryMap.size) {
const variantInventories = await this.listByVariant(
variants.map((v) => v.id)
)
variantInventories.forEach((inventory) => {
const variantId = inventory.variant_id
const currentInventories = variantInventoryMap.get(variantId) || []
currentInventories.push(inventory)
variantInventoryMap.set(variantId, currentInventories)
})
}
return await Promise.all( return await Promise.all(
variants.map(async (variant) => { variants.map(async (variant) => {
if (!variant.id) { if (!variant.id) {
return variant return variant
} }
if (!salesChannelId) { variant.purchasable = variant.allow_backorder
delete variant.inventory_quantity
if (!variant.manage_inventory) {
variant.purchasable = true
return variant return variant
} }
// first get all inventory items required for a variant const variantInventory = variantInventoryMap.get(variant.id) || []
const variantInventory = await this.listByVariant(variant.id)
if (!variantInventory.length) { if (!variantInventory.length) {
variant.inventory_quantity = 0 delete variant.inventory_quantity
variant.purchasable = true
return variant
}
if (!salesChannelId) {
delete variant.inventory_quantity
variant.purchasable = false
return variant return variant
} }
@@ -678,6 +700,9 @@ class ProductVariantInventoryService extends TransactionBaseService {
0 0
) )
variant.purchasable =
variant.inventory_quantity > 0 || variant.allow_backorder
return variant return variant
}) })
) )
+2 -1
View File
@@ -1,5 +1,6 @@
import { PriceSelectionContext } from "../interfaces/price-selection-strategy"
import { MoneyAmount, Product, ProductVariant, ShippingOption } from "../models" import { MoneyAmount, Product, ProductVariant, ShippingOption } from "../models"
import { PriceSelectionContext } from "../interfaces/price-selection-strategy"
import { TaxServiceRate } from "./tax-service" import { TaxServiceRate } from "./tax-service"
export type ProductVariantPricing = { export type ProductVariantPricing = {
+9 -7
View File
@@ -1,3 +1,9 @@
import {
DateComparisonOperator,
NumericalComparisonOperator,
StringComparisonOperator,
WithRequiredProperty,
} from "./common"
import { import {
IsBoolean, IsBoolean,
IsInt, IsInt,
@@ -7,15 +13,11 @@ import {
ValidateIf, ValidateIf,
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { IsType } from "../utils/validators/is-type" import { IsType } from "../utils/validators/is-type"
import { import { PricedVariant } from "./pricing"
DateComparisonOperator,
NumericalComparisonOperator,
StringComparisonOperator,
WithRequiredProperty,
} from "./common"
import { XorConstraint } from "./validators/xor"
import { ProductVariant } from "../models" import { ProductVariant } from "../models"
import { XorConstraint } from "./validators/xor"
export type ProductVariantPrice = { export type ProductVariantPrice = {
id?: string id?: string
+8 -7
View File
@@ -1,4 +1,4 @@
import { Transform, Type } from "class-transformer" import { DateComparisonOperator, FindConfig, Selector } from "./common"
import { import {
IsArray, IsArray,
IsBoolean, IsBoolean,
@@ -7,21 +7,22 @@ import {
IsString, IsString,
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
import { import {
PriceList, PriceList,
Product, Product,
ProductCategory,
ProductOptionValue, ProductOptionValue,
ProductStatus, ProductStatus,
SalesChannel, SalesChannel,
ProductCategory,
} from "../models" } from "../models"
import { Transform, Type } from "class-transformer"
import { FeatureFlagDecorators } from "../utils/feature-flag-decorators" import { FeatureFlagDecorators } from "../utils/feature-flag-decorators"
import { optionalBooleanMapper } from "../utils/validators/is-boolean"
import { IsType } from "../utils/validators/is-type"
import { DateComparisonOperator, FindConfig, Selector } from "./common"
import { PriceListLoadConfig } from "./price-list"
import { FindOperator } from "typeorm" import { FindOperator } from "typeorm"
import { IsType } from "../utils/validators/is-type"
import { PriceListLoadConfig } from "./price-list"
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
import { optionalBooleanMapper } from "../utils/validators/is-boolean"
/** /**
* API Level DTOs + Validation rules * API Level DTOs + Validation rules