Files
medusa-store/packages/pricing/src/services/pricing-module.ts
Philip Korsholm edf90eecb4 feat(pricing) Add Price Set Rule Type (#4977)
* initial

* initial service

* update pricing module service

* add integration test for rule-type

* update pricing-module integration tests

* update pricing service interface

* feat(pricing): PriceSets as entry point to pricing module

* chore: add price set money amount

* chore: add price set money amount

* chore: change name of test

* chore: added changeset

* chore: use filterable props from money amount in price sets

* chore: update migrations

* test update integration test

* fix weird behavior

* Update packages/pricing/integration-tests/__fixtures__/rule-type/index.ts

Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>

* Apply suggestions from code review

Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>

* move rule-type to common

* chore: reset migration

* chore: remove incorrect conflicts

* chore: address review

* chore: remove ghost price list

* Apply suggestions from code review

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>

* update id prefix

* use persist not persistAndflush

* rename key_value to rule_attribute

* more renaming

---------

Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
2023-09-14 17:58:31 +02:00

524 lines
14 KiB
TypeScript

import {
Context,
DAL,
FindConfig,
InternalModuleDeclaration,
ModuleJoinerConfig,
PricingContext,
PricingFilters,
PricingTypes,
} from "@medusajs/types"
import { Currency, MoneyAmount, PriceSet, RuleType } from "@models"
import {
CurrencyService,
MoneyAmountService,
PriceSetService,
RuleTypeService,
} from "@services"
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
shouldForceTransaction,
} from "@medusajs/utils"
import { joinerConfig } from "../joiner-config"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
currencyService: CurrencyService<any>
moneyAmountService: MoneyAmountService<any>
ruleTypeService: RuleTypeService<any>
priceSetService: PriceSetService<any>
}
export default class PricingModuleService<
TPriceSet extends PriceSet = PriceSet,
TMoneyAmount extends MoneyAmount = MoneyAmount,
TCurrency extends Currency = Currency,
TRuleType extends RuleType = RuleType
> implements PricingTypes.IPricingModuleService
{
protected baseRepository_: DAL.RepositoryService
protected readonly currencyService_: CurrencyService<TCurrency>
protected readonly moneyAmountService_: MoneyAmountService<TMoneyAmount>
protected readonly ruleTypeService_: RuleTypeService<TRuleType>
protected readonly priceSetService_: PriceSetService<TPriceSet>
constructor(
{
baseRepository,
moneyAmountService,
currencyService,
ruleTypeService,
priceSetService,
}: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
this.baseRepository_ = baseRepository
this.currencyService_ = currencyService
this.moneyAmountService_ = moneyAmountService
this.ruleTypeService_ = ruleTypeService
this.priceSetService_ = priceSetService
}
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig
}
@InjectManager("baseRepository_")
async calculatePrices(
pricingFilters: PricingFilters,
pricingContext: PricingContext = { context: {} },
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.CalculatedPriceSetDTO> {
// Keeping this whole logic raw in here for now as they will undergo
// some changes, will abstract them out once we have a final version
const context = pricingContext.context || {}
const priceSetFilters: PricingTypes.FilterablePriceSetProps = {
id: pricingFilters.id,
}
const priceSets = await this.list(
priceSetFilters,
{
select: [
"id",
"money_amounts.id",
"money_amounts.currency_code",
"money_amounts.amount",
"money_amounts.min_quantity",
"money_amounts.max_quantity",
],
relations: ["money_amounts"],
},
sharedContext
)
const calculatedPrices = priceSets.map(
(priceSet): PricingTypes.CalculatedPriceSetDTO => {
// TODO: This will change with the rules engine selection,
// making a DB query directly instead
// This should look for a default price when no rules apply
// When no price is set, return null values for all cases
const selectedMoneyAmount = priceSet.money_amounts?.find(
(ma) =>
context.currency_code && ma.currency_code === context.currency_code
)
return {
id: priceSet.id,
amount: selectedMoneyAmount?.amount || null,
currency_code: selectedMoneyAmount?.currency_code || null,
min_quantity: selectedMoneyAmount?.min_quantity || null,
max_quantity: selectedMoneyAmount?.max_quantity || null,
}
}
)
return JSON.parse(JSON.stringify(calculatedPrices))
}
@InjectManager("baseRepository_")
async retrieve(
id: string,
config: FindConfig<PricingTypes.PriceSetDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.PriceSetDTO> {
const priceSet = await this.priceSetService_.retrieve(
id,
config,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.PriceSetDTO>(priceSet, {
populate: true,
})
}
@InjectManager("baseRepository_")
async list(
filters: PricingTypes.FilterablePriceSetProps = {},
config: FindConfig<PricingTypes.PriceSetDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.PriceSetDTO[]> {
const priceSets = await this.priceSetService_.list(
filters,
config,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.PriceSetDTO[]>(
priceSets,
{
populate: true,
}
)
}
@InjectManager("baseRepository_")
async listAndCount(
filters: PricingTypes.FilterablePriceSetProps = {},
config: FindConfig<PricingTypes.PriceSetDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[PricingTypes.PriceSetDTO[], number]> {
const [priceSets, count] = await this.priceSetService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<PricingTypes.PriceSetDTO[]>(
priceSets,
{
populate: true,
}
),
count,
]
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async create(
data: PricingTypes.CreatePriceSetDTO[],
@MedusaContext() sharedContext: Context = {}
) {
const priceSets = await this.priceSetService_.create(data, sharedContext)
return this.baseRepository_.serialize<PricingTypes.PriceSetDTO[]>(
priceSets,
{
populate: true,
}
)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async update(
data: PricingTypes.UpdatePriceSetDTO[],
@MedusaContext() sharedContext: Context = {}
) {
const priceSets = await this.priceSetService_.update(data, sharedContext)
return this.baseRepository_.serialize<PricingTypes.PriceSetDTO[]>(
priceSets,
{
populate: true,
}
)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async delete(
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.priceSetService_.delete(ids, sharedContext)
}
@InjectManager("baseRepository_")
async retrieveMoneyAmount(
id: string,
config: FindConfig<PricingTypes.MoneyAmountDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.MoneyAmountDTO> {
const moneyAmount = await this.moneyAmountService_.retrieve(
id,
config,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.MoneyAmountDTO>(
moneyAmount,
{
populate: true,
}
)
}
@InjectManager("baseRepository_")
async listMoneyAmounts(
filters: PricingTypes.FilterableMoneyAmountProps = {},
config: FindConfig<PricingTypes.MoneyAmountDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.MoneyAmountDTO[]> {
const moneyAmounts = await this.moneyAmountService_.list(
filters,
config,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.MoneyAmountDTO[]>(
moneyAmounts,
{
populate: true,
}
)
}
@InjectManager("baseRepository_")
async listAndCountMoneyAmounts(
filters: PricingTypes.FilterableMoneyAmountProps = {},
config: FindConfig<PricingTypes.MoneyAmountDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[PricingTypes.MoneyAmountDTO[], number]> {
const [moneyAmounts, count] = await this.moneyAmountService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<PricingTypes.MoneyAmountDTO[]>(
moneyAmounts,
{
populate: true,
}
),
count,
]
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async createMoneyAmounts(
data: PricingTypes.CreateMoneyAmountDTO[],
@MedusaContext() sharedContext: Context = {}
) {
const moneyAmounts = await this.moneyAmountService_.create(
data,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.MoneyAmountDTO[]>(
moneyAmounts,
{
populate: true,
}
)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async updateMoneyAmounts(
data: PricingTypes.UpdateMoneyAmountDTO[],
@MedusaContext() sharedContext: Context = {}
) {
const moneyAmounts = await this.moneyAmountService_.update(
data,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.MoneyAmountDTO[]>(
moneyAmounts,
{
populate: true,
}
)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async deleteMoneyAmounts(
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.moneyAmountService_.delete(ids, sharedContext)
}
@InjectManager("baseRepository_")
async retrieveCurrency(
code: string,
config: FindConfig<PricingTypes.CurrencyDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.CurrencyDTO> {
const currency = await this.currencyService_.retrieve(
code,
config,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.CurrencyDTO>(currency, {
populate: true,
})
}
@InjectManager("baseRepository_")
async listCurrencies(
filters: PricingTypes.FilterableCurrencyProps = {},
config: FindConfig<PricingTypes.CurrencyDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.CurrencyDTO[]> {
const currencies = await this.currencyService_.list(
filters,
config,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.CurrencyDTO[]>(
currencies,
{
populate: true,
}
)
}
@InjectManager("baseRepository_")
async listAndCountCurrencies(
filters: PricingTypes.FilterableCurrencyProps = {},
config: FindConfig<PricingTypes.CurrencyDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[PricingTypes.CurrencyDTO[], number]> {
const [currencies, count] = await this.currencyService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<PricingTypes.CurrencyDTO[]>(
currencies,
{
populate: true,
}
),
count,
]
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async createCurrencies(
data: PricingTypes.CreateCurrencyDTO[],
@MedusaContext() sharedContext: Context = {}
) {
const currencies = await this.currencyService_.create(data, sharedContext)
return this.baseRepository_.serialize<PricingTypes.CurrencyDTO[]>(
currencies,
{
populate: true,
}
)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async updateCurrencies(
data: PricingTypes.UpdateCurrencyDTO[],
@MedusaContext() sharedContext: Context = {}
) {
const currencies = await this.currencyService_.update(data, sharedContext)
return this.baseRepository_.serialize<PricingTypes.CurrencyDTO[]>(
currencies,
{
populate: true,
}
)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async deleteCurrencies(
currencyCodes: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.currencyService_.delete(currencyCodes, sharedContext)
}
@InjectManager("baseRepository_")
async retrieveRuleType(
id: string,
config: FindConfig<PricingTypes.RuleTypeDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.RuleTypeDTO> {
const ruleType = await this.ruleTypeService_.retrieve(
id,
config,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.RuleTypeDTO>(ruleType, {
populate: true,
})
}
@InjectManager("baseRepository_")
async listRuleTypes(
filters: PricingTypes.FilterableRuleTypeProps = {},
config: FindConfig<PricingTypes.RuleTypeDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.RuleTypeDTO[]> {
const ruleTypes = await this.ruleTypeService_.list(
filters,
config,
sharedContext
)
return this.baseRepository_.serialize<PricingTypes.RuleTypeDTO[]>(
ruleTypes,
{
populate: true,
}
)
}
@InjectManager("baseRepository_")
async listAndCountRuleTypes(
filters: PricingTypes.FilterableRuleTypeProps = {},
config: FindConfig<PricingTypes.RuleTypeDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[PricingTypes.RuleTypeDTO[], number]> {
const [ruleTypes, count] = await this.ruleTypeService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<PricingTypes.RuleTypeDTO[]>(
ruleTypes,
{
populate: true,
}
),
count,
]
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async createRuleTypes(
data: PricingTypes.CreateRuleTypeDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.RuleTypeDTO[]> {
const ruleTypes = await this.ruleTypeService_.create(data, sharedContext)
return this.baseRepository_.serialize<PricingTypes.RuleTypeDTO[]>(
ruleTypes,
{
populate: true,
}
)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async updateRuleTypes(
data: PricingTypes.UpdateRuleTypeDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PricingTypes.RuleTypeDTO[]> {
const ruleTypes = await this.ruleTypeService_.update(data, sharedContext)
return this.baseRepository_.serialize<PricingTypes.RuleTypeDTO[]>(
ruleTypes,
{
populate: true,
}
)
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async deleteRuleTypes(
ruleTypes: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.ruleTypeService_.delete(ruleTypes, sharedContext)
}
}