feat(pricing, types): PriceSets as entry point to pricing module (#4978)

What:
- Adds PriceSet, PriceSetMoneyAmount, updates schema
- Adds service/repo for PriceSet
- Shifts entry point to use PriceSet
- Updates link/joiner config

RESOLVES CORE-1495
This commit is contained in:
Riqwan Thamir
2023-09-11 19:24:31 +02:00
committed by GitHub
parent adf4903003
commit 834da5c41a
29 changed files with 1853 additions and 113 deletions

View File

@@ -0,0 +1,7 @@
---
"@medusajs/pricing": patch
"@medusajs/types": patch
"@medusajs/link-modules": patch
---
feat(pricing,types,link-modules): PriceSets as an entry point to pricing module

View File

@@ -1,4 +1,4 @@
export * from "./inventory-level-stock-location"
export * from "./product-variant-inventory-item"
export * from "./product-variant-money-amount"
export * from "./product-variant-price-set"
export * from "./product-shipping-profile"

View File

@@ -2,22 +2,22 @@ import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { LINKS } from "../links"
export const ProductVariantMoneyAmount: ModuleJoinerConfig = {
serviceName: LINKS.ProductVariantMoneyAmount,
export const ProductVariantPriceSet: ModuleJoinerConfig = {
serviceName: LINKS.ProductVariantPriceSet,
isLink: true,
databaseConfig: {
tableName: "product_variant_money_amount",
idPrefix: "pvma",
tableName: "product_variant_price_set",
idPrefix: "pvps",
},
alias: [
{
name: "product_variant_money_amount",
name: "product_variant_price_set",
},
{
name: "product_variant_money_amounts",
name: "product_variant_price_sets",
},
],
primaryKeys: ["id", "variant_id", "money_amount_id"],
primaryKeys: ["id", "variant_id", "price_set_id"],
relationships: [
{
serviceName: Modules.PRODUCT,
@@ -31,8 +31,8 @@ export const ProductVariantMoneyAmount: ModuleJoinerConfig = {
{
serviceName: Modules.PRICING,
primaryKey: "id",
foreignKey: "money_amount_id",
alias: "money_amount",
foreignKey: "price_set_id",
alias: "price_set",
deleteCascade: true,
},
],
@@ -40,18 +40,18 @@ export const ProductVariantMoneyAmount: ModuleJoinerConfig = {
{
serviceName: Modules.PRODUCT,
relationship: {
serviceName: LINKS.ProductVariantMoneyAmount,
serviceName: LINKS.ProductVariantPriceSet,
primaryKey: "variant_id",
foreignKey: "id",
alias: "prices",
isList: true,
isList: false,
},
},
{
serviceName: Modules.PRICING,
relationship: {
serviceName: LINKS.ProductVariantMoneyAmount,
primaryKey: "money_amount_id",
serviceName: LINKS.ProductVariantPriceSet,
primaryKey: "price_set_id",
foreignKey: "id",
alias: "variant_link",
},

View File

@@ -8,11 +8,11 @@ export const LINKS = {
Modules.INVENTORY,
"inventory_item_id"
),
ProductVariantMoneyAmount: composeLinkName(
ProductVariantPriceSet: composeLinkName(
Modules.PRODUCT,
"variant_id",
Modules.PRICING,
"money_amount_id"
"price_set_id"
),
// Internal services

View File

@@ -0,0 +1,13 @@
import { CreatePriceSetDTO } from "@medusajs/types"
export const defaultPriceSetsData = [
{
id: "price-set-1",
},
{
id: "price-set-2",
},
{
id: "price-set-3",
},
] as unknown as CreatePriceSetDTO[]

View File

@@ -0,0 +1,38 @@
import { CreatePriceSetDTO } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { PriceSet, PriceSetMoneyAmount } from "@models"
import { defaultPriceSetsData } from "./data"
export async function createPriceSets(
manager: SqlEntityManager,
priceSetsData: CreatePriceSetDTO[] = defaultPriceSetsData
): Promise<PriceSet[]> {
const priceSets: PriceSet[] = []
for (let priceSetData of priceSetsData) {
const priceSetDataClone = { ...priceSetData }
const moneyAmountsData = priceSetDataClone.money_amounts || []
delete priceSetDataClone.money_amounts
let priceSet = manager.create(PriceSet, priceSetDataClone) as PriceSet
await manager.persist(priceSet).flush()
for (let moneyAmount of moneyAmountsData) {
const price_set = (await manager.findOne(
PriceSet,
priceSet.id
)) as PriceSet
const psma = manager.create(PriceSetMoneyAmount, {
price_set: price_set.id,
money_amount: moneyAmount.id,
title: "test",
})
manager.persist(psma).flush()
}
}
return priceSets
}

View File

@@ -0,0 +1,388 @@
import { CreatePriceSetDTO } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { MoneyAmount, PriceSet } from "@models"
import { PriceSetRepository } from "@repositories"
import { PriceSetService } from "@services"
import { createCurrencies } from "../../../__fixtures__/currency"
import { createMoneyAmounts } from "../../../__fixtures__/money-amount"
import { createPriceSets } from "../../../__fixtures__/price-set"
import { MikroOrmWrapper } from "../../../utils"
jest.setTimeout(30000)
describe("PriceSet Service", () => {
let service: PriceSetService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager
let data!: PriceSet[]
let moneyAmountsData!: MoneyAmount[]
const moneyAmountsInputData = [
{
id: "money-amount-USD",
currency_code: "USD",
amount: 500,
min_quantity: 1,
max_quantity: 10,
},
]
const priceSetInputData = [
{
id: "price-set-1",
money_amounts: [{ id: "money-amount-USD" }],
},
{
id: "price-set-2",
money_amounts: [],
},
{
id: "price-set-3",
money_amounts: [],
},
]
beforeEach(async () => {
await MikroOrmWrapper.setupDatabase()
repositoryManager = await MikroOrmWrapper.forkManager()
testManager = await MikroOrmWrapper.forkManager()
const priceSetRepository = new PriceSetRepository({
manager: repositoryManager,
})
service = new PriceSetService({
priceSetRepository,
})
await createCurrencies(testManager)
moneyAmountsData = await createMoneyAmounts(
testManager,
moneyAmountsInputData
)
data = await createPriceSets(testManager, priceSetInputData)
})
afterEach(async () => {
await MikroOrmWrapper.clearDatabase()
})
describe("list", () => {
it("should list priceSets", async () => {
const priceSetsResult = await service.list()
const serialized = JSON.parse(JSON.stringify(priceSetsResult))
expect(serialized).toEqual([
expect.objectContaining({
id: "price-set-1",
}),
expect.objectContaining({
id: "price-set-2",
}),
expect.objectContaining({
id: "price-set-3",
}),
])
})
it("should list priceSets by id", async () => {
const priceSetsResult = await service.list({
id: ["price-set-1"],
})
expect(priceSetsResult).toEqual([
expect.objectContaining({
id: "price-set-1",
}),
])
})
it("should list priceSets with relations and selects", async () => {
const priceSetsResult = await service.list(
{
id: ["price-set-1"],
},
{
select: ["id", "money_amounts.id"],
relations: ["money_amounts"],
}
)
const serialized = JSON.parse(JSON.stringify(priceSetsResult))
expect(serialized).toEqual([
{
id: "price-set-1",
money_amounts: [
{
id: "money-amount-USD",
},
],
},
])
})
it("should scope priceSets with currency_code of money amounts", async () => {
const priceSetsResult = await service.list(
{
money_amounts: {
currency_code: ["USD"],
},
},
{
select: ["id", "money_amounts.id"],
relations: ["money_amounts"],
}
)
const serialized = JSON.parse(JSON.stringify(priceSetsResult))
expect(serialized).toEqual([
{
id: "price-set-1",
money_amounts: [
{
id: "money-amount-USD",
},
],
},
])
})
})
it("should not return price sets if money amounts with a currency code dont exist", async () => {
const priceSetsResult = await service.list(
{
money_amounts: {
currency_code: ["DOESNOTEXIST"],
},
},
{
select: ["id", "money_amounts.id"],
relations: ["money_amounts"],
}
)
const serialized = JSON.parse(JSON.stringify(priceSetsResult))
expect(serialized).toEqual([])
})
describe("listAndCount", () => {
it("should return priceSets and count", async () => {
const [priceSetsResult, count] = await service.listAndCount()
expect(count).toEqual(3)
expect(priceSetsResult).toEqual([
expect.objectContaining({
id: "price-set-1",
}),
expect.objectContaining({
id: "price-set-2",
}),
expect.objectContaining({
id: "price-set-3",
}),
])
})
it("should return priceSets and count when filtered", async () => {
const [priceSetsResult, count] = await service.listAndCount({
id: ["price-set-1"],
})
expect(count).toEqual(1)
expect(priceSetsResult).toEqual([
expect.objectContaining({
id: "price-set-1",
}),
])
})
it("should list priceSets with relations and selects", async () => {
const [priceSetsResult, count] = await service.listAndCount(
{
id: ["price-set-1"],
},
{
select: ["id", "min_quantity", "money_amounts.id"],
relations: ["money_amounts"],
}
)
const serialized = JSON.parse(JSON.stringify(priceSetsResult))
expect(count).toEqual(1)
expect(serialized).toEqual([
{
id: "price-set-1",
money_amounts: [
{
id: "money-amount-USD",
},
],
},
])
})
it("should return priceSets and count when using skip and take", async () => {
const [priceSetsResult, count] = await service.listAndCount(
{},
{ skip: 1, take: 1 }
)
expect(count).toEqual(3)
expect(priceSetsResult).toEqual([
expect.objectContaining({
id: "price-set-2",
}),
])
})
it("should return requested fields", async () => {
const [priceSetsResult, count] = await service.listAndCount(
{},
{
take: 1,
select: ["id"],
}
)
const serialized = JSON.parse(JSON.stringify(priceSetsResult))
expect(count).toEqual(3)
expect(serialized).toEqual([
{
id: "price-set-1",
},
])
})
})
describe("retrieve", () => {
const id = "price-set-1"
it("should return priceSet for the given id", async () => {
const priceSet = await service.retrieve(id)
expect(priceSet).toEqual(
expect.objectContaining({
id,
})
)
})
it("should throw an error when priceSet with id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"PriceSet with id: does-not-exist was not found"
)
})
it("should throw an error when a id is not provided", async () => {
let error
try {
await service.retrieve(undefined as unknown as string)
} catch (e) {
error = e
}
expect(error.message).toEqual('"priceSetId" must be defined')
})
it("should return priceSet based on config select param", async () => {
const priceSet = await service.retrieve(id, {
select: ["id"],
})
const serialized = JSON.parse(JSON.stringify(priceSet))
expect(serialized).toEqual({
id,
})
})
})
describe("delete", () => {
const id = "price-set-1"
it("should delete the priceSets given an id successfully", async () => {
await service.delete([id])
const priceSets = await service.list({
id: [id],
})
expect(priceSets).toHaveLength(0)
})
})
describe("update", () => {
const id = "price-set-1"
it("should throw an error when a id does not exist", async () => {
let error
try {
await service.update([
{
id: "does-not-exist",
},
])
} catch (e) {
error = e
}
expect(error.message).toEqual(
'PriceSet with id "does-not-exist" not found'
)
})
})
describe("create", () => {
it("should throw an error when a id does not exist", async () => {
let error
try {
await service.update([
{
random: "does-not-exist",
} as any,
])
} catch (e) {
error = e
}
expect(error.message).toEqual('PriceSet with id "undefined" not found')
})
it("should create a priceSet successfully", async () => {
await service.create([
{
id: "price-set-new",
} as unknown as CreatePriceSetDTO,
])
const [priceSet] = await service.list({
id: ["price-set-new"],
})
expect(priceSet).toEqual(
expect.objectContaining({
id: "price-set-new",
} as unknown as CreatePriceSetDTO)
)
})
})
})

View File

@@ -6,7 +6,7 @@ import { initialize } from "../../../../src"
import { createCurrencies } from "../../../__fixtures__/currency"
import { DB_URL, MikroOrmWrapper } from "../../../utils"
describe("PricingModuleService currency", () => {
describe("PricingModule Service - Currency", () => {
let service: IPricingModuleService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager

View File

@@ -9,7 +9,7 @@ import { DB_URL, MikroOrmWrapper } from "../../../utils"
jest.setTimeout(30000)
describe("MoneyAmount Service", () => {
describe("PricingModule Service - MoneyAmount", () => {
let service: IPricingModuleService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager
@@ -38,9 +38,9 @@ describe("MoneyAmount Service", () => {
await MikroOrmWrapper.clearDatabase()
})
describe("list", () => {
describe("listMoneyAmounts", () => {
it("list moneyAmounts", async () => {
const moneyAmountsResult = await service.list()
const moneyAmountsResult = await service.listMoneyAmounts()
expect(moneyAmountsResult).toEqual([
expect.objectContaining({
@@ -58,8 +58,8 @@ describe("MoneyAmount Service", () => {
])
})
it("list moneyAmounts by id", async () => {
const moneyAmountsResult = await service.list({
it("should list moneyAmounts by id", async () => {
const moneyAmountsResult = await service.listMoneyAmounts({
id: ["money-amount-USD"],
})
@@ -70,8 +70,8 @@ describe("MoneyAmount Service", () => {
])
})
it("list moneyAmounts with relations and selects", async () => {
const moneyAmountsResult = await service.list(
it("should list moneyAmounts with relations and selects", async () => {
const moneyAmountsResult = await service.listMoneyAmounts(
{
id: ["money-amount-USD"],
},
@@ -96,9 +96,10 @@ describe("MoneyAmount Service", () => {
})
})
describe("listAndCount", () => {
describe("listAndCountMoneyAmounts", () => {
it("should return moneyAmounts and count", async () => {
const [moneyAmountsResult, count] = await service.listAndCount()
const [moneyAmountsResult, count] =
await service.listAndCountMoneyAmounts()
expect(count).toEqual(3)
expect(moneyAmountsResult).toEqual([
@@ -115,9 +116,10 @@ describe("MoneyAmount Service", () => {
})
it("should return moneyAmounts and count when filtered", async () => {
const [moneyAmountsResult, count] = await service.listAndCount({
id: ["money-amount-USD"],
})
const [moneyAmountsResult, count] =
await service.listAndCountMoneyAmounts({
id: ["money-amount-USD"],
})
expect(count).toEqual(1)
expect(moneyAmountsResult).toEqual([
@@ -128,15 +130,16 @@ describe("MoneyAmount Service", () => {
})
it("list moneyAmounts with relations and selects", async () => {
const [moneyAmountsResult, count] = await service.listAndCount(
{
id: ["money-amount-USD"],
},
{
select: ["id", "min_quantity", "currency.code"],
relations: ["currency"],
}
)
const [moneyAmountsResult, count] =
await service.listAndCountMoneyAmounts(
{
id: ["money-amount-USD"],
},
{
select: ["id", "min_quantity", "currency.code"],
relations: ["currency"],
}
)
const serialized = JSON.parse(JSON.stringify(moneyAmountsResult))
@@ -154,10 +157,8 @@ describe("MoneyAmount Service", () => {
})
it("should return moneyAmounts and count when using skip and take", async () => {
const [moneyAmountsResult, count] = await service.listAndCount(
{},
{ skip: 1, take: 1 }
)
const [moneyAmountsResult, count] =
await service.listAndCountMoneyAmounts({}, { skip: 1, take: 1 })
expect(count).toEqual(3)
expect(moneyAmountsResult).toEqual([
@@ -168,13 +169,14 @@ describe("MoneyAmount Service", () => {
})
it("should return requested fields", async () => {
const [moneyAmountsResult, count] = await service.listAndCount(
{},
{
take: 1,
select: ["id"],
}
)
const [moneyAmountsResult, count] =
await service.listAndCountMoneyAmounts(
{},
{
take: 1,
select: ["id"],
}
)
const serialized = JSON.parse(JSON.stringify(moneyAmountsResult))
@@ -187,12 +189,12 @@ describe("MoneyAmount Service", () => {
})
})
describe("retrieve", () => {
describe("retrieveMoneyAmount", () => {
const id = "money-amount-USD"
const amount = "500"
it("should return moneyAmount for the given id", async () => {
const moneyAmount = await service.retrieve(id)
const moneyAmount = await service.retrieveMoneyAmount(id)
expect(moneyAmount).toEqual(
expect.objectContaining({
@@ -205,7 +207,7 @@ describe("MoneyAmount Service", () => {
let error
try {
await service.retrieve("does-not-exist")
await service.retrieveMoneyAmount("does-not-exist")
} catch (e) {
error = e
}
@@ -219,7 +221,7 @@ describe("MoneyAmount Service", () => {
let error
try {
await service.retrieve(undefined as unknown as string)
await service.retrieveMoneyAmount(undefined as unknown as string)
} catch (e) {
error = e
}
@@ -228,7 +230,7 @@ describe("MoneyAmount Service", () => {
})
it("should return moneyAmount based on config select param", async () => {
const moneyAmount = await service.retrieve(id, {
const moneyAmount = await service.retrieveMoneyAmount(id, {
select: ["id", "amount"],
})
@@ -241,13 +243,13 @@ describe("MoneyAmount Service", () => {
})
})
describe("delete", () => {
describe("deleteMoneyAmounts", () => {
const id = "money-amount-USD"
it("should delete the moneyAmounts given an id successfully", async () => {
await service.delete([id])
await service.deleteMoneyAmounts([id])
const moneyAmounts = await service.list({
const moneyAmounts = await service.listMoneyAmounts({
id: [id],
})
@@ -255,31 +257,31 @@ describe("MoneyAmount Service", () => {
})
})
describe("update", () => {
describe("updateMoneyAmounts", () => {
const id = "money-amount-USD"
it("should update the amount of the moneyAmount successfully", async () => {
await service.update([
await service.updateMoneyAmounts([
{
id,
amount: 700,
},
])
const moneyAmount = await service.retrieve(id)
const moneyAmount = await service.retrieveMoneyAmount(id)
expect(moneyAmount.amount).toEqual("700")
})
it("should update the currency of the moneyAmount successfully", async () => {
await service.update([
await service.updateMoneyAmounts([
{
id,
currency_code: "EUR",
},
])
const moneyAmount = await service.retrieve(id, {
const moneyAmount = await service.retrieveMoneyAmount(id, {
relations: ["currency"],
})
@@ -291,7 +293,7 @@ describe("MoneyAmount Service", () => {
let error
try {
await service.update([
await service.updateMoneyAmounts([
{
id: "does-not-exist",
amount: 666,
@@ -307,9 +309,9 @@ describe("MoneyAmount Service", () => {
})
})
describe("create", () => {
describe("createMoneyAmounts", () => {
it("should create a moneyAmount successfully", async () => {
await service.create([
await service.createMoneyAmounts([
{
id: "money-amount-TESM",
currency_code: "USD",
@@ -319,7 +321,7 @@ describe("MoneyAmount Service", () => {
},
])
const [moneyAmount] = await service.list({
const [moneyAmount] = await service.listMoneyAmounts({
id: ["money-amount-TESM"],
})

View File

@@ -0,0 +1,404 @@
import { CreatePriceSetDTO, IPricingModuleService } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { PriceSet } from "@models"
import { initialize } from "../../../../src"
import { createCurrencies } from "../../../__fixtures__/currency"
import { createMoneyAmounts } from "../../../__fixtures__/money-amount"
import { createPriceSets } from "../../../__fixtures__/price-set"
import { DB_URL, MikroOrmWrapper } from "../../../utils"
jest.setTimeout(30000)
describe("PricingModule Service - PriceSet", () => {
let service: IPricingModuleService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager
let data!: PriceSet[]
const moneyAmountsInputData = [
{
id: "money-amount-USD",
currency_code: "USD",
amount: 500,
min_quantity: 1,
max_quantity: 10,
},
{
id: "money-amount-EUR",
currency_code: "EUR",
amount: 500,
min_quantity: 1,
max_quantity: 10,
},
]
const priceSetInputData = [
{
id: "price-set-1",
money_amounts: [{ id: "money-amount-USD" }],
},
{
id: "price-set-2",
money_amounts: [{ id: "money-amount-EUR" }],
},
{
id: "price-set-3",
money_amounts: [],
},
]
beforeEach(async () => {
await MikroOrmWrapper.setupDatabase()
repositoryManager = MikroOrmWrapper.forkManager()
service = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PRICING_DB_SCHEMA,
},
})
testManager = MikroOrmWrapper.forkManager()
await createCurrencies(testManager)
await createMoneyAmounts(testManager, moneyAmountsInputData)
data = await createPriceSets(testManager, priceSetInputData)
})
afterEach(async () => {
await MikroOrmWrapper.clearDatabase()
})
describe("calculatePrices", () => {
it("retrieves the calculated prices when no context is set", async () => {
const priceSetsResult = await service.calculatePrices(
["price-set-1", "price-set-2"],
{}
)
expect(priceSetsResult).toEqual([
{
id: "price-set-1",
amount: null,
currency_code: null,
min_quantity: null,
max_quantity: null,
},
{
id: "price-set-2",
amount: null,
currency_code: null,
min_quantity: null,
max_quantity: null,
},
])
})
it("retrieves the calculated prices when a context is set", async () => {
const priceSetsResult = await service.calculatePrices(
["price-set-1", "price-set-2"],
{
currency_code: "USD",
}
)
expect(priceSetsResult).toEqual([
{
id: "price-set-1",
amount: "500",
currency_code: "USD",
min_quantity: "1",
max_quantity: "10",
},
{
id: "price-set-2",
amount: null,
currency_code: null,
min_quantity: null,
max_quantity: null,
},
])
})
it("retrieves the calculated prices only when id exists in the database", async () => {
const priceSetsResult = await service.calculatePrices(
["price-set-doesnotexist", "price-set-1"],
{
currency_code: "USD",
}
)
expect(priceSetsResult).toEqual([
{
id: "price-set-1",
amount: "500",
currency_code: "USD",
min_quantity: "1",
max_quantity: "10",
},
])
})
})
describe("list", () => {
it("list priceSets", async () => {
const priceSetsResult = await service.list()
expect(priceSetsResult).toEqual([
expect.objectContaining({
id: "price-set-1",
}),
expect.objectContaining({
id: "price-set-2",
}),
expect.objectContaining({
id: "price-set-3",
}),
])
})
it("list priceSets by id", async () => {
const priceSetsResult = await service.list({
id: ["price-set-1"],
})
expect(priceSetsResult).toEqual([
expect.objectContaining({
id: "price-set-1",
}),
])
})
it("list priceSets with relations and selects", async () => {
const priceSetsResult = await service.list(
{
id: ["price-set-1"],
},
{
select: ["id", "money_amounts.id", "money_amounts.amount"],
relations: ["money_amounts"],
}
)
const serialized = JSON.parse(JSON.stringify(priceSetsResult))
expect(serialized).toEqual([
{
id: "price-set-1",
money_amounts: [{ id: "money-amount-USD", amount: "500" }],
},
])
})
})
describe("listAndCount", () => {
it("should return priceSets and count", async () => {
const [priceSetsResult, count] = await service.listAndCount()
expect(count).toEqual(3)
expect(priceSetsResult).toEqual([
expect.objectContaining({
id: "price-set-1",
}),
expect.objectContaining({
id: "price-set-2",
}),
expect.objectContaining({
id: "price-set-3",
}),
])
})
it("should return priceSets and count when filtered", async () => {
const [priceSetsResult, count] = await service.listAndCount({
id: ["price-set-1"],
})
expect(count).toEqual(1)
expect(priceSetsResult).toEqual([
expect.objectContaining({
id: "price-set-1",
}),
])
})
it("list priceSets with relations and selects", async () => {
const [priceSetsResult, count] = await service.listAndCount(
{
id: ["price-set-1"],
},
{
select: ["id", "min_quantity", "money_amounts.id"],
relations: ["money_amounts"],
}
)
const serialized = JSON.parse(JSON.stringify(priceSetsResult))
expect(count).toEqual(1)
expect(serialized).toEqual([
{
id: "price-set-1",
money_amounts: [{ id: "money-amount-USD" }],
},
])
})
it("should return priceSets and count when using skip and take", async () => {
const [priceSetsResult, count] = await service.listAndCount(
{},
{ skip: 1, take: 1 }
)
expect(count).toEqual(3)
expect(priceSetsResult).toEqual([
expect.objectContaining({
id: "price-set-2",
}),
])
})
it("should return requested fields", async () => {
const [priceSetsResult, count] = await service.listAndCount(
{},
{
take: 1,
select: ["id"],
}
)
const serialized = JSON.parse(JSON.stringify(priceSetsResult))
expect(count).toEqual(3)
expect(serialized).toEqual([
{
id: "price-set-1",
},
])
})
})
describe("retrieve", () => {
const id = "price-set-1"
it("should return priceSet for the given id", async () => {
const priceSet = await service.retrieve(id)
expect(priceSet).toEqual(
expect.objectContaining({
id,
})
)
})
it("should throw an error when priceSet with id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"PriceSet with id: does-not-exist was not found"
)
})
it("should throw an error when a id is not provided", async () => {
let error
try {
await service.retrieve(undefined as unknown as string)
} catch (e) {
error = e
}
expect(error.message).toEqual('"priceSetId" must be defined')
})
it("should return priceSet based on config select param", async () => {
const priceSet = await service.retrieve(id, {
select: ["id"],
})
const serialized = JSON.parse(JSON.stringify(priceSet))
expect(serialized).toEqual({
id,
})
})
})
describe("delete", () => {
const id = "price-set-1"
it("should delete the priceSets given an id successfully", async () => {
await service.delete([id])
const priceSets = await service.list({
id: [id],
})
expect(priceSets).toHaveLength(0)
})
})
describe("update", () => {
const id = "price-set-1"
it("should throw an error when a id does not exist", async () => {
let error
try {
await service.update([
{
id: "does-not-exist",
},
])
} catch (e) {
error = e
}
expect(error.message).toEqual(
'PriceSet with id "does-not-exist" not found'
)
})
})
describe("create", () => {
it("should throw an error when a id does not exist", async () => {
let error
try {
await service.update([
{
random: "does-not-exist",
} as any,
])
} catch (e) {
error = e
}
expect(error.message).toEqual('PriceSet with id "undefined" not found')
})
it("should create a priceSet successfully", async () => {
await service.create([
{
id: "price-set-new",
} as unknown as CreatePriceSetDTO,
])
const [priceSet] = await service.list({
id: ["price-set-new"],
})
expect(priceSet).toEqual(
expect.objectContaining({
id: "price-set-new",
})
)
})
})
})

View File

@@ -1,16 +1,22 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import { Currency, MoneyAmount } from "@models"
import * as Models from "@models"
export enum LinkableKeys {
MONEY_AMOUNT_ID = "money_amount_id",
CURRENCY_CODE = "currency_code",
PRICE_SET_ID = "price_set_id",
}
export const entityNameToLinkableKeysMap: MapToConfig = {
[Currency.name]: [{ mapTo: LinkableKeys.CURRENCY_CODE, valueFrom: "code" }],
[MoneyAmount.name]: [
[Models.PriceSet.name]: [
{ mapTo: LinkableKeys.PRICE_SET_ID, valueFrom: "id" },
],
[Models.Currency.name]: [
{ mapTo: LinkableKeys.CURRENCY_CODE, valueFrom: "code" },
],
[Models.MoneyAmount.name]: [
{ mapTo: LinkableKeys.MONEY_AMOUNT_ID, valueFrom: "id" },
],
}
@@ -20,11 +26,23 @@ export const joinerConfig: ModuleJoinerConfig = {
primaryKeys: ["id", "currency_code"],
linkableKeys: Object.values(LinkableKeys),
alias: [
{
name: "price_set",
},
{
name: "price_sets",
},
{
name: "money_amount",
args: {
methodSuffix: "MoneyAmounts",
},
},
{
name: "money_amounts",
args: {
methodSuffix: "MoneyAmounts",
},
},
{
name: "currency",

View File

@@ -1,11 +1,6 @@
import { ModulesSdkTypes } from "@medusajs/types"
import * as defaultRepositories from "@repositories"
import {
BaseRepository,
CurrencyRepository,
MoneyAmountRepository,
} from "@repositories"
import { CurrencyService, MoneyAmountService } from "@services"
import * as defaultServices from "@services"
import { LoaderOptions } from "@medusajs/modules-sdk"
import { loadCustomRepositories } from "@medusajs/utils"
@@ -23,8 +18,9 @@ export default async ({
)?.repositories
container.register({
currencyService: asClass(CurrencyService).singleton(),
moneyAmountService: asClass(MoneyAmountService).singleton(),
currencyService: asClass(defaultServices.CurrencyService).singleton(),
moneyAmountService: asClass(defaultServices.MoneyAmountService).singleton(),
priceSetService: asClass(defaultServices.PriceSetService).singleton(),
})
if (customRepositories) {
@@ -40,8 +36,15 @@ export default async ({
function loadDefaultRepositories({ container }) {
container.register({
baseRepository: asClass(BaseRepository).singleton(),
currencyRepository: asClass(CurrencyRepository).singleton(),
moneyAmountRepository: asClass(MoneyAmountRepository).singleton(),
baseRepository: asClass(defaultRepositories.BaseRepository).singleton(),
currencyRepository: asClass(
defaultRepositories.CurrencyRepository
).singleton(),
moneyAmountRepository: asClass(
defaultRepositories.MoneyAmountRepository
).singleton(),
priceSetRepository: asClass(
defaultRepositories.PriceSetRepository
).singleton(),
})
}

View File

@@ -0,0 +1,260 @@
{
"namespaces": [
"public"
],
"name": "public",
"tables": [
{
"columns": {
"code": {
"name": "code",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"symbol": {
"name": "symbol",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"symbol_native": {
"name": "symbol_native",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"name": {
"name": "name",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
}
},
"name": "currency",
"schema": "public",
"indexes": [
{
"keyName": "currency_pkey",
"columnNames": [
"code"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
},
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"currency_code": {
"name": "currency_code",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"amount": {
"name": "amount",
"type": "numeric",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "decimal"
},
"min_quantity": {
"name": "min_quantity",
"type": "numeric",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "decimal"
},
"max_quantity": {
"name": "max_quantity",
"type": "numeric",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "decimal"
}
},
"name": "money_amount",
"schema": "public",
"indexes": [
{
"columnNames": [
"currency_code"
],
"composite": false,
"keyName": "IDX_money_amount_currency_code",
"primary": false,
"unique": false
},
{
"keyName": "money_amount_pkey",
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {
"money_amount_currency_code_foreign": {
"constraintName": "money_amount_currency_code_foreign",
"columnNames": [
"currency_code"
],
"localTableName": "public.money_amount",
"referencedColumnNames": [
"code"
],
"referencedTableName": "public.currency",
"deleteRule": "set null",
"updateRule": "cascade"
}
}
},
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
}
},
"name": "price_set",
"schema": "public",
"indexes": [
{
"keyName": "price_set_pkey",
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
},
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"price_set_id": {
"name": "price_set_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"money_amount_id": {
"name": "money_amount_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"title": {
"name": "title",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
}
},
"name": "price_set_money_amount",
"schema": "public",
"indexes": [
{
"keyName": "price_set_money_amount_pkey",
"columnNames": [
"id",
"price_set_id",
"money_amount_id"
],
"composite": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {
"price_set_money_amount_price_set_id_foreign": {
"constraintName": "price_set_money_amount_price_set_id_foreign",
"columnNames": [
"price_set_id"
],
"localTableName": "public.price_set_money_amount",
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.price_set",
"updateRule": "cascade"
},
"price_set_money_amount_money_amount_id_foreign": {
"constraintName": "price_set_money_amount_money_amount_id_foreign",
"columnNames": [
"money_amount_id"
],
"localTableName": "public.price_set_money_amount",
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.money_amount",
"updateRule": "cascade"
}
}
}
]
}

View File

@@ -1,14 +0,0 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20230830085850 extends Migration {
async up(): Promise<void> {
this.addSql('create table "currency" ("code" text not null, "symbol" text not null, "symbol_native" text not null, "name" text not null, constraint "currency_pkey" primary key ("code"));');
this.addSql('create table "money_amount" ("id" text not null, "currency_code" text null, "amount" numeric null, "min_quantity" numeric null, "max_quantity" numeric null, constraint "money_amount_pkey" primary key ("id"));');
this.addSql('create index "IDX_money_amount_currency_code" on "money_amount" ("currency_code");');
this.addSql('alter table "money_amount" add constraint "money_amount_currency_code_foreign" foreign key ("currency_code") references "currency" ("code") on update cascade on delete set null;');
}
}

View File

@@ -0,0 +1,36 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20230907144224 extends Migration {
async up(): Promise<void> {
this.addSql(
'create table "currency" ("code" text not null, "symbol" text not null, "symbol_native" text not null, "name" text not null, constraint "currency_pkey" primary key ("code"));'
)
this.addSql(
'create table "money_amount" ("id" text not null, "currency_code" text null, "amount" numeric null, "min_quantity" numeric null, "max_quantity" numeric null, constraint "money_amount_pkey" primary key ("id"));'
)
this.addSql(
'create index "IDX_money_amount_currency_code" on "money_amount" ("currency_code");'
)
this.addSql(
'create table "price_set" ("id" text not null, constraint "price_set_pkey" primary key ("id"));'
)
this.addSql(
'create table "price_set_money_amount" ("id" text not null, "price_set_id" text null, "money_amount_id" text null, "title" text not null, constraint "price_set_money_amount_pkey" primary key ("id", "price_set_id", "money_amount_id"));'
)
this.addSql(
'alter table "money_amount" add constraint "money_amount_currency_code_foreign" foreign key ("currency_code") references "currency" ("code") on update cascade on delete set null;'
)
this.addSql(
'alter table "price_set_money_amount" add constraint "price_set_money_amount_price_set_id_foreign" foreign key ("price_set_id") references "price_set" ("id") on update cascade;'
)
this.addSql(
'alter table "price_set_money_amount" add constraint "price_set_money_amount_money_amount_id_foreign" foreign key ("money_amount_id") references "money_amount" ("id") on update cascade;'
)
}
}

View File

@@ -1,2 +1,4 @@
export { default as Currency } from "./currency"
export { default as MoneyAmount } from "./money-amount"
export { default as PriceSet } from "./price-set"
export { default as PriceSetMoneyAmount } from "./price-set-money-amount"

View File

@@ -1,13 +1,16 @@
import { generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
Collection,
Entity,
ManyToMany,
ManyToOne,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Currency from "./currency"
import PriceSet from "./price-set"
@Entity()
class MoneyAmount {
@@ -17,6 +20,12 @@ class MoneyAmount {
@Property({ columnType: "text", nullable: true })
currency_code?: string
@ManyToMany({
entity: () => PriceSet,
mappedBy: (ps) => ps.money_amounts,
})
price_sets = new Collection<PriceSet>(this)
@ManyToOne(() => Currency, {
nullable: true,
index: "IDX_money_amount_currency_code",

View File

@@ -0,0 +1,39 @@
import { generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
Entity,
ManyToOne,
PrimaryKey,
PrimaryKeyType,
Property,
} from "@mikro-orm/core"
import MoneyAmount from "./money-amount"
import PriceSet from "./price-set"
@Entity()
export default class PriceSetMoneyAmount {
@PrimaryKey({ columnType: "text" })
id!: string
@Property({ columnType: "text" })
title!: string
@ManyToOne(() => PriceSet, { onDelete: "cascade" })
price_set?: PriceSet
@ManyToOne(() => MoneyAmount, {})
money_amount?: MoneyAmount
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "psma")
}
[PrimaryKeyType]?: [string, string]
constructor(money_amount: MoneyAmount, price_set: PriceSet) {
this.money_amount = money_amount
this.price_set = price_set
}
}

View File

@@ -0,0 +1,28 @@
import { generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
Collection,
Entity,
ManyToMany,
PrimaryKey,
} from "@mikro-orm/core"
import MoneyAmount from "./money-amount"
import PriceSetMoneyAmount from "./price-set-money-amount"
@Entity()
export default class PriceSet {
@PrimaryKey({ columnType: "text" })
id!: string
@ManyToMany({
entity: () => MoneyAmount,
pivotEntity: () => PriceSetMoneyAmount,
})
money_amounts = new Collection<MoneyAmount>(this)
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "pset")
}
}

View File

@@ -1,3 +1,4 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
export { CurrencyRepository } from "./currency"
export { MoneyAmountRepository } from "./money-amount"
export { PriceSetRepository } from "./price-set"

View File

@@ -0,0 +1,127 @@
import {
Context,
CreatePriceSetDTO,
DAL,
UpdatePriceSetDTO,
} from "@medusajs/types"
import { DALUtils, MedusaError } from "@medusajs/utils"
import {
LoadStrategy,
FilterQuery as MikroFilterQuery,
FindOptions as MikroOptions,
} from "@mikro-orm/core"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { PriceSet } from "@models"
export class PriceSetRepository extends DALUtils.MikroOrmBaseRepository {
protected readonly manager_: SqlEntityManager
constructor({ manager }: { manager: SqlEntityManager }) {
// @ts-ignore
// eslint-disable-next-line prefer-rest-params
super(...arguments)
this.manager_ = manager
}
async find(
findOptions: DAL.FindOptions<PriceSet> = { where: {} },
context: Context = {}
): Promise<PriceSet[]> {
const manager = this.getActiveManager<SqlEntityManager>(context)
const findOptions_ = { ...findOptions }
findOptions_.options ??= {}
Object.assign(findOptions_.options, {
strategy: LoadStrategy.SELECT_IN,
})
return await manager.find(
PriceSet,
findOptions_.where as MikroFilterQuery<PriceSet>,
findOptions_.options as MikroOptions<PriceSet>
)
}
async findAndCount(
findOptions: DAL.FindOptions<PriceSet> = { where: {} },
context: Context = {}
): Promise<[PriceSet[], number]> {
const manager = this.getActiveManager<SqlEntityManager>(context)
const findOptions_ = { ...findOptions }
findOptions_.options ??= {}
Object.assign(findOptions_.options, {
strategy: LoadStrategy.SELECT_IN,
})
return await manager.findAndCount(
PriceSet,
findOptions_.where as MikroFilterQuery<PriceSet>,
findOptions_.options as MikroOptions<PriceSet>
)
}
async delete(ids: string[], context: Context = {}): Promise<void> {
const manager = this.getActiveManager<SqlEntityManager>(context)
await manager.nativeDelete(PriceSet, { id: { $in: ids } }, {})
}
async create(
data: CreatePriceSetDTO[],
context: Context = {}
): Promise<PriceSet[]> {
const manager = this.getActiveManager<SqlEntityManager>(context)
const priceSets = data.map((priceSetData) => {
return manager.create(PriceSet, priceSetData)
})
manager.persist(priceSets)
return priceSets
}
async update(
data: UpdatePriceSetDTO[],
context: Context = {}
): Promise<PriceSet[]> {
const manager = this.getActiveManager<SqlEntityManager>(context)
const priceSetIds = data.map((priceSetData) => priceSetData.id)
const existingPriceSets = await this.find(
{
where: {
id: {
$in: priceSetIds,
},
},
},
context
)
const existingPriceSetMap = new Map(
existingPriceSets.map<[string, PriceSet]>((priceSet) => [
priceSet.id,
priceSet,
])
)
const priceSets = data.map((priceSetData) => {
const existingPriceSet = existingPriceSetMap.get(priceSetData.id)
if (!existingPriceSet) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`PriceSet with id "${priceSetData.id}" not found`
)
}
return manager.assign(existingPriceSet, priceSetData)
})
manager.persist(priceSets)
return priceSets
}
}

View File

@@ -22,11 +22,14 @@ export async function run({
logger.info(`Loading seed data from ${path}...`)
const { currenciesData, moneyAmountsData } = await import(
resolve(process.cwd(), path)
).catch((e) => {
const {
currenciesData,
moneyAmountsData,
priceSetsData,
priceSetMoneyAmountsData,
} = await import(resolve(process.cwd(), path)).catch((e) => {
logger?.error(
`Failed to load seed data from ${path}. Please, provide a relative path and check that you export the following: currenciesData, moneyAmountsData.${EOL}${e}`
`Failed to load seed data from ${path}. Please, provide a relative path and check that you export the following: priceSetsData, currenciesData, moneyAmountsData and priceSetMoneyAmountsData.${EOL}${e}`
)
throw e
})
@@ -44,10 +47,12 @@ export async function run({
const manager = orm.em.fork()
try {
logger.info("Inserting currencies & money_amounts")
logger.info("Inserting price_sets, currencies & money_amounts")
await createCurrencies(manager, currenciesData)
await createMoneyAmounts(manager, moneyAmountsData)
await createPriceSets(manager, priceSetsData)
await createPriceSetMoneyAmounts(manager, priceSetMoneyAmountsData)
} catch (e) {
logger.error(
`Failed to insert the seed data in the PostgreSQL database ${dbData.clientUrl}.${EOL}${e}`
@@ -82,3 +87,32 @@ async function createMoneyAmounts(
return moneyAmounts
}
async function createPriceSets(
manager: SqlEntityManager,
data: RequiredEntityData<PricingModels.PriceSet>[]
) {
const priceSets = data.map((priceSetData) => {
return manager.create(PricingModels.PriceSet, priceSetData)
})
await manager.persistAndFlush(priceSets)
return priceSets
}
async function createPriceSetMoneyAmounts(
manager: SqlEntityManager,
data: RequiredEntityData<PricingModels.PriceSetMoneyAmount>[]
) {
const priceSetMoneyAmounts = data.map((priceSetMoneyAmountData) => {
return manager.create(
PricingModels.PriceSetMoneyAmount,
priceSetMoneyAmountData
)
})
await manager.persistAndFlush(priceSetMoneyAmounts)
return priceSetMoneyAmounts
}

View File

@@ -1,3 +1,4 @@
export { default as CurrencyService } from "./currency"
export { default as MoneyAmountService } from "./money-amount"
export { default as PriceSetService } from "./price-set"
export { default as PricingModuleService } from "./pricing-module"

View File

@@ -0,0 +1,106 @@
import { Context, DAL, FindConfig, PricingTypes } from "@medusajs/types"
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
ModulesSdkUtils,
doNotForceTransaction,
retrieveEntity,
shouldForceTransaction,
} from "@medusajs/utils"
import { PriceSet } from "@models"
import { PriceSetRepository } from "@repositories"
type InjectedDependencies = {
priceSetRepository: DAL.RepositoryService
}
export default class PriceSetService<TEntity extends PriceSet = PriceSet> {
protected readonly priceSetRepository_: DAL.RepositoryService
constructor({ priceSetRepository }: InjectedDependencies) {
this.priceSetRepository_ = priceSetRepository
}
@InjectManager("priceSetRepository_")
async retrieve(
priceSetId: string,
config: FindConfig<PricingTypes.PriceSetDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity> {
return (await retrieveEntity<PriceSet, PricingTypes.PriceSetDTO>({
id: priceSetId,
entityName: PriceSet.name,
repository: this.priceSetRepository_,
config,
sharedContext,
})) as TEntity
}
@InjectManager("priceSetRepository_")
async list(
filters: PricingTypes.FilterablePriceSetProps = {},
config: FindConfig<PricingTypes.PriceSetDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
return (await this.priceSetRepository_.find(
this.buildQueryForList(filters, config),
sharedContext
)) as TEntity[]
}
@InjectManager("priceSetRepository_")
async listAndCount(
filters: PricingTypes.FilterablePriceSetProps = {},
config: FindConfig<PricingTypes.PriceSetDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[TEntity[], number]> {
return (await this.priceSetRepository_.findAndCount(
this.buildQueryForList(filters, config),
sharedContext
)) as [TEntity[], number]
}
private buildQueryForList(
filters: PricingTypes.FilterablePriceSetProps = {},
config: FindConfig<PricingTypes.PriceSetDTO> = {}
) {
const queryOptions = ModulesSdkUtils.buildQuery<PriceSet>(filters, config)
if (filters.id) {
queryOptions.where.id = { $in: filters.id }
}
return queryOptions
}
@InjectTransactionManager(shouldForceTransaction, "priceSetRepository_")
async create(
data: PricingTypes.CreatePriceSetDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
return (await (this.priceSetRepository_ as PriceSetRepository).create(
data,
sharedContext
)) as TEntity[]
}
@InjectTransactionManager(shouldForceTransaction, "priceSetRepository_")
async update(
data: PricingTypes.UpdatePriceSetDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<TEntity[]> {
return (await (this.priceSetRepository_ as PriceSetRepository).update(
data,
sharedContext
)) as TEntity[]
}
@InjectTransactionManager(doNotForceTransaction, "priceSetRepository_")
async delete(
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
await this.priceSetRepository_.delete(ids, sharedContext)
}
}

View File

@@ -6,8 +6,8 @@ import {
ModuleJoinerConfig,
PricingTypes,
} from "@medusajs/types"
import { Currency, MoneyAmount } from "@models"
import { CurrencyService, MoneyAmountService } from "@services"
import { Currency, MoneyAmount, PriceSet } from "@models"
import { CurrencyService, MoneyAmountService, PriceSetService } from "@services"
import {
InjectManager,
@@ -22,9 +22,15 @@ type InjectedDependencies = {
baseRepository: DAL.RepositoryService
currencyService: CurrencyService<any>
moneyAmountService: MoneyAmountService<any>
priceSetService: PriceSetService<any>
}
type PricingContext = {
currency_code?: string
}
export default class PricingModuleService<
TPriceSet extends PriceSet = PriceSet,
TMoneyAmount extends MoneyAmount = MoneyAmount,
TCurrency extends Currency = Currency
> implements PricingTypes.IPricingModuleService
@@ -32,26 +38,180 @@ export default class PricingModuleService<
protected baseRepository_: DAL.RepositoryService
protected readonly currencyService_: CurrencyService<TCurrency>
protected readonly moneyAmountService_: MoneyAmountService<TMoneyAmount>
protected readonly priceSetService_: PriceSetService<TPriceSet>
constructor(
{
baseRepository,
moneyAmountService,
currencyService,
priceSetService,
}: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
this.baseRepository_ = baseRepository
this.currencyService_ = currencyService
this.moneyAmountService_ = moneyAmountService
this.priceSetService_ = priceSetService
}
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig
}
@InjectManager("baseRepository_")
async calculatePrices(
priceSetIds: string[],
pricingContext: PricingContext,
@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 priceSetFilters: PricingTypes.FilterablePriceSetProps = {
id: priceSetIds,
}
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) =>
pricingContext.currency_code &&
ma.currency_code === pricingContext.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 = {}
@@ -71,7 +231,7 @@ export default class PricingModuleService<
}
@InjectManager("baseRepository_")
async list(
async listMoneyAmounts(
filters: PricingTypes.FilterableMoneyAmountProps = {},
config: FindConfig<PricingTypes.MoneyAmountDTO> = {},
@MedusaContext() sharedContext: Context = {}
@@ -91,7 +251,7 @@ export default class PricingModuleService<
}
@InjectManager("baseRepository_")
async listAndCount(
async listAndCountMoneyAmounts(
filters: PricingTypes.FilterableMoneyAmountProps = {},
config: FindConfig<PricingTypes.MoneyAmountDTO> = {},
@MedusaContext() sharedContext: Context = {}
@@ -114,7 +274,7 @@ export default class PricingModuleService<
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async create(
async createMoneyAmounts(
data: PricingTypes.CreateMoneyAmountDTO[],
@MedusaContext() sharedContext: Context = {}
) {
@@ -132,7 +292,7 @@ export default class PricingModuleService<
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async update(
async updateMoneyAmounts(
data: PricingTypes.UpdateMoneyAmountDTO[],
@MedusaContext() sharedContext: Context = {}
) {
@@ -150,7 +310,7 @@ export default class PricingModuleService<
}
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
async delete(
async deleteMoneyAmounts(
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {

View File

@@ -1,5 +1,8 @@
{
"extends": "./tsconfig.json",
"include": ["src", "integration-tests"],
"exclude": ["node_modules", "dist"]
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"sourceMap": true
}
}

View File

@@ -1,2 +1,3 @@
export * from "./currency"
export * from "./money-amount"
export * from "./price-set"

View File

@@ -0,0 +1,32 @@
import { BaseFilterable } from "../../dal"
import { FilterableMoneyAmountProps, MoneyAmountDTO } from "./money-amount"
export interface PricingContext {
currency_code?: string
}
export interface PriceSetDTO {
id: string
money_amounts?: MoneyAmountDTO[]
}
export interface CalculatedPriceSetDTO {
id: string
amount: number | null
currency_code: string | null
min_quantity: number | null
max_quantity: number | null
}
export interface CreatePriceSetDTO {
money_amounts?: MoneyAmountDTO[]
}
export interface UpdatePriceSetDTO {
id: string
}
export interface FilterablePriceSetProps
extends BaseFilterable<FilterablePriceSetProps> {
id?: string[]
money_amounts?: FilterableMoneyAmountProps
}

View File

@@ -2,48 +2,90 @@ import { FindConfig } from "../common"
import { ModuleJoinerConfig } from "../modules-sdk"
import { Context } from "../shared-context"
import {
CalculatedPriceSetDTO,
CreateCurrencyDTO,
CreateMoneyAmountDTO,
CreatePriceSetDTO,
CurrencyDTO,
FilterableCurrencyProps,
FilterableMoneyAmountProps,
FilterablePriceSetProps,
MoneyAmountDTO,
PriceSetDTO,
PricingContext,
UpdateCurrencyDTO,
UpdateMoneyAmountDTO,
UpdatePriceSetDTO,
} from "./common"
export interface IPricingModuleService {
__joinerConfig(): ModuleJoinerConfig
calculatePrices(
priceSetIds: string[],
pricingContext: PricingContext,
sharedContext?: Context
): Promise<CalculatedPriceSetDTO>
retrieve(
id: string,
id: string,
config?: FindConfig<PriceSetDTO>,
sharedContext?: Context
): Promise<PriceSetDTO>
list(
filters?: FilterablePriceSetProps,
config?: FindConfig<PriceSetDTO>,
sharedContext?: Context
): Promise<PriceSetDTO[]>
listAndCount(
filters?: FilterablePriceSetProps,
config?: FindConfig<PriceSetDTO>,
sharedContext?: Context
): Promise<[PriceSetDTO[], number]>
create(
data: CreatePriceSetDTO[],
sharedContext?: Context
): Promise<PriceSetDTO[]>
update(
data: UpdatePriceSetDTO[],
sharedContext?: Context
): Promise<PriceSetDTO[]>
delete(ids: string[], sharedContext?: Context): Promise<void>
retrieveMoneyAmount(
id: string,
config?: FindConfig<MoneyAmountDTO>,
sharedContext?: Context
): Promise<MoneyAmountDTO>
list(
listMoneyAmounts(
filters?: FilterableMoneyAmountProps,
config?: FindConfig<MoneyAmountDTO>,
sharedContext?: Context
): Promise<MoneyAmountDTO[]>
listAndCount(
listAndCountMoneyAmounts(
filters?: FilterableMoneyAmountProps,
config?: FindConfig<MoneyAmountDTO>,
sharedContext?: Context
): Promise<[MoneyAmountDTO[], number]>
create(
createMoneyAmounts(
data: CreateMoneyAmountDTO[],
sharedContext?: Context
): Promise<MoneyAmountDTO[]>
update(
updateMoneyAmounts(
data: UpdateMoneyAmountDTO[],
sharedContext?: Context
): Promise<MoneyAmountDTO[]>
delete(ids: string[], sharedContext?: Context): Promise<void>
deleteMoneyAmounts(ids: string[], sharedContext?: Context): Promise<void>
retrieveCurrency(
code: string,