chore: add batch update prices for price lists (#6999)

This commit is contained in:
Riqwan Thamir
2024-04-08 09:23:23 +02:00
committed by GitHub
parent dd35a4dbff
commit 5e953f8cf3
12 changed files with 166 additions and 293 deletions
@@ -422,7 +422,7 @@ medusaIntegrationTestRunner({
)
})
it("should update price lists and set prices successfully", async () => {
it("should update price lists", async () => {
await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
@@ -446,14 +446,6 @@ medusaIntegrationTestRunner({
rules: {
customer_group_id: [customerGroup.id],
},
prices: [
{
amount: 400,
variant_id: variant.id,
currency_code: "usd",
rules: { region_id: region.id },
},
],
}
let response = await api.post(
@@ -471,49 +463,6 @@ medusaIntegrationTestRunner({
rules: {
customer_group_id: [customerGroup.id],
},
prices: [
{
id: expect.any(String),
currency_code: "usd",
amount: 400,
min_quantity: null,
max_quantity: null,
variant_id: variant.id,
rules: {
region_id: region.id,
},
},
],
})
)
// Updating prices should remove existing prices and create new ones
response = await api.post(
`admin/price-lists/${priceList.id}`,
{
prices: [
{
amount: 600,
variant_id: variant.id,
currency_code: "usd",
rules: { region_id: region.id },
},
],
},
adminHeaders
)
expect(response.data.price_list).toEqual(
expect.objectContaining({
prices: [
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
amount: 600,
variant_id: variant.id,
rules: { region_id: region.id },
}),
],
})
)
})
@@ -582,6 +531,65 @@ medusaIntegrationTestRunner({
})
})
describe("POST /admin/price-lists/:id/prices/batch/update", () => {
it("should update price list prices successfully", async () => {
const priceSet = await createVariantPriceSet({
container: appContainer,
variantId: variant.id,
prices: [{ amount: 3000, currency_code: "usd" }],
})
const [priceList] = await pricingModule.createPriceLists([
{
title: "test price list",
description: "test",
prices: [
{
id: "test-price-id",
amount: 5000,
currency_code: "usd",
price_set_id: priceSet.id,
rules: { region_id: region.id },
},
],
},
])
const data = {
prices: [
{
id: "test-price-id",
amount: 400,
variant_id: variant.id,
currency_code: "usd",
rules: { region_id: region.id },
},
],
}
const response = await api.post(
`admin/price-lists/${priceList.id}/prices/batch/update`,
data,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.price_list.prices.length).toEqual(1)
expect(response.data.price_list).toEqual(
expect.objectContaining({
id: expect.any(String),
prices: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
amount: 400,
}),
]),
})
)
})
})
describe("POST /admin/price-lists/:id/prices/batch/remove", () => {
it("should remove price list prices successfully", async () => {
const priceSet = await createVariantPriceSet({
@@ -5,6 +5,5 @@ export * from "./get-existing-price-lists-price-ids"
export * from "./remove-price-list-prices"
export * from "./update-price-list-prices"
export * from "./update-price-lists"
export * from "./upsert-price-list-prices"
export * from "./validate-price-lists"
export * from "./validate-variant-price-links"
@@ -1,123 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
AddPriceListPricesDTO,
CreatePriceListPriceDTO,
CreatePriceListPriceWorkflowDTO,
IPricingModuleService,
PriceDTO,
UpdatePriceListPriceDTO,
UpdatePriceListPriceWorkflowDTO,
UpdatePriceListPricesDTO,
UpsertPriceListPricesWorkflowStepDTO,
} from "@medusajs/types"
import { buildPriceSetPricesForModule, promiseAll } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const upsertPriceListPricesStepId = "upsert-price-list-prices"
export const upsertPriceListPricesStep = createStep(
upsertPriceListPricesStepId,
async (stepInput: UpsertPriceListPricesWorkflowStepDTO, { container }) => {
const { data, variant_price_map: variantPriceSetMap } = stepInput
const priceListPricesToUpdate: UpdatePriceListPricesDTO[] = []
const priceListPricesToAdd: AddPriceListPricesDTO[] = []
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
for (const upsertPriceListPricesData of data) {
const { prices = [], id } = upsertPriceListPricesData
const pricesToAdd: CreatePriceListPriceDTO[] = []
const pricesToUpdate: UpdatePriceListPriceDTO[] = []
for (const price of prices) {
const priceSetId = variantPriceSetMap[price.variant_id!]
if (isPriceUpdate(price)) {
pricesToUpdate.push({ ...price, price_set_id: priceSetId })
} else {
pricesToAdd.push({ ...price, price_set_id: priceSetId })
}
}
if (pricesToUpdate.length) {
priceListPricesToUpdate.push({
price_list_id: id,
prices: pricesToUpdate,
})
}
if (pricesToAdd.length) {
priceListPricesToAdd.push({
price_list_id: id,
prices: pricesToAdd,
})
}
}
const updatedPrices = await pricingModule.listPrices(
{
id: priceListPricesToUpdate
.map((priceListData) => priceListData.prices.map((price) => price.id))
.filter(Boolean)
.flat(1),
},
{ relations: ["price_list"] }
)
const priceListPricesMap = new Map<string, PriceDTO[]>()
const dataBeforePriceUpdate: UpdatePriceListPricesDTO[] = []
for (const updatedPrice of updatedPrices) {
const priceListId = updatedPrice.price_list!.id
const prices = priceListPricesMap.get(priceListId) || []
priceListPricesMap.set(priceListId, prices)
}
for (const [priceListId, prices] of Object.entries(priceListPricesMap)) {
dataBeforePriceUpdate.push({
price_list_id: priceListId,
prices: buildPriceSetPricesForModule(prices),
})
}
// TODO: `addPriceListPrices` will return a list of price lists
// This should be reworked to return prices instead, as we need to
// do a revert incase this step fails
const [createdPriceListPrices, _] = await promiseAll([
pricingModule.addPriceListPrices(priceListPricesToAdd),
pricingModule.updatePriceListPrices(priceListPricesToUpdate),
])
return new StepResponse(null, {
createdPriceListPrices,
updatedPriceListPrices: dataBeforePriceUpdate,
})
},
async (data, { container }) => {
if (!data) {
return
}
const { createdPriceListPrices = [], updatedPriceListPrices = [] } = data
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
if (createdPriceListPrices.length) {
await pricingModule.removePrices(createdPriceListPrices.map((p) => p.id))
}
if (updatedPriceListPrices.length) {
await pricingModule.updatePriceListPrices(updatedPriceListPrices)
}
}
)
function isPriceUpdate(
data: UpdatePriceListPriceWorkflowDTO | CreatePriceListPriceWorkflowDTO
): data is UpdatePriceListPriceWorkflowDTO {
return "id" in data
}
@@ -1,4 +1,3 @@
import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
MedusaError,
@@ -10,7 +9,11 @@ export const validateVariantPriceLinksStepId = "validate-variant-price-links"
export const validateVariantPriceLinksStep = createStep(
validateVariantPriceLinksStepId,
async (
data: Pick<UpdatePriceListWorkflowInputDTO, "prices">[],
data: {
prices: {
variant_id: string
}[]
}[],
{ container }
) => {
const remoteQuery = container.resolve(
@@ -18,7 +21,7 @@ export const validateVariantPriceLinksStep = createStep(
)
const variantIds: string[] = data
.map((pl) => pl?.prices?.map((price) => price.variant_id!) || [])
.map((pl) => pl?.prices?.map((price) => price.variant_id) || [])
.filter(Boolean)
.flat(1)
@@ -2,5 +2,5 @@ export * from "./create-price-list-prices"
export * from "./create-price-lists"
export * from "./delete-price-lists"
export * from "./remove-price-list-prices"
export * from "./update-price-list-prices"
export * from "./update-price-lists"
export * from "./upsert-price-list-prices"
@@ -0,0 +1,31 @@
import { UpdatePriceListPricesWorkflowDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
} from "@medusajs/workflows-sdk"
import {
updatePriceListPricesStep,
validatePriceListsStep,
validateVariantPriceLinksStep,
} from "../steps"
export const updatePriceListPricesWorkflowId = "update-price-list-prices"
export const updatePriceListPricesWorkflow = createWorkflow(
updatePriceListPricesWorkflowId,
(
input: WorkflowData<{
data: UpdatePriceListPricesWorkflowDTO[]
}>
): WorkflowData<void> => {
const [_, variantPriceMap] = parallelize(
validatePriceListsStep(input.data),
validateVariantPriceLinksStep(input.data)
)
updatePriceListPricesStep({
data: input.data,
variant_price_map: variantPriceMap,
})
}
)
@@ -1,18 +1,6 @@
import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import {
createPriceListPricesStep,
getExistingPriceListsPriceIdsStep,
removePriceListPricesStep,
updatePriceListsStep,
validatePriceListsStep,
validateVariantPriceLinksStep,
} from "../steps"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { updatePriceListsStep, validatePriceListsStep } from "../steps"
export const updatePriceListsWorkflowId = "update-price-lists"
export const updatePriceListsWorkflow = createWorkflow(
@@ -20,41 +8,8 @@ export const updatePriceListsWorkflow = createWorkflow(
(
input: WorkflowData<{ price_lists_data: UpdatePriceListWorkflowInputDTO[] }>
): WorkflowData<void> => {
const [priceListsMap, variantPriceMap] = parallelize(
validatePriceListsStep(input.price_lists_data),
validateVariantPriceLinksStep(input.price_lists_data)
)
validatePriceListsStep(input.price_lists_data)
const getPriceListPricesInput = transform({ priceListsMap }, (data) => ({
price_list_ids: Object.keys(data.priceListsMap),
}))
const priceListPriceIdMap = getExistingPriceListsPriceIdsStep(
getPriceListPricesInput
)
const removePriceListPricesInput = transform(
{ priceListPriceIdMap },
(data) => Object.values(data.priceListPriceIdMap).flat(1)
)
removePriceListPricesStep(removePriceListPricesInput)
const updatePricesInput = transform({ variantPriceMap, input }, (data) => ({
data: data.input.price_lists_data,
variant_price_map: data.variantPriceMap,
}))
createPriceListPricesStep(updatePricesInput)
const updatePriceListInput = transform({ input }, (data) => {
return data.input.price_lists_data.map((priceListData) => {
delete priceListData.prices
return priceListData
})
})
updatePriceListsStep(updatePriceListInput)
updatePriceListsStep(input.price_lists_data)
}
)
@@ -1,31 +0,0 @@
import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
} from "@medusajs/workflows-sdk"
import {
upsertPriceListPricesStep,
validatePriceListsStep,
validateVariantPriceLinksStep,
} from "../steps"
export const upsertPriceListPricesWorkflowId = "upsert-price-list-prices"
export const upsertPriceListPricesWorkflow = createWorkflow(
upsertPriceListPricesWorkflowId,
(
input: WorkflowData<{
price_lists_data: Pick<UpdatePriceListWorkflowInputDTO, "id" | "prices">[]
}>
): WorkflowData<void> => {
const [_, variantPriceMap] = parallelize(
validatePriceListsStep(input.price_lists_data),
validateVariantPriceLinksStep(input.price_lists_data)
)
upsertPriceListPricesStep({
data: input.price_lists_data,
variant_price_map: variantPriceMap,
})
}
)
@@ -0,0 +1,38 @@
import { updatePriceListPricesWorkflow } from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../../../types/routing"
import { getPriceList } from "../../../../queries"
import {
adminPriceListRemoteQueryFields,
defaultAdminPriceListFields,
} from "../../../../query-config"
import { AdminPostPriceListPriceBatchUpdate } from "../../../../validators"
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPriceListPriceBatchUpdate>,
res: MedusaResponse
) => {
const { prices } = req.validatedBody
const id = req.params.id
const workflow = updatePriceListPricesWorkflow(req.scope)
const { errors } = await workflow.run({
input: { data: [{ id, prices }] },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const priceList = await getPriceList({
id,
container: req.scope,
remoteQueryFields: adminPriceListRemoteQueryFields,
apiFields: defaultAdminPriceListFields,
})
res.status(200).json({ price_list: priceList })
}
@@ -5,6 +5,7 @@ import * as QueryConfig from "./query-config"
import {
AdminGetPriceListsParams,
AdminGetPriceListsPriceListParams,
AdminPostPriceListPriceBatchUpdate,
AdminPostPriceListsPriceListPricesBatchAddReq,
AdminPostPriceListsPriceListPricesBatchRemoveReq,
AdminPostPriceListsPriceListReq,
@@ -59,4 +60,9 @@ export const adminPriceListsRoutesMiddlewares: MiddlewareRoute[] = [
transformBody(AdminPostPriceListsPriceListPricesBatchRemoveReq),
],
},
{
method: ["POST"],
matcher: "/admin/price-lists/:id/prices/batch/update",
middlewares: [transformBody(AdminPostPriceListPriceBatchUpdate)],
},
]
@@ -7,7 +7,6 @@ import {
IsObject,
IsOptional,
IsString,
ValidateIf,
ValidateNested,
} from "class-validator"
import { FindParams } from "../../../types/common"
@@ -111,32 +110,33 @@ export class AdminPostPriceListsPriceListPricesBatchAddReq {
prices: AdminPriceListPricesCreateReq[]
}
export class AdminPostPriceListPriceBatchUpdate {
@IsOptional()
@IsArray()
prices: AdminPostPriceListPriceUpdate[]
}
export class AdminPostPriceListsPriceListPricesBatchRemoveReq {
@IsArray()
@IsString({ each: true })
ids: string[]
}
export class AdminPriceListPricesUpdateReq {
@IsOptional()
export class AdminPostPriceListPriceUpdate {
@IsString()
id: string
@IsString()
variant_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
+10 -23
View File
@@ -9,16 +9,6 @@ export interface CreatePriceListPriceWorkflowDTO {
rules?: Record<string, string>
}
export interface UpdatePriceListPriceWorkflowDTO {
id: string
amount?: number
currency_code?: string
variant_id?: string
max_quantity?: number
min_quantity?: number
rules?: Record<string, string>
}
export interface CreatePriceListWorkflowInputDTO {
title: string
description: string
@@ -26,7 +16,7 @@ export interface CreatePriceListWorkflowInputDTO {
ends_at?: string | null
status?: PriceListStatus
rules?: Record<string, string[]>
prices?: CreatePriceListPriceWorkflowDTO[]
prices: CreatePriceListPriceWorkflowDTO[]
}
export interface UpdatePriceListWorkflowInputDTO {
@@ -37,12 +27,19 @@ export interface UpdatePriceListWorkflowInputDTO {
ends_at?: string | null
status?: PriceListStatus
rules?: Record<string, string[]>
prices?: CreatePriceListPriceWorkflowDTO[]
}
export interface UpdatePriceListPricesWorkflowDTO {
id: string
prices: UpdatePriceListPriceWorkflowDTO[]
prices: {
id: string
variant_id: string
amount?: number
currency_code?: string
max_quantity?: number
min_quantity?: number
rules?: Record<string, string>
}[]
}
export interface CreatePriceListPricesWorkflowDTO {
@@ -50,21 +47,11 @@ export interface CreatePriceListPricesWorkflowDTO {
prices: CreatePriceListPriceWorkflowDTO[]
}
export interface UpdatePriceListPriceWorkflowDTO {
data: Pick<UpdatePriceListWorkflowInputDTO, "id" | "prices">[]
variant_price_map: Record<string, string>
}
export interface UpdatePriceListPriceWorkflowStepDTO {
data?: UpdatePriceListPricesWorkflowDTO[]
variant_price_map: Record<string, string>
}
export interface UpsertPriceListPricesWorkflowStepDTO {
data: Pick<UpdatePriceListWorkflowInputDTO, "id" | "prices">[]
variant_price_map: Record<string, string>
}
export interface CreatePriceListsWorkflowStepDTO {
data: CreatePriceListWorkflowInputDTO[]
variant_price_map: Record<string, string>