feat: Add product and pricing link on create and delete operations (#6740)

Things that remain to be done:
1. Handle product and variant updates
2. Add tests for the workflows independently
3. Align the endpoints to the new code conventions we defined
4. Finish up the update/upsert endpoints for variants

All of those can be done in a separate PR, as this is quite large already.
This commit is contained in:
Stevche Radevski
2024-03-19 17:14:02 +00:00
committed by GitHub
parent 3062605bce
commit db9c460490
26 changed files with 829 additions and 252 deletions
@@ -26,8 +26,8 @@ export const getVariantPriceSetsStep = createStep(
{
variant: {
fields: ["id"],
price: {
fields: ["price_set_id"],
price_set: {
fields: ["id"],
},
},
},
@@ -42,8 +42,8 @@ export const getVariantPriceSetsStep = createStep(
const priceSetIds: string[] = []
variantPriceSets.forEach((v) => {
if (v.price?.price_set_id) {
priceSetIds.push(v.price.price_set_id)
if (v.price_set?.id) {
priceSetIds.push(v.price_set.id)
} else {
notFound.push(v.id)
}
@@ -66,8 +66,8 @@ export const getVariantPriceSetsStep = createStep(
)
const variantToCalculatedPriceSets = variantPriceSets.reduce(
(acc, { id, price }) => {
const calculatedPriceSet = idToPriceSet.get(price?.price_set_id)
(acc, { id, price_set }) => {
const calculatedPriceSet = idToPriceSet.get(price_set?.id)
if (calculatedPriceSet) {
acc[id] = calculatedPriceSet
}
@@ -0,0 +1,31 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CreatePriceSetDTO, IPricingModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const createPriceSetsStepId = "create-price-sets"
export const createPriceSetsStep = createStep(
createPriceSetsStepId,
async (data: CreatePriceSetDTO[], { container }) => {
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const priceSets = await pricingModule.create(data)
return new StepResponse(
priceSets,
priceSets.map((priceSet) => priceSet.id)
)
},
async (priceSets, { container }) => {
if (!priceSets?.length) {
return
}
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
await pricingModule.delete(priceSets)
}
)
@@ -1,3 +1,5 @@
export * from "./create-price-sets"
export * from "./update-price-sets"
export * from "./create-pricing-rule-types"
export * from "./delete-pricing-rule-types"
export * from "./update-pricing-rule-types"
@@ -0,0 +1,48 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService, UpdatePriceSetDTO } from "@medusajs/types"
import {
convertItemResponseToUpdateRequest,
getSelectsAndRelationsFromObjectArray,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const updatePriceSetsStepId = "update-price-sets"
export const updatePriceSetsStep = createStep(
updatePriceSetsStepId,
async (data: UpdatePriceSetDTO[], { container }) => {
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data)
const dataBeforeUpdate = await pricingModule.list(
{ id: data.map((d) => d.id) },
{ relations, select: selects }
)
const updatedPriceSets = await pricingModule.update(data)
return new StepResponse(updatedPriceSets, {
dataBeforeUpdate,
selects,
relations,
})
},
async (revertInput, { container }) => {
if (!revertInput) {
return
}
const { dataBeforeUpdate = [], selects, relations } = revertInput
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
await pricingModule.update(
dataBeforeUpdate.map((data) =>
convertItemResponseToUpdateRequest(data, selects, relations)
)
)
}
)
@@ -9,7 +9,6 @@ export const createProductVariantsStep = createStep(
const service = container.resolve<IProductModuleService>(
ModuleRegistrationName.PRODUCT
)
const created = await service.createVariants(data)
return new StepResponse(
created,
@@ -0,0 +1,47 @@
import { Modules } from "@medusajs/modules-sdk"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type StepInput = {
links: {
variant_id: string
price_set_id: string
}[]
}
export const createVariantPricingLinkStepId = "create-variant-pricing-link"
export const createVariantPricingLinkStep = createStep(
createVariantPricingLinkStepId,
async (data: StepInput, { container }) => {
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
await remoteLink.create(
data.links.map((entry) => ({
[Modules.PRODUCT]: {
variant_id: entry.variant_id,
},
[Modules.PRICING]: {
price_set_id: entry.price_set_id,
},
}))
)
return new StepResponse(void 0, data)
},
async (data, { container }) => {
if (!data?.links?.length) {
return
}
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
const links = data.links.map((entry) => ({
[Modules.PRODUCT]: {
variant_id: entry.variant_id,
},
[Modules.PRICING]: {
price_set_id: entry.price_set_id,
},
}))
await remoteLink.dismiss(links)
}
)
@@ -0,0 +1,23 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IProductModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type StepInput = {
ids: string[]
}
export const getProductsStepId = "get-products"
export const getProductsStep = createStep(
getProductsStepId,
async (data: StepInput, { container }) => {
const service = container.resolve<IProductModuleService>(
ModuleRegistrationName.PRODUCT
)
const products = await service.list(
{ id: data.ids },
{ relations: ["variants"], take: null }
)
return new StepResponse(products, products)
}
)
@@ -1,6 +1,9 @@
export * from "./create-products"
export * from "./update-products"
export * from "./delete-products"
export * from "./get-products"
export * from "./create-variant-pricing-link"
export * from "./remove-variant-pricing-link"
export * from "./create-product-options"
export * from "./update-product-options"
export * from "./delete-product-options"
@@ -0,0 +1,49 @@
import { Modules } from "@medusajs/modules-sdk"
import { ILinkModule } from "@medusajs/types"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type StepInput = {
variant_ids: string[]
}
export const removeVariantPricingLinkStepId = "remove-variant-pricing-link"
export const removeVariantPricingLinkStep = createStep(
removeVariantPricingLinkStepId,
async (data: StepInput, { container }) => {
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
const linkModule: ILinkModule = remoteLink.getLinkModule(
Modules.PRODUCT,
"variant_id",
Modules.PRICING,
"price_set_id"
)
const links = (await linkModule.list(
{
variant_id: data.variant_ids,
},
{ select: ["id", "variant_id", "price_set_id"] }
)) as { id: string; variant_id: string; price_set_id: string }[]
await remoteLink.delete(links.map((link) => link.id))
return new StepResponse(void 0, links)
},
async (prevData, { container }) => {
if (!prevData?.length) {
return
}
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
await remoteLink.create(
prevData.map((entry) => ({
[Modules.PRODUCT]: {
variant_id: entry.variant_id,
},
[Modules.PRICING]: {
price_set_id: entry.price_set_id,
},
}))
)
}
)
@@ -1,9 +1,20 @@
import { ProductTypes } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createProductVariantsStep } from "../steps"
import { ProductTypes, PricingTypes } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import {
createProductVariantsStep,
createVariantPricingLinkStep,
} from "../steps"
import { createPriceSetsStep } from "../../pricing"
// TODO: Create separate typings for the workflow input
type WorkflowInput = {
product_variants: ProductTypes.CreateProductVariantDTO[]
product_variants: (ProductTypes.CreateProductVariantDTO & {
prices?: PricingTypes.CreateMoneyAmountDTO[]
})[]
}
export const createProductVariantsWorkflowId = "create-product-variants"
@@ -12,6 +23,71 @@ export const createProductVariantsWorkflow = createWorkflow(
(
input: WorkflowData<WorkflowInput>
): WorkflowData<ProductTypes.ProductVariantDTO[]> => {
return createProductVariantsStep(input.product_variants)
// Passing prices to the product module will fail, we want to keep them for after the variant is created.
const variantsWithoutPrices = transform({ input }, (data) =>
data.input.product_variants.map((v) => ({
...v,
prices: undefined,
}))
)
const createdVariants = createProductVariantsStep(variantsWithoutPrices)
// Note: We rely on the same order of input and output when creating variants here, make sure that assumption holds
const variantsWithAssociatedPrices = transform(
{ input, createdVariants },
(data) =>
data.createdVariants
.map((variant, i) => {
return {
id: variant.id,
prices: data.input.product_variants[i]?.prices,
}
})
.flat()
.filter((v) => !!v.prices?.length)
)
// TODO: From here until the final transform the code is the same as when creating a product, we can probably refactor
const createdPriceSets = createPriceSetsStep(variantsWithAssociatedPrices)
const variantAndPriceSets = transform(
{ variantsWithAssociatedPrices, createdPriceSets },
(data) => {
return data.variantsWithAssociatedPrices.map((variant, i) => ({
variant: variant,
price_set: data.createdPriceSets[i],
}))
}
)
const variantAndPriceSetLinks = transform(
{ variantAndPriceSets },
(data) => {
return {
links: data.variantAndPriceSets.map((entry) => ({
variant_id: entry.variant.id,
price_set_id: entry.price_set.id,
})),
}
}
)
createVariantPricingLinkStep(variantAndPriceSetLinks)
return transform(
{
createdVariants,
variantAndPriceSets,
},
(data) => {
return data.createdVariants.map((variant) => ({
...variant,
price_set: data.variantAndPriceSets.find(
(v) => v.variant.id === variant.id
)?.price_set,
}))
}
)
}
)
@@ -1,8 +1,21 @@
import { ProductTypes } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createProductsStep } from "../steps"
import { ProductTypes, PricingTypes } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { createProductsStep, createVariantPricingLinkStep } from "../steps"
import { createPriceSetsStep } from "../../pricing"
type WorkflowInput = { products: ProductTypes.CreateProductDTO[] }
// TODO: We should have separate types here as input, not the module DTO. Eg. the HTTP request that we are handling
// has different data than the DTO, so that needs to be represented differently.
type WorkflowInput = {
products: (Omit<ProductTypes.CreateProductDTO, "variants"> & {
variants?: (ProductTypes.CreateProductVariantDTO & {
prices?: PricingTypes.CreateMoneyAmountDTO[]
})[]
})[]
}
export const createProductsWorkflowId = "create-products"
export const createProductsWorkflow = createWorkflow(
@@ -10,6 +23,78 @@ export const createProductsWorkflow = createWorkflow(
(
input: WorkflowData<WorkflowInput>
): WorkflowData<ProductTypes.ProductDTO[]> => {
return createProductsStep(input.products)
// Passing prices to the product module will fail, we want to keep them for after the product is created.
const productWithoutPrices = transform({ input }, (data) =>
data.input.products.map((p) => ({
...p,
variants: p.variants?.map((v) => ({
...v,
prices: undefined,
})),
}))
)
const createdProducts = createProductsStep(productWithoutPrices)
// Note: We rely on the same order of input and output when creating products here, make sure that assumption holds
const variantsWithAssociatedPrices = transform(
{ input, createdProducts },
(data) => {
return data.createdProducts
.map((p, i) => {
const inputProduct = data.input.products[i]
return p.variants?.map((v, j) => ({
id: v.id,
prices: inputProduct?.variants?.[j]?.prices,
}))
})
.flat()
.filter((v) => !!v.prices?.length)
}
)
const createdPriceSets = createPriceSetsStep(variantsWithAssociatedPrices)
const variantAndPriceSets = transform(
{ variantsWithAssociatedPrices, createdPriceSets },
(data) =>
data.variantsWithAssociatedPrices.map((variant, i) => ({
variant: variant,
price_set: data.createdPriceSets[i],
}))
)
const variantAndPriceSetLinks = transform(
{ variantAndPriceSets },
(data) => {
return {
links: data.variantAndPriceSets.map((entry) => ({
variant_id: entry.variant.id,
price_set_id: entry.price_set.id,
})),
}
}
)
createVariantPricingLinkStep(variantAndPriceSetLinks)
// TODO: Should we just refetch the products here?
return transform(
{
createdProducts,
variantAndPriceSets,
},
(data) => {
return data.createdProducts.map((product) => ({
...product,
variants: product.variants?.map((variant) => ({
...variant,
price_set: data.variantAndPriceSets.find(
(v) => v.variant.id === variant.id
)?.price_set,
})),
}))
}
)
}
)
@@ -1,5 +1,8 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { deleteProductVariantsStep } from "../steps"
import {
deleteProductVariantsStep,
removeVariantPricingLinkStep,
} from "../steps"
type WorkflowInput = { ids: string[] }
@@ -7,6 +10,8 @@ export const deleteProductVariantsWorkflowId = "delete-product-variants"
export const deleteProductVariantsWorkflow = createWorkflow(
deleteProductVariantsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
// Question: Should we also remove the price set manually, or would that be cascaded?
removeVariantPricingLinkStep({ variant_ids: input.ids })
return deleteProductVariantsStep(input.ids)
}
)
@@ -1,5 +1,13 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { deleteProductsStep } from "../steps"
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import {
deleteProductsStep,
getProductsStep,
removeVariantPricingLinkStep,
} from "../steps"
type WorkflowInput = { ids: string[] }
@@ -7,6 +15,17 @@ export const deleteProductsWorkflowId = "delete-products"
export const deleteProductsWorkflow = createWorkflow(
deleteProductsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
const productsToDelete = getProductsStep({ ids: input.ids })
const variantsToBeDeleted = transform({ productsToDelete }, (data) => {
return data.productsToDelete
.flatMap((product) => product.variants)
.map((variant) => variant.id)
})
// Question: Should we also remove the price set manually, or would that be cascaded?
// Question: Since we soft-delete the product, how do we restore the product with the prices and the links?
removeVariantPricingLinkStep({ variant_ids: variantsToBeDeleted })
return deleteProductsStep(input.ids)
}
)
@@ -41,11 +41,14 @@ export const ProductVariantPriceSet: ModuleJoinerConfig = {
extends: [
{
serviceName: Modules.PRODUCT,
fieldAlias: {
price_set: "price_set_link.price_set",
},
relationship: {
serviceName: LINKS.ProductVariantPriceSet,
primaryKey: "variant_id",
foreignKey: "id",
alias: "price",
alias: "price_set_link",
},
},
{
@@ -25,7 +25,7 @@ export const GET = async (
const queryObject = remoteQueryObjectFromString({
entryPoint: "product_option",
variables,
fields: req.retrieveConfig.select as string[],
fields: req.remoteQueryConfig.fields,
})
const [product_option] = await remoteQuery(queryObject)
@@ -22,7 +22,7 @@ export const GET = async (
skip: req.listConfig.skip,
take: req.listConfig.take,
},
fields: req.listConfig.select as string[],
fields: req.remoteQueryConfig.fields,
})
const { rows: product_options, metadata } = await remoteQuery(queryObject)
@@ -9,6 +9,7 @@ import {
import { UpdateProductDTO } from "@medusajs/types"
import { remoteQueryObjectFromString } from "@medusajs/utils"
import { remapKeysForProduct, remapProduct } from "../helpers"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -18,15 +19,16 @@ export const GET = async (
const variables = { id: req.params.id }
const selectFields = remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
const queryObject = remoteQueryObjectFromString({
entryPoint: "product",
variables,
fields: req.retrieveConfig.select as string[],
fields: selectFields,
})
const [product] = await remoteQuery(queryObject)
res.status(200).json({ product })
res.status(200).json({ product: remapProduct(product) })
}
export const POST = async (
@@ -45,7 +47,7 @@ export const POST = async (
throw errors[0].error
}
res.status(200).json({ product: result[0] })
res.status(200).json({ product: remapProduct(result[0]) })
}
export const DELETE = async (
@@ -10,6 +10,7 @@ import {
import { UpdateProductVariantDTO } from "@medusajs/types"
import { defaultAdminProductsVariantFields } from "../../../query-config"
import { remoteQueryObjectFromString } from "@medusajs/utils"
import { remapKeysForVariant, remapVariant } from "../../../helpers"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -26,11 +27,11 @@ export const GET = async (
const queryObject = remoteQueryObjectFromString({
entryPoint: "variant",
variables,
fields: req.retrieveConfig.select as string[],
fields: remapKeysForVariant(req.remoteQueryConfig.fields ?? []),
})
const [variant] = await remoteQuery(queryObject)
res.status(200).json({ variant })
res.status(200).json({ variant: remapVariant(variant) })
}
export const POST = async (
@@ -55,7 +56,7 @@ export const POST = async (
throw errors[0].error
}
res.status(200).json({ variant: result[0] })
res.status(200).json({ variant: remapVariant(result[0]) })
}
export const DELETE = async (
@@ -6,6 +6,12 @@ import {
import { CreateProductVariantDTO } from "@medusajs/types"
import { createProductVariantsWorkflow } from "@medusajs/core-flows"
import { remoteQueryObjectFromString } from "@medusajs/utils"
import {
remapKeysForProduct,
remapKeysForVariant,
remapProduct,
remapVariant,
} from "../../helpers"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -22,13 +28,13 @@ export const GET = async (
skip: req.listConfig.skip,
take: req.listConfig.take,
},
fields: req.listConfig.select as string[],
fields: remapKeysForVariant(req.remoteQueryConfig.fields ?? []),
})
const { rows: variants, metadata } = await remoteQuery(queryObject)
res.json({
variants,
variants: variants.map(remapVariant),
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
@@ -58,5 +64,15 @@ export const POST = async (
throw errors[0].error
}
res.status(200).json({ variant: result[0] })
const remoteQuery = req.scope.resolve("remoteQuery")
const queryObject = remoteQueryObjectFromString({
entryPoint: "product",
variables: {
filters: { id: productId },
},
fields: remapKeysForProduct(req.remoteQueryConfig.fields ?? []),
})
const products = await remoteQuery(queryObject)
res.status(200).json({ product: remapProduct(products[0]) })
}
@@ -0,0 +1,46 @@
import { ProductDTO, ProductVariantDTO } from "@medusajs/types"
// The variant had prices before, but that is not part of the price_set money amounts. Do we remap the request and response or not?
export const remapKeysForProduct = (selectFields: string[]) => {
const productFields = selectFields.filter(
(fieldName: string) => !fieldName.startsWith("variants.prices")
)
const pricingFields = selectFields
.filter((fieldName: string) => fieldName.startsWith("variants.prices"))
.map((fieldName: string) =>
fieldName.replace("variants.prices.", "variants.price_set.money_amounts.")
)
return [...productFields, ...pricingFields]
}
export const remapKeysForVariant = (selectFields: string[]) => {
const variantFields = selectFields.filter(
(fieldName: string) => !fieldName.startsWith("prices")
)
const pricingFields = selectFields
.filter((fieldName: string) => fieldName.startsWith("prices"))
.map((fieldName: string) =>
fieldName.replace("prices.", "price_set.money_amounts.")
)
return [...variantFields, ...pricingFields]
}
export const remapProduct = (p: ProductDTO) => {
return {
...p,
variants: p.variants?.map(remapVariant),
}
}
export const remapVariant = (v: ProductVariantDTO) => {
return {
...v,
prices: (v as any).price_set?.money_amounts?.map((ma) => ({
...ma,
variant_id: v.id,
})),
price_set: undefined,
}
}
@@ -83,7 +83,14 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["POST"],
matcher: "/admin/products/:id/variants",
middlewares: [transformBody(AdminPostProductsProductVariantsReq)],
middlewares: [
transformBody(AdminPostProductsProductVariantsReq),
// We specify the product here as that's what we return after updating the variant
transformQuery(
AdminGetProductsProductParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
@@ -22,6 +22,11 @@ export const defaultAdminProductsVariantFields = [
"ean",
"upc",
"barcode",
"prices.id",
"prices.currency_code",
"prices.amount",
"prices.created_at",
"prices.updated_at",
"options.id",
"options.option_value.value",
"options.option_value.option.title",
@@ -55,7 +60,6 @@ export const listOptionConfig = {
/* export const allowedAdminProductRelations = [
"variants",
// TODO: Add in next iteration
// "variants.prices",
"variants.options",
"images",
@@ -11,13 +11,13 @@ import {
} from "../../../types/routing"
import { listPriceLists } from "../price-lists/queries"
import { AdminGetProductsParams } from "./validators"
import { remapKeysForProduct, remapProduct } from "./helpers"
import { MedusaContainer } from "medusa-core-utils"
export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetProductsParams>,
res: MedusaResponse
const applyVariantFiltersForPriceList = async (
scope: MedusaContainer,
filterableFields: AdminGetProductsParams
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const filterableFields: AdminGetProductsParams = { ...req.filterableFields }
const filterByPriceListIds = filterableFields.price_list_id
const priceListVariantIds: string[] = []
@@ -25,17 +25,17 @@ export const GET = async (
// the variant IDs through the price list price sets.
if (Array.isArray(filterByPriceListIds)) {
const [priceLists] = await listPriceLists({
container: req.scope,
container: scope,
remoteQueryFields: ["price_set_money_amounts.price_set.variant.id"],
apiFields: ["prices.variant_id"],
variables: { filters: { id: filterByPriceListIds }, skip: 0, take: null },
})
priceListVariantIds.push(
...(priceLists
...((priceLists
.map((priceList) => priceList.prices?.map((price) => price.variant_id))
.flat(2)
.filter(isString) || [])
.filter(isString) || []) as string[])
)
delete filterableFields.price_list_id
@@ -50,19 +50,34 @@ export const GET = async (
}
}
return filterableFields
}
export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetProductsParams>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
let filterableFields: AdminGetProductsParams = { ...req.filterableFields }
filterableFields = await applyVariantFiltersForPriceList(
req.scope,
filterableFields
)
const selectFields = remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
const queryObject = remoteQueryObjectFromString({
entryPoint: "product",
variables: {
filters: filterableFields,
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
fields: selectFields,
})
const { rows: products, metadata } = await remoteQuery(queryObject)
res.json({
products,
products: products.map(remapProduct),
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
@@ -88,5 +103,5 @@ export const POST = async (
throw errors[0].error
}
res.status(200).json({ product: result[0] })
res.status(200).json({ product: remapProduct(result[0]) })
}
@@ -5,11 +5,13 @@ import {
IsArray,
IsBoolean,
IsEnum,
IsInt,
IsNumber,
IsObject,
IsOptional,
IsString,
NotEquals,
Validate,
ValidateIf,
ValidateNested,
} from "class-validator"
@@ -17,6 +19,7 @@ import { FindParams, extendedFindParamsMixin } from "../../../types/common"
import { OperatorMapValidator } from "../../../types/validators/operator-map"
import { IsType } from "../../../utils"
import { optionalBooleanMapper } from "../../../utils/validators/is-boolean"
import { XorConstraint } from "../../../types/validators/xor"
export class AdminGetProductsProductParams extends FindParams {}
export class AdminGetProductsProductVariantsVariantParams extends FindParams {}
@@ -537,13 +540,10 @@ export class AdminPostProductsProductVariantsReq {
@IsOptional()
metadata?: Record<string, unknown>
// TODO: Add on next iteration, adding temporary field for now
// @IsArray()
// @ValidateNested({ each: true })
// @Type(() => ProductVariantPricesCreateReq)
// prices: ProductVariantPricesCreateReq[]
@IsArray()
prices: any[]
@ValidateNested({ each: true })
@Type(() => ProductVariantPricesCreateReq)
prices: ProductVariantPricesCreateReq[]
@IsOptional()
@IsObject()
@@ -619,12 +619,11 @@ export class AdminPostProductsProductVariantsVariantReq {
@IsOptional()
metadata?: Record<string, unknown>
// TODO: Deal with in next iteration
// @IsArray()
// @IsOptional()
// @ValidateNested({ each: true })
// @Type(() => ProductVariantPricesUpdateReq)
// prices?: ProductVariantPricesUpdateReq[]
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => ProductVariantPricesUpdateReq)
prices?: ProductVariantPricesUpdateReq[]
@IsOptional()
@IsObject()
@@ -679,3 +678,41 @@ export class ProductTypeReq {
@IsString()
value: string
}
// TODO: Add support for rules
export class ProductVariantPricesCreateReq {
@IsString()
currency_code: string
@IsInt()
amount: number
@IsOptional()
@IsInt()
min_quantity?: number
@IsOptional()
@IsInt()
max_quantity?: number
}
export class ProductVariantPricesUpdateReq {
@IsString()
@IsOptional()
id?: string
@IsString()
@IsOptional()
currency_code?: string
@IsInt()
amount: number
@IsOptional()
@IsInt()
min_quantity?: number
@IsOptional()
@IsInt()
max_quantity?: number
}
@@ -1,5 +1,5 @@
import { BaseFilterable } from "../../dal";
import { CreatePriceSetPriceRules } from "./price-list";
import { BaseFilterable } from "../../dal"
import { CreatePriceSetPriceRules } from "./price-list"
import {
CreateMoneyAmountDTO,
FilterableMoneyAmountProps,