feat(medusa,types,workflows,utils,product): PricingModule Integration of PriceLists into Core (#5536)
This commit is contained in:
1 parent
9c7ac7332a
commit
dc5750dd66
94 files changed
+5697
-880
No files matched your search
@@ -1,12 +1,15 @@
|
||||
import { MedusaV2Flag } from "@medusajs/utils"
|
||||
import { updatePriceLists } from "@medusajs/workflows"
|
||||
import { Type } from "class-transformer"
|
||||
import { IsArray, IsBoolean, IsOptional, ValidateNested } from "class-validator"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
|
||||
|
||||
import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list"
|
||||
import { PriceList } from "../../../.."
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { Type } from "class-transformer"
|
||||
import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { getPriceListPricingModule } from "./modules-queries"
|
||||
|
||||
/**
|
||||
* @oas [post] /admin/price-lists/{id}/prices/batch
|
||||
@@ -85,23 +88,48 @@ import { EntityManager } from "typeorm"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const validated = await validator(AdminPostPriceListPricesPricesReq, req.body)
|
||||
|
||||
let priceList
|
||||
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.addPrices(id, validated.prices, validated.override)
|
||||
})
|
||||
const validated = await validator(AdminPostPriceListPricesPricesReq, req.body)
|
||||
|
||||
const priceList = await priceListService.retrieve(id, {
|
||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
const updatePriceListWorkflow = updatePriceLists(req.scope)
|
||||
|
||||
const input = {
|
||||
price_lists: [
|
||||
{
|
||||
id,
|
||||
...validated,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
await updatePriceListWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager,
|
||||
},
|
||||
})
|
||||
|
||||
priceList = await getPriceListPricingModule(id, {
|
||||
container: req.scope as MedusaContainer,
|
||||
})
|
||||
} else {
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.addPrices(id, validated.prices, validated.override)
|
||||
})
|
||||
|
||||
priceList = await priceListService.retrieve(id, {
|
||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
}
|
||||
|
||||
res.json({ price_list: priceList })
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
import { MedusaContainer, PricingTypes, WorkflowTypes } from "@medusajs/types"
|
||||
import {
|
||||
FlagRouter,
|
||||
MedusaV2Flag,
|
||||
PriceListStatus,
|
||||
PriceListType,
|
||||
} from "@medusajs/utils"
|
||||
import { createPriceLists } from "@medusajs/workflows"
|
||||
import { Type } from "class-transformer"
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
@@ -6,23 +15,18 @@ import {
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { Request } from "express"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
|
||||
import TaxInclusivePricingFeatureFlag from "../../../../loaders/feature-flags/tax-inclusive-pricing"
|
||||
import { PriceList } from "../../../../models"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import {
|
||||
AdminPriceListPricesCreateReq,
|
||||
CreatePriceListInput,
|
||||
} from "../../../../types/price-list"
|
||||
|
||||
import { PriceListStatus, PriceListType } from "@medusajs/utils"
|
||||
import { Type } from "class-transformer"
|
||||
import { Request } from "express"
|
||||
import { EntityManager } from "typeorm"
|
||||
import TaxInclusivePricingFeatureFlag from "../../../../loaders/feature-flags/tax-inclusive-pricing"
|
||||
import { PriceList } from "../../../../models"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||
import {
|
||||
defaultAdminPriceListFields,
|
||||
defaultAdminPriceListRelations,
|
||||
} from "./index"
|
||||
import { getPriceListPricingModule } from "./modules-queries"
|
||||
|
||||
/**
|
||||
* @oas [post] /admin/price-lists
|
||||
@@ -109,16 +113,57 @@ export default async (req: Request, res) => {
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
let priceList = await manager.transaction(async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.create(req.validatedBody as CreatePriceListInput)
|
||||
})
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
let priceList
|
||||
|
||||
priceList = await priceListService.retrieve(priceList.id, {
|
||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
if (isMedusaV2FlagEnabled) {
|
||||
const createPriceListWorkflow = createPriceLists(req.scope)
|
||||
const validatedInput = req.validatedBody as CreatePriceListInput
|
||||
const rules: PricingTypes.CreatePriceListRules = {}
|
||||
const customerGroups = validatedInput?.customer_groups || []
|
||||
delete validatedInput.customer_groups
|
||||
|
||||
if (customerGroups.length) {
|
||||
rules["customer_group_id"] = customerGroups.map((cg) => cg.id)
|
||||
}
|
||||
|
||||
const input = {
|
||||
price_lists: [
|
||||
{
|
||||
...validatedInput,
|
||||
rules,
|
||||
},
|
||||
],
|
||||
} as WorkflowTypes.PriceListWorkflow.CreatePriceListWorkflowInputDTO
|
||||
|
||||
const { result } = await createPriceListWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager,
|
||||
},
|
||||
})
|
||||
|
||||
priceList = result[0]!.priceList
|
||||
|
||||
priceList = await getPriceListPricingModule(priceList.id, {
|
||||
container: req.scope as MedusaContainer,
|
||||
})
|
||||
} else {
|
||||
priceList = await manager.transaction(async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.create(req.validatedBody as CreatePriceListInput)
|
||||
})
|
||||
|
||||
priceList = await priceListService.retrieve(priceList.id, {
|
||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
}
|
||||
|
||||
res.json({ price_list: priceList })
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { WorkflowTypes } from "@medusajs/types"
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { removePriceLists } from "@medusajs/workflows"
|
||||
import { EntityManager } from "typeorm"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
|
||||
@@ -56,12 +59,33 @@ import PriceListService from "../../../../services/price-list"
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
return await priceListService.withTransaction(transactionManager).delete(id)
|
||||
})
|
||||
|
||||
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
if (isMedusaV2FlagEnabled) {
|
||||
const removePriceListsWorkflow = removePriceLists(req.scope)
|
||||
|
||||
const input = {
|
||||
price_lists: [id],
|
||||
} as WorkflowTypes.PriceListWorkflow.RemovePriceListWorkflowInputDTO
|
||||
|
||||
await removePriceListsWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await priceListService.withTransaction(transactionManager).delete(id)
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
id,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { removePriceListProductPrices } from "@medusajs/workflows/dist/definition/price-list/remove-price-list-prices"
|
||||
import { ArrayNotEmpty, IsString } from "class-validator"
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { WorkflowTypes } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* @oas [delete] /admin/price-lists/{id}/prices/batch
|
||||
@@ -81,13 +83,37 @@ export default async (req, res) => {
|
||||
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.deletePrices(id, validated.price_ids)
|
||||
})
|
||||
|
||||
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
if (isMedusaV2FlagEnabled) {
|
||||
const deletePriceListPricesWorkflow = removePriceListProductPrices(
|
||||
req.scope
|
||||
)
|
||||
|
||||
const input = {
|
||||
price_list_id: id,
|
||||
money_amount_ids: validated.price_ids,
|
||||
} as WorkflowTypes.PriceListWorkflow.RemovePriceListPricesWorkflowInputDTO
|
||||
|
||||
await deletePriceListPricesWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.deletePrices(id, validated.price_ids)
|
||||
})
|
||||
}
|
||||
|
||||
res.json({ ids: validated.price_ids, object: "money-amount", deleted: true })
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { WorkflowTypes } from "@medusajs/types"
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { removePriceListProductPrices } from "@medusajs/workflows/dist/definition/price-list/remove-product-prices"
|
||||
import { EntityManager } from "typeorm"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
|
||||
@@ -60,15 +63,44 @@ export default async (req, res) => {
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const [deletedPriceIds] = await manager.transaction(
|
||||
async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.deleteProductPrices(id, [product_id])
|
||||
}
|
||||
|
||||
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
let deletedPriceIds: string[] = []
|
||||
|
||||
if (isMedusaV2FlagEnabled) {
|
||||
const deletePriceListProductsWorkflow = removePriceListProductPrices(
|
||||
req.scope
|
||||
)
|
||||
|
||||
const input = {
|
||||
product_ids: [product_id],
|
||||
price_list_id: id,
|
||||
} as WorkflowTypes.PriceListWorkflow.RemovePriceListProductsWorkflowInputDTO
|
||||
|
||||
const { result } = await deletePriceListProductsWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager,
|
||||
},
|
||||
})
|
||||
|
||||
deletedPriceIds = result
|
||||
} else {
|
||||
const [deletedIds] = await manager.transaction(
|
||||
async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.deleteProductPrices(id, [product_id])
|
||||
}
|
||||
)
|
||||
deletedPriceIds = deletedIds
|
||||
}
|
||||
|
||||
return res.json({
|
||||
ids: deletedPriceIds,
|
||||
object: "money-amount",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { removePriceListProductPrices } from "@medusajs/workflows"
|
||||
import { ArrayNotEmpty, IsString } from "class-validator"
|
||||
import { Request, Response } from "express"
|
||||
import { EntityManager } from "typeorm"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { WorkflowTypes } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* @oas [delete] /admin/price-lists/{id}/products/prices/batch
|
||||
@@ -79,15 +82,44 @@ export default async (req: Request, res: Response) => {
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const [deletedPriceIds] = await manager.transaction(
|
||||
async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.deleteProductPrices(id, validated.product_ids)
|
||||
}
|
||||
|
||||
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
let deletedPriceIds: string[] = []
|
||||
|
||||
if (isMedusaV2FlagEnabled) {
|
||||
const deletePriceListProductsWorkflow = removePriceListProductPrices(
|
||||
req.scope
|
||||
)
|
||||
|
||||
const input = {
|
||||
product_ids: validated.product_ids,
|
||||
price_list_id: id,
|
||||
} as WorkflowTypes.PriceListWorkflow.RemovePriceListProductsWorkflowInputDTO
|
||||
|
||||
const { result } = await deletePriceListProductsWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager,
|
||||
},
|
||||
})
|
||||
deletedPriceIds = result
|
||||
} else {
|
||||
const [deletedIds] = await manager.transaction(
|
||||
async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.deleteProductPrices(id, validated.product_ids)
|
||||
}
|
||||
)
|
||||
|
||||
deletedPriceIds = deletedIds
|
||||
}
|
||||
|
||||
return res.json({
|
||||
ids: deletedPriceIds,
|
||||
object: "money-amount",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { removePriceListVariantPrices } from "@medusajs/workflows"
|
||||
import { EntityManager } from "typeorm"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { WorkflowTypes } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* @oas [delete] /admin/price-lists/{id}/variants/{variant_id}/prices
|
||||
@@ -59,16 +62,44 @@ export default async (req, res) => {
|
||||
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const [deletedPriceIds] = await manager.transaction(
|
||||
async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.deleteVariantPrices(id, [variant_id])
|
||||
}
|
||||
|
||||
const isMedusaV2FlagEnabled = featureFlagRouter.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
let deletedPriceIds: string[] = []
|
||||
|
||||
if (isMedusaV2FlagEnabled) {
|
||||
const deletePriceListProductsWorkflow = removePriceListVariantPrices(
|
||||
req.scope
|
||||
)
|
||||
|
||||
const input = {
|
||||
variant_ids: [variant_id],
|
||||
price_list_id: id,
|
||||
} as WorkflowTypes.PriceListWorkflow.RemovePriceListVariantsWorkflowInputDTO
|
||||
|
||||
const { result } = await deletePriceListProductsWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager,
|
||||
},
|
||||
})
|
||||
deletedPriceIds = result
|
||||
} else {
|
||||
const [deletedIds] = await manager.transaction(
|
||||
async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.deleteVariantPrices(id, [variant_id])
|
||||
}
|
||||
)
|
||||
|
||||
deletedPriceIds = deletedIds
|
||||
}
|
||||
|
||||
return res.json({
|
||||
ids: deletedPriceIds,
|
||||
object: "money-amount",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
|
||||
|
||||
import { PriceList } from "../../../.."
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { getPriceListPricingModule } from "./modules-queries"
|
||||
|
||||
/**
|
||||
* @oas [get] /admin/price-lists/{id}
|
||||
@@ -58,13 +60,22 @@ import PriceListService from "../../../../services/price-list"
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
const priceList = await priceListService.retrieve(id, {
|
||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
let priceList
|
||||
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
priceList = await getPriceListPricingModule(id, {
|
||||
container: req.scope as MedusaContainer,
|
||||
})
|
||||
} else {
|
||||
priceList = await priceListService.retrieve(id, {
|
||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
}
|
||||
|
||||
res.status(200).json({ price_list: priceList })
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import middlewares, {
|
||||
import {
|
||||
defaultAdminProductFields,
|
||||
defaultAdminProductRelations,
|
||||
defaultAdminProductRemoteQueryObject,
|
||||
} from "../products"
|
||||
|
||||
import { FlagRouter } from "@medusajs/utils"
|
||||
@@ -86,6 +87,50 @@ export default (app, featureFlagRouter: FlagRouter) => {
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultAdminPriceListRemoteQueryObject = {
|
||||
fields: [
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"description",
|
||||
"ends_at",
|
||||
"id",
|
||||
"title",
|
||||
"starts_at",
|
||||
"status",
|
||||
"type",
|
||||
"updated_at",
|
||||
],
|
||||
price_list_rules: {
|
||||
price_list_rule_values: {
|
||||
fields: ["value"],
|
||||
},
|
||||
rule_type: {
|
||||
fields: ["rule_attribute"],
|
||||
},
|
||||
},
|
||||
price_set_money_amounts: {
|
||||
money_amount: {
|
||||
fields: [
|
||||
"id",
|
||||
"currency_code",
|
||||
"amount",
|
||||
"min_quantity",
|
||||
"max_quantity",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"updated_at",
|
||||
],
|
||||
},
|
||||
price_set: {
|
||||
variant_link: {
|
||||
variant: {
|
||||
fields: defaultAdminProductRemoteQueryObject.variants.fields,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const defaultAdminPriceListFields = [
|
||||
"id",
|
||||
"name",
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
DateComparisonOperator,
|
||||
extendedFindParamsMixin,
|
||||
} from "../../../../types/common"
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
@@ -10,14 +6,20 @@ import {
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { MedusaError, isDefined } from "medusa-core-utils"
|
||||
import { isDefined } from "medusa-core-utils"
|
||||
import {
|
||||
DateComparisonOperator,
|
||||
extendedFindParamsMixin,
|
||||
} from "../../../../types/common"
|
||||
|
||||
import { FilterableProductProps } from "../../../../types/product"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { ProductStatus } from "../../../../models"
|
||||
import { Request } from "express"
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { Type } from "class-transformer"
|
||||
import { Request } from "express"
|
||||
import { pickBy } from "lodash"
|
||||
import { ProductStatus } from "../../../../models"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { FilterableProductProps } from "../../../../types/product"
|
||||
import { listAndCountProductWithIsolatedProductModule } from "../products/list-products"
|
||||
|
||||
/**
|
||||
* @oas [get] /admin/price-lists/{id}/products
|
||||
@@ -181,6 +183,9 @@ import { pickBy } from "lodash"
|
||||
export default async (req: Request, res) => {
|
||||
const { id } = req.params
|
||||
const { offset, limit } = req.validatedQuery
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
let products
|
||||
let count
|
||||
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
@@ -190,11 +195,19 @@ export default async (req: Request, res) => {
|
||||
price_list_id: [id],
|
||||
}
|
||||
|
||||
const [products, count] = await priceListService.listProducts(
|
||||
id,
|
||||
pickBy(filterableFields, (val) => isDefined(val)),
|
||||
req.listConfig
|
||||
)
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
;[products, count] = await listAndCountProductWithIsolatedProductModule(
|
||||
req,
|
||||
filterableFields,
|
||||
req.listConfig
|
||||
)
|
||||
} else {
|
||||
;[products, count] = await priceListService.listProducts(
|
||||
id,
|
||||
pickBy(filterableFields, (val) => isDefined(val)),
|
||||
req.listConfig
|
||||
)
|
||||
}
|
||||
|
||||
res.json({
|
||||
products,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||
|
||||
import { FilterablePriceListProps } from "../../../../types/price-list"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { Request } from "express"
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { Type } from "class-transformer"
|
||||
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||
import { Request } from "express"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { FilterablePriceListProps } from "../../../../types/price-list"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { listAndCountPriceListPricingModule } from "./modules-queries"
|
||||
|
||||
/**
|
||||
* @oas [get] /admin/price-lists
|
||||
@@ -161,18 +163,30 @@ import { Type } from "class-transformer"
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req: Request, res) => {
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
|
||||
const validated = req.validatedQuery
|
||||
let priceLists
|
||||
let count
|
||||
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
;[priceLists, count] = await listAndCountPriceListPricingModule({
|
||||
filters: req.filterableFields,
|
||||
listConfig: req.listConfig,
|
||||
container: req.scope as MedusaContainer,
|
||||
})
|
||||
} else {
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
const [price_lists, count] = await priceListService.listAndCount(
|
||||
req.filterableFields,
|
||||
req.listConfig
|
||||
)
|
||||
;[priceLists, count] = await priceListService.listAndCount(
|
||||
req.filterableFields,
|
||||
req.listConfig
|
||||
)
|
||||
}
|
||||
|
||||
res.json({
|
||||
price_lists,
|
||||
price_lists: priceLists,
|
||||
count,
|
||||
offset: validated.offset,
|
||||
limit: validated.limit,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { PriceList } from "../../../../../models"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { listAndCountPriceListPricingModule } from "./list-and-count-price-lists"
|
||||
|
||||
export async function getPriceListPricingModule(
|
||||
id: string,
|
||||
{
|
||||
container,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
}
|
||||
): Promise<PriceList> {
|
||||
const [priceLists, count] = await listAndCountPriceListPricingModule({
|
||||
filters: {
|
||||
id: [id],
|
||||
},
|
||||
container,
|
||||
})
|
||||
|
||||
if (count === 0) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Price list with id: ${id} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return priceLists[0]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./get-price-list"
|
||||
export * from "./list-and-count-price-lists"
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import { FilterablePriceListProps, MedusaContainer } from "@medusajs/types"
|
||||
import { FindConfig } from "../../../../../types/common"
|
||||
import { CustomerGroup, MoneyAmount, PriceList } from "../../../../../models"
|
||||
import { CustomerGroupService } from "../../../../../services"
|
||||
import { defaultAdminPriceListRemoteQueryObject } from "../index"
|
||||
|
||||
export async function listAndCountPriceListPricingModule({
|
||||
filters,
|
||||
listConfig = { skip: 0 },
|
||||
container,
|
||||
}: {
|
||||
container: MedusaContainer
|
||||
filters?: FilterablePriceListProps
|
||||
listConfig?: FindConfig<PriceList>
|
||||
}): Promise<[PriceList[], number]> {
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
const customerGroupService: CustomerGroupService = container.resolve(
|
||||
"customerGroupService"
|
||||
)
|
||||
|
||||
const query = {
|
||||
price_list: {
|
||||
__args: { filters, ...listConfig },
|
||||
...defaultAdminPriceListRemoteQueryObject,
|
||||
},
|
||||
}
|
||||
|
||||
const {
|
||||
rows: priceLists,
|
||||
metadata: { count },
|
||||
} = await remoteQuery(query)
|
||||
|
||||
if (!count) {
|
||||
return [[], 0]
|
||||
}
|
||||
|
||||
const customerGroupIds: string[] = priceLists
|
||||
.map((priceList) =>
|
||||
priceList.price_list_rules
|
||||
.filter((rule) => rule.rule_type.rule_attribute === "customer_group_id")
|
||||
.map((rule) =>
|
||||
rule.price_list_rule_values.map((rule_value) => rule_value.value)
|
||||
)
|
||||
)
|
||||
.flat(2)
|
||||
|
||||
const priceListCustomerGroups = await customerGroupService.list(
|
||||
{ id: customerGroupIds },
|
||||
{}
|
||||
)
|
||||
|
||||
const customerGroupIdCustomerGroupMap = new Map(
|
||||
priceListCustomerGroups.map((customerGroup) => [
|
||||
customerGroup.id,
|
||||
customerGroup,
|
||||
])
|
||||
)
|
||||
|
||||
for (const priceList of priceLists) {
|
||||
const priceSetMoneyAmounts = priceList.price_set_money_amounts || []
|
||||
const priceListRulesData = priceList.price_list_rules || []
|
||||
delete priceList.price_set_money_amounts
|
||||
delete priceList.price_list_rules
|
||||
|
||||
priceList.prices = priceSetMoneyAmounts.map((priceSetMoneyAmount) => {
|
||||
const productVariant = priceSetMoneyAmount.price_set.variant_link.variant
|
||||
|
||||
return {
|
||||
...(priceSetMoneyAmount.money_amount as MoneyAmount),
|
||||
price_list_id: priceList.id,
|
||||
variant_id: productVariant?.id ?? null,
|
||||
variant: productVariant ?? null,
|
||||
region_id: null,
|
||||
}
|
||||
})
|
||||
|
||||
priceList.name = priceList.title
|
||||
delete priceList.title
|
||||
|
||||
const customerGroupPriceListRule = priceListRulesData.find(
|
||||
(plr) => plr.rule_type.rule_attribute === "customer_group_id"
|
||||
)
|
||||
|
||||
if (
|
||||
customerGroupPriceListRule &&
|
||||
customerGroupPriceListRule?.price_list_rule_values
|
||||
) {
|
||||
priceList.customer_groups =
|
||||
customerGroupPriceListRule?.price_list_rule_values
|
||||
.map((customerGroupRule) =>
|
||||
customerGroupIdCustomerGroupMap.get(customerGroupRule.value)
|
||||
)
|
||||
.filter(
|
||||
(
|
||||
customerGroup: CustomerGroup | undefined
|
||||
): customerGroup is CustomerGroup => !!customerGroup
|
||||
)
|
||||
} else {
|
||||
priceList.customer_groups = []
|
||||
}
|
||||
}
|
||||
|
||||
return [priceLists, count]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PriceListStatus, PriceListType } from "@medusajs/utils"
|
||||
import { MedusaContainer, PricingTypes, WorkflowTypes } from "@medusajs/types"
|
||||
import { MedusaV2Flag, PriceListStatus, PriceListType } from "@medusajs/utils"
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
@@ -8,15 +9,17 @@ import {
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { defaultAdminPriceListFields, defaultAdminPriceListRelations } from "."
|
||||
import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list"
|
||||
|
||||
import { updatePriceLists } from "@medusajs/workflows"
|
||||
import { Type } from "class-transformer"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { PriceList } from "../../../.."
|
||||
import TaxInclusivePricingFeatureFlag from "../../../../loaders/feature-flags/tax-inclusive-pricing"
|
||||
import PriceListService from "../../../../services/price-list"
|
||||
import { AdminPriceListPricesUpdateReq } from "../../../../types/price-list"
|
||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { getPriceListPricingModule } from "./modules-queries"
|
||||
|
||||
/**
|
||||
* @oas [post] /admin/price-lists/{id}
|
||||
@@ -83,26 +86,59 @@ import { validator } from "../../../../utils/validator"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
let priceList
|
||||
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
|
||||
const validated = await validator(
|
||||
AdminPostPriceListsPriceListPriceListReq,
|
||||
req.body
|
||||
)
|
||||
|
||||
const priceListService: PriceListService =
|
||||
req.scope.resolve("priceListService")
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
const updateVariantsWorkflow = updatePriceLists(req.scope)
|
||||
const rules: PricingTypes.CreatePriceListRules = {}
|
||||
const customerGroups = validated.customer_groups || []
|
||||
delete validated.customer_groups
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.update(id, validated)
|
||||
})
|
||||
if (customerGroups.length) {
|
||||
rules["customer_group_id"] = customerGroups.map((group) => group.id)
|
||||
}
|
||||
|
||||
const priceList = await priceListService.retrieve(id, {
|
||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
const input = {
|
||||
price_lists: [
|
||||
{
|
||||
id,
|
||||
...validated,
|
||||
rules,
|
||||
},
|
||||
],
|
||||
} as WorkflowTypes.PriceListWorkflow.UpdatePriceListWorkflowInputDTO
|
||||
|
||||
await updateVariantsWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager,
|
||||
},
|
||||
})
|
||||
|
||||
priceList = await getPriceListPricingModule(id, {
|
||||
container: req.scope as MedusaContainer,
|
||||
})
|
||||
} else {
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
return await priceListService
|
||||
.withTransaction(transactionManager)
|
||||
.update(id, validated)
|
||||
})
|
||||
|
||||
priceList = await priceListService.retrieve(id, {
|
||||
select: defaultAdminPriceListFields as (keyof PriceList)[],
|
||||
relations: defaultAdminPriceListRelations,
|
||||
})
|
||||
}
|
||||
|
||||
res.json({ price_list: priceList })
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
CreateProductVariantInput,
|
||||
ProductVariantPricesCreateReq,
|
||||
} from "../../../../types/product-variant"
|
||||
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
|
||||
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
|
||||
import { CreateProductVariants } from "@medusajs/workflows"
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
@@ -11,19 +10,22 @@ import {
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||
import {
|
||||
PricingService,
|
||||
ProductService,
|
||||
ProductVariantInventoryService,
|
||||
ProductVariantService,
|
||||
} from "../../../../services"
|
||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import {
|
||||
CreateProductVariantInput,
|
||||
ProductVariantPricesCreateReq,
|
||||
} from "../../../../types/product-variant"
|
||||
import { Type } from "class-transformer"
|
||||
import { createVariantsTransaction } from "./transaction/create-product-variant"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { getProductWithIsolatedProductModule } from "./get-product"
|
||||
import { createVariantsTransaction } from "./transaction/create-product-variant"
|
||||
|
||||
/**
|
||||
* @oas [post] /admin/products/{id}/variants
|
||||
@@ -122,6 +124,8 @@ export default async (req, res) => {
|
||||
req.body
|
||||
)
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const inventoryService: IInventoryService | undefined =
|
||||
req.scope.resolve("inventoryService")
|
||||
const productVariantInventoryService: ProductVariantInventoryService =
|
||||
@@ -129,29 +133,56 @@ export default async (req, res) => {
|
||||
const productVariantService: ProductVariantService = req.scope.resolve(
|
||||
"productVariantService"
|
||||
)
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await createVariantsTransaction(
|
||||
{
|
||||
manager: transactionManager,
|
||||
inventoryService,
|
||||
productVariantInventoryService,
|
||||
productVariantService,
|
||||
},
|
||||
id,
|
||||
[validated as CreateProductVariantInput]
|
||||
)
|
||||
})
|
||||
|
||||
const productService: ProductService = req.scope.resolve("productService")
|
||||
const pricingService: PricingService = req.scope.resolve("pricingService")
|
||||
let rawProduct
|
||||
|
||||
const rawProduct = await productService.retrieve(id, {
|
||||
select: defaultAdminProductFields,
|
||||
relations: defaultAdminProductRelations,
|
||||
})
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
const createVariantsWorkflow = CreateProductVariants.createProductVariants(
|
||||
req.scope
|
||||
)
|
||||
|
||||
const input = {
|
||||
productVariants: [
|
||||
{
|
||||
product_id: id,
|
||||
...validated,
|
||||
},
|
||||
] as WorkflowTypes.ProductWorkflow.CreateProductVariantsInputDTO[],
|
||||
}
|
||||
|
||||
await createVariantsWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager,
|
||||
},
|
||||
})
|
||||
|
||||
rawProduct = await getProductWithIsolatedProductModule(
|
||||
req,
|
||||
id,
|
||||
req.retrieveConfig
|
||||
)
|
||||
} else {
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await createVariantsTransaction(
|
||||
{
|
||||
manager: transactionManager,
|
||||
inventoryService,
|
||||
productVariantInventoryService,
|
||||
productVariantService,
|
||||
},
|
||||
id,
|
||||
[validated as CreateProductVariantInput]
|
||||
)
|
||||
})
|
||||
|
||||
const productService: ProductService = req.scope.resolve("productService")
|
||||
|
||||
rawProduct = await productService.retrieve(id, {
|
||||
select: defaultAdminProductFields,
|
||||
relations: defaultAdminProductRelations,
|
||||
})
|
||||
}
|
||||
|
||||
const [product] = await pricingService.setAdminProductPricing([rawProduct])
|
||||
|
||||
|
||||
@@ -118,7 +118,11 @@ export default async (req, res) => {
|
||||
res.json({ product })
|
||||
}
|
||||
|
||||
async function getProductWithIsolatedProductModule(req, id, retrieveConfig) {
|
||||
export async function getProductWithIsolatedProductModule(
|
||||
req,
|
||||
id,
|
||||
retrieveConfig
|
||||
) {
|
||||
// TODO: Add support for fields/expands
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
SalesChannelService,
|
||||
} from "../../../../services"
|
||||
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import {
|
||||
IInventoryService,
|
||||
IPricingModuleService,
|
||||
IProductModuleService,
|
||||
} from "@medusajs/types"
|
||||
import { MedusaV2Flag, promiseAll } from "@medusajs/utils"
|
||||
import { Type } from "class-transformer"
|
||||
import { Product } from "../../../../models"
|
||||
@@ -301,7 +305,53 @@ export default async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
async function listAndCountProductWithIsolatedProductModule(
|
||||
async function getVariantsFromPriceList(req, priceListId) {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
const pricingModuleService: IPricingModuleService = req.scope.resolve(
|
||||
"pricingModuleService"
|
||||
)
|
||||
const productModuleService: IProductModuleService = req.scope.resolve(
|
||||
"productModuleService"
|
||||
)
|
||||
|
||||
const [priceList] = await pricingModuleService.listPriceLists(
|
||||
{ id: [priceListId] },
|
||||
{
|
||||
relations: [
|
||||
"price_set_money_amounts",
|
||||
"price_set_money_amounts.price_set",
|
||||
],
|
||||
select: ["price_set_money_amounts.price_set.id"],
|
||||
}
|
||||
)
|
||||
|
||||
const priceSetIds = priceList.price_set_money_amounts?.map(
|
||||
(psma) => psma.price_set?.id
|
||||
)
|
||||
|
||||
const query = {
|
||||
product_variant_price_set: {
|
||||
__args: {
|
||||
price_set_id: priceSetIds,
|
||||
},
|
||||
fields: ["variant_id", "price_set_id"],
|
||||
},
|
||||
}
|
||||
|
||||
const variantPriceSets = await remoteQuery(query)
|
||||
const variantIds = variantPriceSets.map((vps) => vps.variant_id)
|
||||
|
||||
return await productModuleService.listVariants(
|
||||
{
|
||||
id: variantIds,
|
||||
},
|
||||
{
|
||||
select: ["product_id"],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export async function listAndCountProductWithIsolatedProductModule(
|
||||
req,
|
||||
filterableFields,
|
||||
listConfig
|
||||
@@ -309,6 +359,7 @@ async function listAndCountProductWithIsolatedProductModule(
|
||||
// TODO: Add support for fields/expands
|
||||
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
|
||||
const productIdsFilter: Set<string> = new Set()
|
||||
const variantIdsFilter: Set<string> = new Set()
|
||||
@@ -352,22 +403,28 @@ async function listAndCountProductWithIsolatedProductModule(
|
||||
delete filterableFields.price_list_id
|
||||
|
||||
if (priceListId) {
|
||||
// TODO: it is working but validate the behaviour.
|
||||
// e.g pricing context properly set.
|
||||
// At the moment filtering by price list but not having any customer id or
|
||||
// include discount forces the query to filter with price list id is null
|
||||
const priceListService = req.scope.resolve(
|
||||
"priceListService"
|
||||
) as PriceListService
|
||||
promises.push(
|
||||
priceListService
|
||||
.listPriceListsVariantIdsMap(priceListId)
|
||||
.then((priceListVariantIdsMap) => {
|
||||
priceListVariantIdsMap[priceListId].map((variantId) =>
|
||||
variantIdsFilter.add(variantId)
|
||||
)
|
||||
})
|
||||
)
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
const variants = await getVariantsFromPriceList(req, priceListId)
|
||||
|
||||
variants.forEach((pv) => variantIdsFilter.add(pv.id))
|
||||
} else {
|
||||
// TODO: it is working but validate the behaviour.
|
||||
// e.g pricing context properly set.
|
||||
// At the moment filtering by price list but not having any customer id or
|
||||
// include discount forces the query to filter with price list id is null
|
||||
const priceListService = req.scope.resolve(
|
||||
"priceListService"
|
||||
) as PriceListService
|
||||
promises.push(
|
||||
priceListService
|
||||
.listPriceListsVariantIdsMap(priceListId)
|
||||
.then((priceListVariantIdsMap) => {
|
||||
priceListVariantIdsMap[priceListId].map((variantId) =>
|
||||
variantIdsFilter.add(variantId)
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const discountConditionId = filterableFields.discount_condition_id
|
||||
|
||||
@@ -5,7 +5,7 @@ import getMigrations, {
|
||||
runIsolatedModulesMigration,
|
||||
} from "./utils/get-migrations"
|
||||
|
||||
import { MedusaV2Flag, createMedusaContainer } from "@medusajs/utils"
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import configModuleLoader from "../loaders/config"
|
||||
import databaseLoader from "../loaders/database"
|
||||
import featureFlagLoader from "../loaders/feature-flags"
|
||||
@@ -38,6 +38,11 @@ const getDataSource = async (directory) => {
|
||||
const runLinkMigrations = async (directory) => {
|
||||
const configModule = configModuleLoader(directory)
|
||||
const container = createMedusaContainer()
|
||||
const featureFlagRouter = featureFlagLoader(configModule)
|
||||
|
||||
container.register({
|
||||
featureFlagRouter: asValue(featureFlagRouter),
|
||||
})
|
||||
|
||||
await pgConnectionLoader({ configModule, container })
|
||||
|
||||
@@ -63,16 +68,13 @@ const main = async function ({ directory }) {
|
||||
|
||||
const configModule = configModuleLoader(directory)
|
||||
const dataSource = await getDataSource(directory)
|
||||
const featureFlagRouter = featureFlagLoader(configModule)
|
||||
|
||||
if (args[0] === "run") {
|
||||
await dataSource.runMigrations()
|
||||
await dataSource.destroy()
|
||||
await runIsolatedModulesMigration(configModule)
|
||||
|
||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||
await runLinkMigrations(directory)
|
||||
}
|
||||
await runLinkMigrations(directory)
|
||||
process.exit()
|
||||
|
||||
Logger.info("Migrations completed.")
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MedusaV2Flag } from "@medusajs/utils"
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export const featureFlag = MedusaV2Flag.key
|
||||
|
||||
export class DropNonNullConstraintPriceList1699371074198
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE IF EXISTS price_list ALTER COLUMN name DROP NOT NULL;
|
||||
`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE IF EXISTS price_list ALTER COLUMN name SET NOT NULL;
|
||||
`)
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ export class MoneyAmount extends SoftDeletableEntity {
|
||||
|
||||
@Index("idx_money_amount_region_id")
|
||||
@Column({ nullable: true })
|
||||
region_id: string
|
||||
region_id: string | null
|
||||
|
||||
@ManyToOne(() => Region)
|
||||
@JoinColumn({ name: "region_id" })
|
||||
|
||||
@@ -11,17 +11,28 @@ export const createDefaultRuleTypes = async (container: AwilixContainer) => {
|
||||
"pricingModuleService"
|
||||
)
|
||||
const existing = await pricingModuleService.listRuleTypes(
|
||||
{ rule_attribute: ["region_id"] },
|
||||
{ take: 1 }
|
||||
{ rule_attribute: ["region_id", "customer_group_id"] },
|
||||
{ take: 2 }
|
||||
)
|
||||
|
||||
if (existing.length) {
|
||||
if (existing.length === 2) {
|
||||
return
|
||||
}
|
||||
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "region_id", rule_attribute: "region_id" },
|
||||
])
|
||||
if (existing.length === 0) {
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "region_id", rule_attribute: "region_id" },
|
||||
{ name: "customer_group_id", rule_attribute: "customer_group_id" },
|
||||
])
|
||||
} else if (existing[0].rule_attribute === "region_id") {
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "customer_group_id", rule_attribute: "customer_group_id" },
|
||||
])
|
||||
} else {
|
||||
await pricingModuleService.createRuleTypes([
|
||||
{ name: "region_id", rule_attribute: "region_id" },
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const migrate = async function ({ directory }) {
|
||||
@@ -38,7 +49,9 @@ const migrate = async function ({ directory }) {
|
||||
migrate({ directory: process.cwd() })
|
||||
.then(() => {
|
||||
console.log("Created default rule types")
|
||||
process.exit()
|
||||
})
|
||||
.catch(() => {
|
||||
console.log("Failed to create rule types")
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,146 @@
|
||||
import { IPricingModuleService, PricingTypes } from "@medusajs/types"
|
||||
import { AwilixContainer } from "awilix"
|
||||
import dotenv from "dotenv"
|
||||
import express from "express"
|
||||
import loaders from "../loaders"
|
||||
import Logger from "../loaders/logger"
|
||||
import { PriceListService } from "../services"
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const BATCH_SIZE = 1000
|
||||
|
||||
export const migratePriceLists = async (container: AwilixContainer) => {
|
||||
const pricingModuleService: IPricingModuleService = container.resolve(
|
||||
"pricingModuleService"
|
||||
)
|
||||
|
||||
const priceListCoreService: PriceListService =
|
||||
container.resolve("priceListService")
|
||||
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
|
||||
const existingRuleTypes = await pricingModuleService.listRuleTypes(
|
||||
{ rule_attribute: ["customer_group_id"] },
|
||||
{ take: 2 }
|
||||
)
|
||||
|
||||
if (existingRuleTypes.length === 0) {
|
||||
Logger.info(
|
||||
`Run default rules migration before running this migration - node node_modules/@medusajs/medusa/dist/scripts/create-default-rule-types.js`
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let offset = 0
|
||||
let arePriceListsAvailable = true
|
||||
|
||||
while (arePriceListsAvailable) {
|
||||
const priceLists = await priceListCoreService.list(
|
||||
{},
|
||||
{
|
||||
take: BATCH_SIZE,
|
||||
skip: offset,
|
||||
relations: ["customer_groups"],
|
||||
}
|
||||
)
|
||||
|
||||
if (priceLists.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
offset += BATCH_SIZE
|
||||
|
||||
await pricingModuleService.update(
|
||||
priceLists.map((priceList) => {
|
||||
const updateData: PricingTypes.UpdatePriceListDTO = {
|
||||
id: priceList.id,
|
||||
title: priceList.name,
|
||||
}
|
||||
|
||||
if (priceList?.customer_groups?.length) {
|
||||
updateData.rules = {
|
||||
customer_group_id: priceList.customer_groups.map((cg) => cg.id),
|
||||
}
|
||||
}
|
||||
|
||||
return updateData
|
||||
})
|
||||
)
|
||||
|
||||
for (const priceList of priceLists) {
|
||||
let productsOffset = 0
|
||||
let areVariantsAvailable = true
|
||||
|
||||
while (areVariantsAvailable) {
|
||||
const [priceListVariants, variantsCount] =
|
||||
await priceListCoreService.listVariants(
|
||||
priceList.id,
|
||||
{},
|
||||
{
|
||||
skip: productsOffset,
|
||||
take: BATCH_SIZE,
|
||||
}
|
||||
)
|
||||
|
||||
if (variantsCount === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
productsOffset += BATCH_SIZE
|
||||
|
||||
const query = {
|
||||
product_variant_price_set: {
|
||||
__args: {
|
||||
variant_id: priceListVariants.map((plv) => plv.id),
|
||||
},
|
||||
fields: ["variant_id", "price_set_id"],
|
||||
},
|
||||
}
|
||||
|
||||
const variantPriceSets = await remoteQuery(query)
|
||||
const variantPriceSetMap = new Map<string, string>(
|
||||
variantPriceSets.map((mps) => [mps.variant_id, mps.price_set_id])
|
||||
)
|
||||
|
||||
const variantPrices = priceListVariants
|
||||
.map((plv) => plv.prices || [])
|
||||
.flat()
|
||||
|
||||
await pricingModuleService.addPriceListPrices([
|
||||
{
|
||||
priceListId: priceList.id,
|
||||
prices: variantPrices.map((vp) => {
|
||||
return {
|
||||
id: vp.id,
|
||||
price_set_id: variantPriceSetMap.get(vp.variant_id)!,
|
||||
currency_code: vp.currency_code,
|
||||
amount: vp.amount,
|
||||
}
|
||||
}),
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const migrate = async function ({ directory }) {
|
||||
const app = express()
|
||||
const { container } = await loaders({
|
||||
directory,
|
||||
expressApp: app,
|
||||
isTest: false,
|
||||
})
|
||||
|
||||
return await migratePriceLists(container)
|
||||
}
|
||||
|
||||
migrate({ directory: process.cwd() })
|
||||
.then(() => {
|
||||
console.log("Migrated price lists")
|
||||
})
|
||||
.catch(() => {
|
||||
console.log("Failed to migrate price lists")
|
||||
})
|
||||
@@ -45,7 +45,7 @@ const migrateProductVariant = async (
|
||||
rules: [{ rule_attribute: "region_id" }],
|
||||
prices: variant.prices.map((price) => ({
|
||||
rules: {
|
||||
region_id: price.region_id,
|
||||
...(price.region_id ? { region_id: price.region_id } : {}),
|
||||
},
|
||||
currency_code: price.currency_code,
|
||||
min_quantity: price.min_quantity,
|
||||
|
||||
@@ -4,15 +4,18 @@ import {
|
||||
PriceSetMoneyAmountDTO,
|
||||
RemoteQueryFunction,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import {
|
||||
CustomerService,
|
||||
ProductVariantService,
|
||||
RegionService,
|
||||
TaxProviderService,
|
||||
} from "."
|
||||
import {
|
||||
FlagRouter,
|
||||
MedusaV2Flag,
|
||||
promiseAll,
|
||||
removeNullish,
|
||||
} from "@medusajs/utils"
|
||||
import { ProductVariantService, RegionService, TaxProviderService } from "."
|
||||
|
||||
import {
|
||||
IPriceSelectionStrategy,
|
||||
PriceSelectionContext,
|
||||
@@ -33,11 +36,11 @@ import {
|
||||
TaxedPricing,
|
||||
} from "../types/pricing"
|
||||
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing"
|
||||
import { TaxServiceRate } from "../types/tax-service"
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import { calculatePriceTaxAmount } from "../utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
@@ -45,6 +48,7 @@ type InjectedDependencies = {
|
||||
productVariantService: ProductVariantService
|
||||
taxProviderService: TaxProviderService
|
||||
regionService: RegionService
|
||||
customerService: CustomerService
|
||||
priceSelectionStrategy: IPriceSelectionStrategy
|
||||
featureFlagRouter: FlagRouter
|
||||
remoteQuery: RemoteQueryFunction
|
||||
@@ -57,6 +61,7 @@ type InjectedDependencies = {
|
||||
class PricingService extends TransactionBaseService {
|
||||
protected readonly regionService: RegionService
|
||||
protected readonly taxProviderService: TaxProviderService
|
||||
protected readonly customerService_: CustomerService
|
||||
protected readonly priceSelectionStrategy: IPriceSelectionStrategy
|
||||
protected readonly productVariantService: ProductVariantService
|
||||
protected readonly featureFlagRouter: FlagRouter
|
||||
@@ -64,6 +69,7 @@ class PricingService extends TransactionBaseService {
|
||||
protected get pricingModuleService(): IPricingModuleService {
|
||||
return this.__container__.pricingModuleService
|
||||
}
|
||||
|
||||
protected get remoteQuery(): RemoteQueryFunction {
|
||||
return this.__container__.remoteQuery
|
||||
}
|
||||
@@ -74,6 +80,7 @@ class PricingService extends TransactionBaseService {
|
||||
regionService,
|
||||
priceSelectionStrategy,
|
||||
featureFlagRouter,
|
||||
customerService,
|
||||
}: InjectedDependencies) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
super(arguments[0])
|
||||
@@ -82,6 +89,7 @@ class PricingService extends TransactionBaseService {
|
||||
this.taxProviderService = taxProviderService
|
||||
this.priceSelectionStrategy = priceSelectionStrategy
|
||||
this.productVariantService = productVariantService
|
||||
this.customerService_ = customerService
|
||||
this.featureFlagRouter = featureFlagRouter
|
||||
}
|
||||
|
||||
@@ -220,9 +228,20 @@ class PricingService extends TransactionBaseService {
|
||||
(variantPriceSet) => variantPriceSet.price_set_id
|
||||
)
|
||||
|
||||
const queryContext: PriceSelectionContext = removeNullish(
|
||||
context.price_selection
|
||||
)
|
||||
const queryContext: PriceSelectionContext & {
|
||||
customer_group_id?: string[]
|
||||
} = removeNullish(context.price_selection)
|
||||
|
||||
if (queryContext.customer_id) {
|
||||
const { groups } = await this.customerService_.retrieve(
|
||||
queryContext.customer_id,
|
||||
{ relations: ["groups"] }
|
||||
)
|
||||
|
||||
if (groups?.length) {
|
||||
queryContext.customer_group_id = groups.map((group) => group.id)
|
||||
}
|
||||
}
|
||||
|
||||
let calculatedPrices: CalculatedPriceSet[] = []
|
||||
|
||||
@@ -259,25 +278,37 @@ class PricingService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
if (priceSetId) {
|
||||
const calculatedPrice: CalculatedPriceSet | undefined =
|
||||
const calculatedPrices: CalculatedPriceSet | undefined =
|
||||
calculatedPriceMap.get(priceSetId)
|
||||
|
||||
if (calculatedPrice) {
|
||||
pricingResult.prices = [
|
||||
{
|
||||
id: calculatedPrice.calculated_price?.money_amount_id,
|
||||
currency_code: calculatedPrice.currency_code,
|
||||
amount: calculatedPrice.calculated_amount,
|
||||
min_quantity: calculatedPrice.calculated_price?.min_quantity,
|
||||
max_quantity: calculatedPrice.calculated_price?.max_quantity,
|
||||
price_list_id: calculatedPrice.calculated_price?.price_list_id,
|
||||
},
|
||||
] as MoneyAmount[]
|
||||
if (calculatedPrices) {
|
||||
pricingResult.prices.push({
|
||||
id: calculatedPrices?.original_price?.money_amount_id,
|
||||
currency_code: calculatedPrices.currency_code,
|
||||
amount: calculatedPrices.original_amount,
|
||||
min_quantity: calculatedPrices.original_price?.min_quantity,
|
||||
max_quantity: calculatedPrices.original_price?.max_quantity,
|
||||
price_list_id: calculatedPrices.original_price?.price_list_id,
|
||||
} as MoneyAmount)
|
||||
|
||||
pricingResult.original_price = calculatedPrice?.original_amount
|
||||
pricingResult.calculated_price = calculatedPrice?.calculated_amount
|
||||
if (
|
||||
calculatedPrices.calculated_price?.money_amount_id !==
|
||||
calculatedPrices.original_price?.money_amount_id
|
||||
) {
|
||||
pricingResult.prices.push({
|
||||
id: calculatedPrices.calculated_price?.money_amount_id,
|
||||
currency_code: calculatedPrices.currency_code,
|
||||
amount: calculatedPrices.calculated_amount,
|
||||
min_quantity: calculatedPrices.calculated_price?.min_quantity,
|
||||
max_quantity: calculatedPrices.calculated_price?.max_quantity,
|
||||
price_list_id: calculatedPrices.calculated_price?.price_list_id,
|
||||
} as MoneyAmount)
|
||||
}
|
||||
|
||||
pricingResult.original_price = calculatedPrices?.original_amount
|
||||
pricingResult.calculated_price = calculatedPrices?.calculated_amount
|
||||
pricingResult.calculated_price_type =
|
||||
calculatedPrice?.calculated_price?.price_list_type
|
||||
calculatedPrices?.calculated_price?.price_list_type
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { PriceList } from "../models/price-list"
|
||||
import { DateComparisonOperator, FindConfig } from "./common"
|
||||
import { DateComparisonOperator } from "./common"
|
||||
import { XorConstraint } from "./validators/xor"
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user