feat(core-flows,medusa,pricing,types,utils): added price list workflows + endpoints (#6648)

Apologies for the giant PR in advance, but most of these are removing of files and migrations from old workflows and routes to new.

What:

- Adds CRUD endpoints for price lists
- Migrate workflows from old sdk to new
- Added missing updatePriceListPrices method to pricing module
This commit is contained in:
Riqwan Thamir
2024-03-15 11:09:02 +00:00
committed by GitHub
parent c3f26a6826
commit 9288f53327
81 changed files with 1959 additions and 2410 deletions
@@ -1,4 +1,4 @@
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
import { Type } from "class-transformer"
import {
IsArray,
IsDateString,
@@ -9,10 +9,9 @@ import {
IsString,
ValidateNested,
} from "class-validator"
import { Transform, Type } from "class-transformer"
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
import { CampaignBudgetType } from "@medusajs/utils"
import { transformOptionalDate } from "../../../utils/validators/date-transform"
export class AdminGetCampaignsCampaignParams extends FindParams {}
@@ -0,0 +1,68 @@
import {
removePriceListPricesWorkflow,
upsertPriceListPricesWorkflow,
} from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../types/routing"
import { listPriceLists } from "../../queries"
import {
adminPriceListRemoteQueryFields,
defaultAdminPriceListFields,
} from "../../query-config"
import {
AdminDeletePriceListsPriceListPricesReq,
AdminPostPriceListsPriceListPricesReq,
} from "../../validators"
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPriceListsPriceListPricesReq>,
res: MedusaResponse
) => {
const { prices } = req.validatedBody
const id = req.params.id
const workflow = upsertPriceListPricesWorkflow(req.scope)
const { errors } = await workflow.run({
input: {
price_lists_data: [{ id, prices }],
},
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const [[priceList]] = await listPriceLists({
container: req.scope,
remoteQueryFields: adminPriceListRemoteQueryFields,
apiFields: defaultAdminPriceListFields,
variables: { filters: { id }, skip: 0, take: 1 },
})
res.status(200).json({ price_list: priceList })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest<AdminDeletePriceListsPriceListPricesReq>,
res: MedusaResponse
) => {
const { ids } = req.validatedBody
const workflow = removePriceListPricesWorkflow(req.scope)
const { errors } = await workflow.run({
input: { ids },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
res.status(200).json({
ids,
object: "price_list_prices",
deleted: true,
})
}
@@ -1,10 +1,18 @@
import {
deletePriceListsWorkflow,
updatePriceListsWorkflow,
} from "@medusajs/core-flows"
import { MedusaError } from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { listPriceLists } from "../queries"
import { adminPriceListRemoteQueryFields } from "../query-config"
import {
adminPriceListRemoteQueryFields,
defaultAdminPriceListFields,
} from "../query-config"
import { AdminPostPriceListsPriceListReq } from "../validators"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -31,3 +39,52 @@ export const GET = async (
res.status(200).json({ price_list: priceList })
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPriceListsPriceListReq>,
res: MedusaResponse
) => {
const id = req.params.id
const workflow = updatePriceListsWorkflow(req.scope)
const { errors } = await workflow.run({
input: { price_lists_data: [{ id, ...req.validatedBody }] },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const [[priceList]] = await listPriceLists({
container: req.scope,
remoteQueryFields: adminPriceListRemoteQueryFields,
apiFields: defaultAdminPriceListFields,
variables: { filters: { id }, skip: 0, take: 1 },
})
res.status(200).json({ price_list: priceList })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const id = req.params.id
const workflow = deletePriceListsWorkflow(req.scope)
const { errors } = await workflow.run({
input: { ids: [id] },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
res.status(200).json({
id,
object: "price_list",
deleted: true,
})
}
@@ -1,10 +1,14 @@
import { transformQuery } from "../../../api/middlewares"
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { authenticate } from "../../../utils/authenticate-middleware"
import * as QueryConfig from "./query-config"
import {
AdminDeletePriceListsPriceListPricesReq,
AdminGetPriceListsParams,
AdminGetPriceListsPriceListParams,
AdminPostPriceListsPriceListPricesReq,
AdminPostPriceListsPriceListReq,
AdminPostPriceListsReq,
} from "./validators"
export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [
@@ -33,4 +37,24 @@ export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [
),
],
},
{
method: ["POST"],
matcher: "/admin/price-lists",
middlewares: [transformBody(AdminPostPriceListsReq)],
},
{
method: ["POST"],
matcher: "/admin/price-lists/:id",
middlewares: [transformBody(AdminPostPriceListsPriceListReq)],
},
{
method: ["POST"],
matcher: "/admin/price-lists/:id/prices",
middlewares: [transformBody(AdminPostPriceListsPriceListPricesReq)],
},
{
method: ["DELETE"],
matcher: "/admin/price-lists/:id/prices",
middlewares: [transformBody(AdminDeletePriceListsPriceListPricesReq)],
},
]
@@ -1,11 +1,8 @@
import {
MedusaContainer,
PriceListRuleDTO,
PriceSetMoneyAmountDTO,
ProductVariantDTO,
} from "@medusajs/types"
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
buildPriceListRules,
buildPriceSetPricesForCore,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { cleanResponseData } from "../../../../utils/clean-response-data"
@@ -37,7 +34,7 @@ export async function listPriceLists({
for (const priceList of priceLists) {
priceList.rules = buildPriceListRules(priceList.price_list_rules || [])
priceList.prices = buildPriceSetPrices(
priceList.prices = buildPriceSetPricesForCore(
priceList.price_set_money_amounts || []
)
}
@@ -48,43 +45,3 @@ export async function listPriceLists({
return [sanitizedPriceLists, metadata.count]
}
function buildPriceListRules(
priceListRules: PriceListRuleDTO[]
): Record<string, string[]> {
return priceListRules.reduce((acc, curr) => {
const ruleAttribute = curr.rule_type.rule_attribute
const ruleValues = curr.price_list_rule_values || []
if (ruleAttribute) {
acc[ruleAttribute] = ruleValues.map((ruleValue) => ruleValue.value)
}
return acc
}, {})
}
function buildPriceSetPrices(
priceSetMoneyAmounts: (PriceSetMoneyAmountDTO & {
price_set: PriceSetMoneyAmountDTO["price_set"] & {
variant?: ProductVariantDTO
}
})[]
): Record<string, any>[] {
return priceSetMoneyAmounts.map((priceSetMoneyAmount) => {
const productVariant = priceSetMoneyAmount.price_set?.variant
const rules = priceSetMoneyAmount.price_rules?.reduce((acc, curr) => {
if (curr.rule_type.rule_attribute) {
acc[curr.rule_type.rule_attribute] = curr.value
}
return acc
}, {})
return {
...priceSetMoneyAmount.money_amount,
variant_id: productVariant?.id ?? null,
rules,
}
})
}
@@ -1,9 +1,14 @@
import { createPriceListsWorkflow } from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { listPriceLists } from "./queries"
import { adminPriceListRemoteQueryFields } from "./query-config"
import {
adminPriceListRemoteQueryFields,
defaultAdminPriceListFields,
} from "./query-config"
import { AdminPostPriceListsReq } from "./validators"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -29,3 +34,31 @@ export const GET = async (
limit,
})
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPriceListsReq>,
res: MedusaResponse
) => {
const workflow = createPriceListsWorkflow(req.scope)
const { result, errors } = await workflow.run({
input: { price_lists_data: [req.validatedBody] },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const [[priceList]] = await listPriceLists({
container: req.scope,
apiFields: defaultAdminPriceListFields,
remoteQueryFields: adminPriceListRemoteQueryFields,
variables: {
filters: { id: result[0].id },
skip: 0,
take: 1,
},
})
res.status(200).json({ price_list: priceList })
}
@@ -1,4 +1,152 @@
import { PriceListStatus, PriceListType } from "@medusajs/types"
import { Transform, Type } from "class-transformer"
import {
IsArray,
IsEnum,
IsInt,
IsObject,
IsOptional,
IsString,
ValidateIf,
ValidateNested,
} from "class-validator"
import { FindParams } from "../../../types/common"
import { transformOptionalDate } from "../../../utils/validators/date-transform"
export class AdminGetPriceListsParams extends FindParams {}
export class AdminGetPriceListsPriceListParams extends FindParams {}
export class AdminPostPriceListsReq {
@IsString()
title: string
@IsString()
description: string
@IsOptional()
@Transform(transformOptionalDate)
starts_at?: string
@IsOptional()
@Transform(transformOptionalDate)
ends_at?: string
@IsOptional()
@IsEnum(PriceListStatus)
status?: PriceListStatus
@IsEnum(PriceListType)
type: PriceListType
@IsArray()
@Type(() => AdminPriceListPricesCreateReq)
@ValidateNested({ each: true })
prices: AdminPriceListPricesCreateReq[]
@IsOptional()
@IsObject()
rules?: Record<string, string[]>
}
export class AdminPriceListPricesCreateReq {
@IsString()
currency_code: string
@IsInt()
amount: number
@IsString()
variant_id: string
@IsOptional()
@IsInt()
min_quantity?: number
@IsOptional()
@IsInt()
max_quantity?: number
@IsOptional()
@IsObject()
rules?: Record<string, string>
}
export class AdminPostPriceListsPriceListReq {
@IsString()
@IsOptional()
title?: string
@IsString()
@IsOptional()
description?: string
@IsOptional()
@Transform(transformOptionalDate)
starts_at?: string
@IsOptional()
@Transform(transformOptionalDate)
ends_at?: string
@IsOptional()
@IsEnum(PriceListStatus)
status?: PriceListStatus
@IsOptional()
@IsEnum(PriceListType)
type?: PriceListType
@IsOptional()
@IsArray()
prices: (AdminPriceListPricesCreateReq | AdminPriceListPricesUpdateReq)[]
@IsOptional()
@IsObject()
rules?: Record<string, string[]>
}
export class AdminPostPriceListsPriceListPricesReq {
@IsOptional()
@IsArray()
prices: (AdminPriceListPricesCreateReq | AdminPriceListPricesUpdateReq)[]
}
export class AdminDeletePriceListsPriceListPricesReq {
@IsOptional()
@IsArray()
@IsString({ each: true })
ids: string[]
}
export class AdminPriceListPricesUpdateReq {
@IsOptional()
@IsString()
id: string
@IsOptional()
@ValidateIf((object) => !object.id)
@IsString()
currency_code?: string
@IsOptional()
@ValidateIf((object) => !object.id)
@IsInt()
amount?: number
@IsOptional()
@ValidateIf((object) => !object.id)
@IsString()
variant_id: string
@IsOptional()
@IsInt()
min_quantity?: number
@IsOptional()
@IsInt()
max_quantity?: number
@IsOptional()
@IsObject()
rules?: Record<string, string>
}