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 authored and GitHub committed 2024-03-15 11:09:02 +00:00
1 parent c3f26a6826
commit 9288f53327
81 files changed
+1959 -2410

No files matched your search

@@ -2,5 +2,4 @@ export * from "./cart"
export * from "./inventory"
export * from "./line-item"
export * from "./payment-collection"
export * from "./price-list"
export * from "./product"
@@ -1,74 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { PricingTypes, WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum CreatePriceListActions {
prepare = "prepare",
createPriceList = "createPriceList",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: CreatePriceListActions.prepare,
noCompensation: true,
next: {
action: CreatePriceListActions.createPriceList,
},
},
}
const handlers = new Map([
[
CreatePriceListActions.prepare,
{
invoke: pipe(
{
inputAlias: CreatePriceListActions.prepare,
merge: true,
invoke: {
from: CreatePriceListActions.prepare,
},
},
PriceListHandlers.prepareCreatePriceLists
),
},
],
[
CreatePriceListActions.createPriceList,
{
invoke: pipe(
{
invoke: {
from: CreatePriceListActions.prepare,
alias: PriceListHandlers.createPriceLists.aliases.priceLists,
},
},
PriceListHandlers.createPriceLists
),
compensate: pipe(
{
invoke: {
from: CreatePriceListActions.createPriceList,
alias: PriceListHandlers.removePriceLists.aliases.priceLists,
},
},
PriceListHandlers.removePriceLists
),
},
],
])
WorkflowManager.register(Workflows.CreatePriceList, workflowSteps, handlers)
export const createPriceLists = exportWorkflow<
WorkflowTypes.PriceListWorkflow.CreatePriceListWorkflowInputDTO,
{
priceList: PricingTypes.CreatePriceListDTO
}[]
>(Workflows.CreatePriceList, CreatePriceListActions.createPriceList)
@@ -1,71 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum RemovePriceListPricesActions {
prepare = "prepare",
removePriceListPrices = "removePriceListPrices",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: RemovePriceListPricesActions.prepare,
noCompensation: true,
next: {
action: RemovePriceListPricesActions.removePriceListPrices,
noCompensation: true,
},
},
}
const handlers = new Map([
[
RemovePriceListPricesActions.prepare,
{
invoke: pipe(
{
inputAlias: RemovePriceListPricesActions.prepare,
merge: true,
invoke: {
from: RemovePriceListPricesActions.prepare,
},
},
PriceListHandlers.prepareRemovePriceListPrices
),
},
],
[
RemovePriceListPricesActions.removePriceListPrices,
{
invoke: pipe(
{
merge: true,
invoke: {
from: RemovePriceListPricesActions.prepare,
},
},
PriceListHandlers.removePrices
),
},
],
])
WorkflowManager.register(
Workflows.RemovePriceListPrices,
workflowSteps,
handlers
)
export const removePriceListPrices = exportWorkflow<
WorkflowTypes.PriceListWorkflow.RemovePriceListPricesWorkflowInputDTO,
string[]
>(
Workflows.RemovePriceListPrices,
RemovePriceListPricesActions.removePriceListPrices
)
@@ -1,57 +0,0 @@
import { WorkflowTypes } from "@medusajs/types"
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { PriceListHandlers } from "../../handlers"
import { Workflows } from "../../definitions"
export enum RemovePriceListActions {
removePriceList = "removePriceList",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: RemovePriceListActions.removePriceList,
noCompensation: true,
},
}
const handlers = new Map([
[
RemovePriceListActions.removePriceList,
{
invoke: pipe(
{
inputAlias: RemovePriceListActions.removePriceList,
merge: true,
invoke: {
from: RemovePriceListActions.removePriceList,
},
},
PriceListHandlers.removePriceLists
),
},
],
])
WorkflowManager.register(Workflows.DeletePriceLists, workflowSteps, handlers)
export const removePriceLists = exportWorkflow<
WorkflowTypes.PriceListWorkflow.RemovePriceListWorkflowInputDTO,
{
price_list_ids: string[]
}
>(
Workflows.DeletePriceLists,
RemovePriceListActions.removePriceList,
async (data) => {
return {
price_lists: data.price_lists.map((priceListId) => ({
price_list: { id: priceListId },
})),
}
}
)
@@ -1,72 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum RemoveProductPricesActions {
prepare = "prepare",
removePriceListPriceSetPrices = "removePriceListPriceSetPrices",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: RemoveProductPricesActions.prepare,
noCompensation: true,
next: {
action: RemoveProductPricesActions.removePriceListPriceSetPrices,
noCompensation: true,
},
},
}
const handlers = new Map([
[
RemoveProductPricesActions.prepare,
{
invoke: pipe(
{
inputAlias: RemoveProductPricesActions.prepare,
merge: true,
invoke: {
from: RemoveProductPricesActions.prepare,
},
},
PriceListHandlers.prepareRemoveProductPrices
),
},
],
[
RemoveProductPricesActions.removePriceListPriceSetPrices,
{
invoke: pipe(
{
merge: true,
invoke: {
from: RemoveProductPricesActions.prepare,
alias: PriceListHandlers.createPriceLists.aliases.priceLists,
},
},
PriceListHandlers.removePriceListPriceSetPrices
),
},
],
])
WorkflowManager.register(
Workflows.RemovePriceListProductPrices,
workflowSteps,
handlers
)
export const removePriceListProductPrices = exportWorkflow<
WorkflowTypes.PriceListWorkflow.RemovePriceListProductsWorkflowInputDTO,
string[]
>(
Workflows.RemovePriceListProductPrices,
RemoveProductPricesActions.removePriceListPriceSetPrices
)
@@ -1,72 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum RemoveVariantPricesActions {
prepare = "prepare",
removePriceListPriceSetPrices = "removePriceListPriceSetPrices",
}
const workflowSteps: TransactionStepsDefinition = {
next: {
action: RemoveVariantPricesActions.prepare,
noCompensation: true,
next: {
action: RemoveVariantPricesActions.removePriceListPriceSetPrices,
noCompensation: true,
},
},
}
const handlers = new Map([
[
RemoveVariantPricesActions.prepare,
{
invoke: pipe(
{
inputAlias: RemoveVariantPricesActions.prepare,
merge: true,
invoke: {
from: RemoveVariantPricesActions.prepare,
},
},
PriceListHandlers.prepareRemoveVariantPrices
),
},
],
[
RemoveVariantPricesActions.removePriceListPriceSetPrices,
{
invoke: pipe(
{
merge: true,
invoke: {
from: RemoveVariantPricesActions.prepare,
alias: PriceListHandlers.createPriceLists.aliases.priceLists,
},
},
PriceListHandlers.removePriceListPriceSetPrices
),
},
],
])
WorkflowManager.register(
Workflows.RemovePriceListVariantPrices,
workflowSteps,
handlers
)
export const removePriceListVariantPrices = exportWorkflow<
WorkflowTypes.PriceListWorkflow.RemovePriceListVariantsWorkflowInputDTO,
string[]
>(
Workflows.RemovePriceListVariantPrices,
RemoveVariantPricesActions.removePriceListPriceSetPrices
)
@@ -1,65 +0,0 @@
import {
TransactionStepsDefinition,
WorkflowManager,
} from "@medusajs/orchestration"
import { WorkflowTypes } from "@medusajs/types"
import { exportWorkflow, pipe } from "@medusajs/workflows-sdk"
import { Workflows } from "../../definitions"
import { PriceListHandlers } from "../../handlers"
export enum UpdatePriceListActions {
prepare = "prepare",
updatePriceList = "updatePriceList",
}
const workflowSteps: TransactionStepsDefinition = {
action: UpdatePriceListActions.prepare,
noCompensation: true,
next: {
next: {
noCompensation: true,
action: UpdatePriceListActions.updatePriceList,
},
},
}
const handlers = new Map([
[
UpdatePriceListActions.prepare,
{
invoke: pipe(
{
inputAlias: UpdatePriceListActions.prepare,
merge: true,
invoke: {
from: UpdatePriceListActions.prepare,
},
},
PriceListHandlers.prepareUpdatePriceLists
),
},
],
[
UpdatePriceListActions.updatePriceList,
{
invoke: pipe(
{
inputAlias: UpdatePriceListActions.updatePriceList,
merge: true,
invoke: {
from: UpdatePriceListActions.prepare,
},
},
PriceListHandlers.updatePriceLists
),
},
],
])
WorkflowManager.register(Workflows.UpdatePriceLists, workflowSteps, handlers)
export const updatePriceLists = exportWorkflow<
WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowInputDTO,
{ priceList: WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowDTO }[]
>(Workflows.UpdatePriceLists, UpdatePriceListActions.updatePriceList)
@@ -3,7 +3,6 @@ export * as CommonHandlers from "./common"
export * as CustomerHandlers from "./customer"
export * as InventoryHandlers from "./inventory"
export * as MiddlewaresHandlers from "./middlewares"
export * as PriceListHandlers from "./price-list"
export * as ProductHandlers from "./product"
export * as RegionHandlers from "./region"
export * as SalesChannelHandlers from "./sales-channel"
@@ -1,42 +0,0 @@
import {
CreatePriceListDTO,
IPricingModuleService,
PriceListDTO,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
priceList: PriceListDTO
}[]
type Input = {
tag?: string
priceList: CreatePriceListDTO
}[]
export async function createPriceLists({
container,
data,
}: WorkflowArguments<{
priceLists: Input
}>): Promise<Result> {
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
return await Promise.all(
data.priceLists.map(async (item) => {
const [priceList] = await pricingService!.createPriceLists([
item.priceList,
])
return { tag: item.tag ?? priceList.id, priceList }
})
)
}
createPriceLists.aliases = {
priceLists: "priceLists",
}
@@ -1,10 +0,0 @@
export * from "./create-price-list"
export * from "./prepare-create-price-list"
export * from "./prepare-update-price-lists"
export * from "./remove-price-list"
export * from "./update-price-lists"
export * from "./prepare-remove-product-prices"
export * from "./remove-price-set-price-list-prices"
export * from "./prepare-remove-variant-prices"
export * from "./prepare-remove-price-list-prices"
export * from "./remove-prices"
@@ -1,108 +0,0 @@
import { CreatePriceListDTO, PriceListWorkflow } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
tag?: string
priceList: CreatePriceListDTO
}[]
export async function prepareCreatePriceLists({
container,
data,
}: WorkflowArguments<{
price_lists: (PriceListWorkflow.CreatePriceListWorkflowDTO & {
_associationTag?: string
})[]
}>): Promise<Result | void> {
const remoteQuery = container.resolve("remoteQuery")
const regionService = container.resolve("regionService")
const { price_lists } = data
const variantIds = price_lists
.map((priceList) => priceList.prices.map((price) => price.variant_id))
.flat()
const variables = {
variant_id: variantIds,
take: null,
}
const query = {
product_variant_price_set: {
__args: variables,
fields: ["variant_id", "price_set_id"],
},
}
const variantPriceSets = await remoteQuery(query)
const variantIdPriceSetIdMap: Map<string, string> = new Map(
variantPriceSets.map((variantPriceSet) => [
variantPriceSet.variant_id,
variantPriceSet.price_set_id,
])
)
const variantsWithoutPriceSets: string[] = []
for (const variantId of variantIds) {
if (!variantIdPriceSetIdMap.has(variantId)) {
variantsWithoutPriceSets.push(variantId)
}
}
if (variantsWithoutPriceSets.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`No priceSet exist for variants: ${variantsWithoutPriceSets.join(", ")}`
)
}
const regionIds = price_lists
.map(({ prices }) => prices?.map((price) => price.region_id) ?? [])
.flat(2)
const regions = await regionService.list({ id: regionIds }, {})
const regionIdCurrencyCodeMap: Map<string, string> = new Map(
regions.map((region: { id: string; currency_code: string }) => [
region.id,
region.currency_code,
])
)
return price_lists.map((priceListDTO) => {
priceListDTO.title ??= priceListDTO.name
const { _associationTag, name, prices, ...rest } = priceListDTO
const priceList = rest as CreatePriceListDTO
priceList.rules ??= {}
priceList.prices =
prices?.map((price) => {
const price_set_id = variantIdPriceSetIdMap.get(price.variant_id)!
const rules: Record<string, string> = {}
if (price.region_id) {
rules.region_id = price.region_id
}
return {
currency_code:
regionIdCurrencyCodeMap.get(price.region_id as string) ??
(price.currency_code as string),
amount: price.amount,
min_quantity: price.min_quantity,
max_quantity: price.max_quantity,
price_set_id,
rules,
}
}) ?? []
return { priceList, tag: _associationTag }
})
}
prepareCreatePriceLists.aliases = {
payload: "payload",
}
@@ -1,49 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
moneyAmountIds: string[]
priceListId: string
}
export async function prepareRemovePriceListPrices({
container,
data,
}: WorkflowArguments<{
money_amount_ids: string[]
price_list_id: string
}>): Promise<Result | void> {
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
const {
price_list_id: priceListId,
money_amount_ids: moneyAmountIdsToDelete,
} = data
const moneyAmounts = await pricingService.listMoneyAmounts(
{ id: moneyAmountIdsToDelete },
{
relations: [
"price_set_money_amount",
"price_set_money_amount.price_list",
],
take: null,
}
)
const moneyAmountIds = moneyAmounts
.filter(
(moneyAmount) =>
moneyAmount?.price_set_money_amount?.price_list?.id === priceListId
)
.map((ma) => ma.id)
return { moneyAmountIds, priceListId }
}
prepareRemovePriceListPrices.aliases = {
payload: "payload",
}
@@ -1,63 +0,0 @@
import { WorkflowArguments } from "@medusajs/workflows-sdk"
import { prepareCreatePriceLists } from "./prepare-create-price-list"
type Result = {
priceSetIds: string[]
priceListId: string
}
export async function prepareRemoveProductPrices({
container,
data,
}: WorkflowArguments<{
product_ids: string[]
price_list_id: string
}>): Promise<Result | void> {
const remoteQuery = container.resolve("remoteQuery")
const { price_list_id, product_ids } = data
const variables = {
id: product_ids,
take: null,
}
const query = {
product: {
__args: variables,
...defaultAdminProductRemoteQueryObject,
},
}
const productsWithVariantPriceSets: QueryResult[] = await remoteQuery(query)
const priceSetIds = productsWithVariantPriceSets
.map(({ variants }) => variants.map(({ price }) => price.price_set_id))
.flat()
return { priceSetIds, priceListId: price_list_id }
}
prepareCreatePriceLists.aliases = {
payload: "payload",
}
type QueryResult = {
id: string
variants: {
id: string
price: {
price_set_id: string
variant_id: string
}
}[]
}
const defaultAdminProductRemoteQueryObject = {
fields: ["id"],
variants: {
price: {
fields: ["variant_id", "price_set_id"],
},
},
}
@@ -1,57 +0,0 @@
import { WorkflowArguments } from "@medusajs/workflows-sdk"
import { prepareCreatePriceLists } from "./prepare-create-price-list"
type Result = {
priceSetIds: string[]
priceListId: string
}
export async function prepareRemoveVariantPrices({
container,
data,
}: WorkflowArguments<{
variant_ids: string[]
price_list_id: string
}>): Promise<Result | void> {
const remoteQuery = container.resolve("remoteQuery")
const { price_list_id, variant_ids } = data
const variables = {
variant_id: variant_ids,
take: null,
}
const query = {
product_variant_price_set: {
__args: variables,
fields: ["variant_id", "price_set_id"],
},
}
const productsWithVariantPriceSets: QueryResult[] = await remoteQuery(query)
const priceSetIds = productsWithVariantPriceSets.map(
(variantPriceSet) => variantPriceSet.price_set_id
)
return { priceSetIds, priceListId: price_list_id }
}
prepareCreatePriceLists.aliases = {
payload: "payload",
}
type QueryResult = {
price_set_id: string
variant_id: string
}
const defaultAdminProductRemoteQueryObject = {
fields: ["id"],
variants: {
price: {
fields: ["variant_id", "price_set_id"],
},
},
}
@@ -1,108 +0,0 @@
import {
PriceListPriceDTO,
UpdatePriceListDTO,
WorkflowTypes,
} from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
priceLists: UpdatePriceListDTO[]
priceListPricesMap: Map<string, PriceListPriceDTO[]>
}
export async function prepareUpdatePriceLists({
container,
data,
}: WorkflowArguments<{
price_lists: WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowDTO[]
}>): Promise<Result> {
const { price_lists: priceListsData } = data
const remoteQuery = container.resolve("remoteQuery")
const regionService = container.resolve("regionService")
const variantPriceSetMap = new Map<string, string>()
const priceListPricesMap = new Map<string, PriceListPriceDTO[]>()
const variantIds = priceListsData
.map((priceListData) => priceListData.prices?.map((p) => p.variant_id))
.flat()
const variables = {
variant_id: variantIds,
take: null,
}
const query = {
product_variant_price_set: {
__args: variables,
fields: ["variant_id", "price_set_id"],
},
}
const variantPriceSets = await remoteQuery(query)
for (const { variant_id, price_set_id } of variantPriceSets) {
variantPriceSetMap.set(variant_id, price_set_id)
}
const regionIds = priceListsData
.map(({ prices }) => prices?.map((price) => price.region_id) ?? [])
.flat(2)
const regions = await regionService.list({ id: regionIds })
const regionsMap: Map<string, string> = new Map(
regions.map((region: { id: string; currency_code: string }) => [
region.id,
region.currency_code,
])
)
const priceLists = priceListsData.map((priceListData) => {
const priceListPrices: PriceListPriceDTO[] = []
priceListData.prices?.forEach((price) => {
const { variant_id, ...priceData } = price
if (!variant_id) {
return
}
const rules: Record<string, string> = {}
if (price.region_id) {
rules.region_id = price.region_id
}
priceListPrices.push({
id: priceData.id,
price_set_id: variantPriceSetMap.get(variant_id) as string,
currency_code:
regionsMap.get(priceData.region_id as string) ??
(priceData.currency_code as string),
amount: priceData.amount,
min_quantity: priceData.min_quantity,
max_quantity: priceData.max_quantity,
rules,
})
return
})
priceListPricesMap.set(priceListData.id, priceListPrices)
delete priceListData?.prices
const priceListDataClone: UpdatePriceListDTO = {
...priceListData,
}
if (priceListData.name) {
priceListDataClone.title = priceListData.name
}
return priceListDataClone
})
return { priceLists, priceListPricesMap }
}
prepareUpdatePriceLists.aliases = {
payload: "prepare",
}
@@ -1,31 +0,0 @@
import { IPricingModuleService, PriceListDTO } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
export async function removePriceLists({
container,
data,
}: WorkflowArguments<{
price_lists: {
price_list: PriceListDTO
}[]
}>): Promise<
{
price_list: PriceListDTO
}[]
> {
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
await pricingService!.deletePriceLists(
data.price_lists.map(({ price_list }) => price_list.id)
)
return data.price_lists
}
removePriceLists.aliases = {
priceLists: "priceLists",
}
@@ -1,40 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
import { prepareCreatePriceLists } from "./prepare-create-price-list"
export async function removePriceListPriceSetPrices({
container,
data,
}: WorkflowArguments<{
priceSetIds: string[]
priceListId: string
}>): Promise<string[]> {
const { priceSetIds, priceListId } = data
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
const priceSetMoneyAmounts = await pricingService.listPriceSetMoneyAmounts(
{
price_set_id: priceSetIds,
price_list_id: [priceListId],
},
{
relations: ["money_amount"],
take: null,
}
)
const moneyAmountIDs = priceSetMoneyAmounts
.map((priceSetMoneyAmount) => priceSetMoneyAmount.money_amount?.id)
.filter((moneyAmountId): moneyAmountId is string => !!moneyAmountId)
await pricingService.deleteMoneyAmounts(moneyAmountIDs)
return moneyAmountIDs
}
prepareCreatePriceLists.aliases = {
payload: "payload",
}
@@ -1,29 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
deletedPriceIds: string[]
}
export async function removePrices({
container,
data,
}: WorkflowArguments<{
moneyAmountIds: string[]
}>): Promise<Result> {
const { moneyAmountIds } = data
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
await pricingService.deleteMoneyAmounts(moneyAmountIds)
return {
deletedPriceIds: moneyAmountIds,
}
}
removePrices.aliases = {
payload: "payload",
}
@@ -1,63 +0,0 @@
import {
AddPriceListPricesDTO,
IPricingModuleService,
PriceListDTO,
PriceListPriceDTO,
UpdateMoneyAmountDTO,
UpdatePriceListDTO,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { WorkflowArguments } from "@medusajs/workflows-sdk"
type Result = {
priceLists: PriceListDTO[]
}
export async function updatePriceLists({
container,
data,
}: WorkflowArguments<{
priceLists: UpdatePriceListDTO[]
priceListPricesMap: Map<string, PriceListPriceDTO[]>
}>): Promise<Result> {
const { priceLists: priceListsData, priceListPricesMap } = data
const pricingService: IPricingModuleService = container.resolve(
ModuleRegistrationName.PRICING
)
const priceLists = await pricingService.updatePriceLists(priceListsData)
const addPriceListPricesData: AddPriceListPricesDTO[] = []
const moneyAmountsToUpdate: UpdateMoneyAmountDTO[] = []
for (const [priceListId, prices] of priceListPricesMap.entries()) {
const moneyAmountsToCreate: PriceListPriceDTO[] = []
for (const price of prices) {
if (price.id) {
moneyAmountsToUpdate.push(price as UpdateMoneyAmountDTO)
} else {
moneyAmountsToCreate.push(price)
}
}
addPriceListPricesData.push({
priceListId,
prices: moneyAmountsToCreate,
})
}
if (addPriceListPricesData.length) {
await pricingService.addPriceListPrices(addPriceListPricesData)
}
if (moneyAmountsToUpdate.length) {
await pricingService.updateMoneyAmounts(moneyAmountsToUpdate)
}
return { priceLists }
}
updatePriceLists.aliases = {
payload: "updatePriceLists",
}
+1
View File
@@ -8,6 +8,7 @@ export * from "./fulfillment"
export * as Handlers from "./handlers"
export * from "./invite"
export * from "./payment"
export * from "./price-list"
export * from "./pricing"
export * from "./product"
export * from "./promotion"
@@ -0,0 +1,2 @@
export * from "./steps"
export * from "./workflows"
@@ -0,0 +1,58 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CreatePriceListDTO,
CreatePriceListWorkflowInputDTO,
IPricingModuleService,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type WorkflowStepInput = {
data: CreatePriceListWorkflowInputDTO[]
variant_price_map: Record<string, string>
}
export const createPriceListsStepId = "create-price-lists"
export const createPriceListsStep = createStep(
createPriceListsStepId,
async (stepInput: WorkflowStepInput, { container }) => {
const { data, variant_price_map: variantPriceMap } = stepInput
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const createData = data.map((priceListDTO) => {
const { prices = [], ...rest } = priceListDTO
const createPriceListData: CreatePriceListDTO = { ...rest }
createPriceListData.prices = prices.map((price) => ({
currency_code: price.currency_code,
amount: price.amount,
min_quantity: price.min_quantity,
max_quantity: price.max_quantity,
price_set_id: variantPriceMap[price.variant_id],
rules: price.rules,
}))
return createPriceListData
})
const createdPriceLists = await pricingModule.createPriceLists(createData)
return new StepResponse(
createdPriceLists,
createdPriceLists.map((createdPriceLists) => createdPriceLists.id)
)
},
async (createdPriceListIds, { container }) => {
if (!createdPriceListIds?.length) {
return
}
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
await pricingModule.deletePriceLists(createdPriceListIds)
}
)
@@ -0,0 +1,30 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const deletePriceListsStepId = "delete-campaigns"
export const deletePriceListsStep = createStep(
deletePriceListsStepId,
async (ids: string[], { container }) => {
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
// TODO: Implement soft delete price lists
await pricingModule.deletePriceLists(ids)
return new StepResponse(void 0, ids)
},
async (idsToRestore, { container }) => {
if (!idsToRestore?.length) {
return
}
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
// TODO: Implement restore price lists
// await pricingModule.restorePriceLists(idsToRestore)
}
)
@@ -0,0 +1,7 @@
export * from "./create-price-lists"
export * from "./delete-price-lists"
export * from "./remove-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"
@@ -0,0 +1,41 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const removePriceListPricesStepId = "remove-price-list-prices"
export const removePriceListPricesStep = createStep(
removePriceListPricesStepId,
async (ids: string[], { container }) => {
if (!ids.length) {
return new StepResponse(null, [])
}
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const psmas = await pricingModule.listPriceSetMoneyAmounts(
{ id: ids },
{ relations: ["price_list"] }
)
await pricingModule.removePrices(psmas.map((psma) => psma.id))
return new StepResponse(
null,
psmas.map((psma) => psma.id)
)
},
async (ids, { container }) => {
if (!ids) {
return
}
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
// TODO: This needs to be implemented
// pricingModule.restorePrices(ids)
}
)
@@ -0,0 +1,105 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
IPricingModuleService,
UpdatePriceListDTO,
UpdatePriceListWorkflowInputDTO,
} from "@medusajs/types"
import {
buildPriceListRules,
convertItemResponseToUpdateRequest,
getSelectsAndRelationsFromObjectArray,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const updatePriceListsStepId = "update-price-lists"
export const updatePriceListsStep = createStep(
updatePriceListsStepId,
async (data: UpdatePriceListDTO[], { container }) => {
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const { dataBeforeUpdate, selects, relations } = await getDataBeforeUpdate(
pricingModule,
data
)
const updatedPriceLists = await pricingModule.updatePriceLists(data)
return new StepResponse(updatedPriceLists, {
dataBeforeUpdate,
selects,
relations,
})
},
async (revertInput, { container }) => {
if (!revertInput) {
return
}
const { dataBeforeUpdate, selects, relations } = revertInput
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
await pricingModule.updatePriceLists(
dataBeforeUpdate.map((data) => {
const { price_list_rules: priceListRules = [], rules, ...rest } = data
const updateData: UpdatePriceListDTO = {
...rest,
rules: buildPriceListRules(priceListRules),
}
return convertItemResponseToUpdateRequest(
updateData,
selects,
relations
)
})
)
}
)
// Since rules is an API level abstraction, we need to do this dance of data fetching
// to its actual attributes in the module to do perform a revert in case a rollback needs to happen.
// TODO: Check if there is a better way to approach this. Preferably the module should be handling this
// if this is not the response the module provides.
async function getDataBeforeUpdate(
pricingModule: IPricingModuleService,
data: UpdatePriceListWorkflowInputDTO[]
) {
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data, {
objectFields: ["rules"],
})
const selectsClone = [...selects]
const relationsClone = [...relations]
if (selectsClone.includes("rules")) {
const index = selectsClone.indexOf("rules", 0)
if (index > -1) {
selectsClone.splice(index, 1)
}
selectsClone.push(
"price_list_rules.price_list_rule_values.value",
"price_list_rules.rule_type.rule_attribute"
)
relationsClone.push(
"price_list_rules.price_list_rule_values",
"price_list_rules.rule_type"
)
}
const dataBeforeUpdate = await pricingModule.listPriceLists(
{ id: data.map((d) => d.id) },
{ relations: relationsClone, select: selectsClone }
)
return {
dataBeforeUpdate,
selects,
relations,
}
}
@@ -0,0 +1,131 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
AddPriceListPricesDTO,
CreatePriceListPriceDTO,
CreatePriceListPriceWorkflowDTO,
IPricingModuleService,
PriceSetMoneyAmountDTO,
UpdatePriceListPriceDTO,
UpdatePriceListPriceWorkflowDTO,
UpdatePriceListPricesDTO,
UpdatePriceListWorkflowInputDTO,
} from "@medusajs/types"
import { buildPriceSetPricesForModule, promiseAll } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type WorkflowStepInput = {
data: Pick<UpdatePriceListWorkflowInputDTO, "id" | "prices">[]
variant_price_map: Record<string, string>
}
export const upsertPriceListPricesStepId = "upsert-price-list-prices"
export const upsertPriceListPricesStep = createStep(
upsertPriceListPricesStepId,
async (stepInput: WorkflowStepInput, { 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 updatedPriceSetMoneyAmounts =
await pricingModule.listPriceSetMoneyAmounts(
{
id: priceListPricesToUpdate
.map((priceListData) =>
priceListData.prices.map((price) => price.id)
)
.filter(Boolean)
.flat(1),
},
{ relations: ["price_list"] }
)
const priceListPsmaMap = new Map<string, PriceSetMoneyAmountDTO[]>()
const dataBeforePriceUpdate: UpdatePriceListPricesDTO[] = []
for (const priceSetMoneyAmount of updatedPriceSetMoneyAmounts) {
const priceListId = priceSetMoneyAmount.price_list!.id
const psmas = priceListPsmaMap.get(priceListId) || []
priceListPsmaMap.set(priceListId, psmas)
}
for (const [priceListId, psmas] of Object.entries(priceListPsmaMap)) {
dataBeforePriceUpdate.push({
price_list_id: priceListId,
prices: buildPriceSetPricesForModule(psmas),
})
}
// 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
}
@@ -0,0 +1,41 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
IPricingModuleService,
PriceListDTO,
UpdatePriceListDTO,
} from "@medusajs/types"
import { MedusaError, arrayDifference } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const validatePriceListsStepId = "validate-price-lists"
export const validatePriceListsStep = createStep(
validatePriceListsStepId,
async (data: Pick<UpdatePriceListDTO, "id">[], { container }) => {
const pricingModule = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const priceListIds = data.map((d) => d.id)
const priceLists = await pricingModule.listPriceLists({ id: priceListIds })
const diff = arrayDifference(
priceListIds,
priceLists.map((pl) => pl.id)
)
if (diff.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Price lists with id: ${diff.join(", ")} was not found`
)
}
const priceListMap: Record<string, PriceListDTO> = {}
for (const priceList of priceLists) {
priceListMap[priceList.id] = priceList
}
return new StepResponse(priceListMap)
}
)
@@ -0,0 +1,48 @@
import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type WorkflowStepInput = Pick<UpdatePriceListWorkflowInputDTO, "prices">[]
export const validateVariantPriceLinksStepId = "validate-variant-price-links"
export const validateVariantPriceLinksStep = createStep(
validateVariantPriceLinksStepId,
async (data: WorkflowStepInput, { container }) => {
const remoteQuery = container.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
const variantIds: string[] = data
.map((pl) => pl?.prices?.map((price) => price.variant_id!) || [])
.filter(Boolean)
.flat(1)
const variantPricingLinkQuery = remoteQueryObjectFromString({
entryPoint: "product_variant_price_set",
fields: ["variant_id", "price_set_id"],
variables: { variant_id: variantIds, take: null },
})
const links = await remoteQuery(variantPricingLinkQuery)
const variantPriceSetMap: Record<string, string> = {}
for (const link of links) {
variantPriceSetMap[link.variant_id] = link.price_set_id
}
const withoutLinks = variantIds.filter((id) => !variantPriceSetMap[id])
if (withoutLinks.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`No price set exist for variants: ${withoutLinks.join(", ")}`
)
}
return new StepResponse(variantPriceSetMap)
}
)
@@ -0,0 +1,20 @@
import { CreatePriceListWorkflowInputDTO, PriceListDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createPriceListsStep, validateVariantPriceLinksStep } from "../steps"
type WorkflowInput = { price_lists_data: CreatePriceListWorkflowInputDTO[] }
export const createPriceListsWorkflowId = "create-price-lists"
export const createPriceListsWorkflow = createWorkflow(
createPriceListsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<PriceListDTO[]> => {
const variantPriceMap = validateVariantPriceLinksStep(
input.price_lists_data
)
return createPriceListsStep({
data: input.price_lists_data,
variant_price_map: variantPriceMap,
})
}
)
@@ -0,0 +1,12 @@
import { createWorkflow, WorkflowData } from "@medusajs/workflows-sdk"
import { deletePriceListsStep } from "../steps"
type WorkflowInput = { ids: string[] }
export const deletePriceListsWorkflowId = "delete-price-lists"
export const deletePriceListsWorkflow = createWorkflow(
deletePriceListsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
return deletePriceListsStep(input.ids)
}
)
@@ -1,6 +1,5 @@
export * from "./create-price-lists"
export * from "./remove-price-lists"
export * from "./delete-price-lists"
export * from "./remove-price-list-prices"
export * from "./remove-product-prices"
export * from "./remove-variant-prices"
export * from "./update-price-lists"
export * from "./upsert-price-list-prices"
@@ -0,0 +1,12 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { removePriceListPricesStep } from "../steps"
type WorkflowInput = { ids: string[] }
export const removePriceListPricesWorkflowId = "remove-price-list-prices"
export const removePriceListPricesWorkflow = createWorkflow(
removePriceListPricesWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
removePriceListPricesStep(input.ids)
}
)
@@ -0,0 +1,47 @@
import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import {
updatePriceListsStep,
upsertPriceListPricesStep,
validatePriceListsStep,
validateVariantPriceLinksStep,
} from "../steps"
type WorkflowInput = { price_lists_data: UpdatePriceListWorkflowInputDTO[] }
export const updatePriceListsWorkflowId = "update-price-lists"
export const updatePriceListsWorkflow = createWorkflow(
updatePriceListsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
const [priceListsMap, variantPriceMap] = parallelize(
validatePriceListsStep(input.price_lists_data),
validateVariantPriceLinksStep(input.price_lists_data)
)
const updatePricesInput = transform(
{ priceListsMap, variantPriceMap, input },
(data) => ({
data: data.input.price_lists_data,
price_lists_map: data.priceListsMap,
variant_price_map: data.variantPriceMap,
})
)
upsertPriceListPricesStep(updatePricesInput)
const updatePriceListInput = transform({ input }, (data) => {
return data.input.price_lists_data.map((priceListData) => {
delete priceListData.prices
return priceListData
})
})
updatePriceListsStep(updatePriceListInput)
}
)
@@ -0,0 +1,31 @@
import { UpdatePriceListWorkflowInputDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
} from "@medusajs/workflows-sdk"
import {
upsertPriceListPricesStep,
validatePriceListsStep,
validateVariantPriceLinksStep,
} from "../steps"
type WorkflowInput = {
price_lists_data: Pick<UpdatePriceListWorkflowInputDTO, "id" | "prices">[]
}
export const upsertPriceListPricesWorkflowId = "upsert-price-list-prices"
export const upsertPriceListPricesWorkflow = createWorkflow(
upsertPriceListPricesWorkflowId,
(input: WorkflowData<WorkflowInput>): 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,
})
}
)