diff --git a/.changeset/witty-hats-applaud.md b/.changeset/witty-hats-applaud.md new file mode 100644 index 0000000000..33d1e2353e --- /dev/null +++ b/.changeset/witty-hats-applaud.md @@ -0,0 +1,6 @@ +--- +"@medusajs/pricing": patch +"@medusajs/types": patch +--- + +feat(pricing,types): addPrice and removePricremoveRules APIs diff --git a/packages/pricing/integration-tests/__fixtures__/price-set/index.ts b/packages/pricing/integration-tests/__fixtures__/price-set/index.ts index 5ffdff4a8d..1dfbce1abf 100644 --- a/packages/pricing/integration-tests/__fixtures__/price-set/index.ts +++ b/packages/pricing/integration-tests/__fixtures__/price-set/index.ts @@ -13,27 +13,24 @@ export async function createPriceSets( for (let priceSetData of priceSetsData) { const priceSetDataClone = { ...priceSetData } - const moneyAmountsData = priceSetDataClone.money_amounts || [] - delete priceSetDataClone.money_amounts + const moneyAmountsData = priceSetDataClone.prices || [] + delete priceSetDataClone.prices let priceSet = manager.create(PriceSet, priceSetDataClone) as PriceSet - await manager.persist(priceSet).flush() + manager.persist(priceSet) 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, + price_set: priceSet, money_amount: moneyAmount.id, title: "test", }) - manager.persist(psma).flush() + manager.persist(psma) } + + await manager.flush() } return priceSets diff --git a/packages/pricing/integration-tests/__fixtures__/seed-price-data.ts b/packages/pricing/integration-tests/__fixtures__/seed-price-data.ts new file mode 100644 index 0000000000..bd7f1fb585 --- /dev/null +++ b/packages/pricing/integration-tests/__fixtures__/seed-price-data.ts @@ -0,0 +1,41 @@ +import { SqlEntityManager } from "@mikro-orm/postgresql" + +import { createCurrencies, defaultCurrencyData } from "./currency" +import { createMoneyAmounts, defaultMoneyAmountsData } from "./money-amount" +import { createPriceRules, defaultPriceRuleData } from "./price-rule" +import { createPriceSets, defaultPriceSetsData } from "./price-set" +import { + createPriceSetMoneyAmounts, + defaultPriceSetMoneyAmountsData, +} from "./price-set-money-amount" +import { + createPriceSetMoneyAmountRules, + defaultPriceSetMoneyAmountRulesData, +} from "./price-set-money-amount-rules" +import { createRuleTypes, defaultRuleTypesData } from "./rule-type" + +jest.setTimeout(30000) + +export async function seedPriceData( + testManager: SqlEntityManager, + { + moneyAmountsData = defaultMoneyAmountsData, + priceSetsData = defaultPriceSetsData, + currencyData = defaultCurrencyData, + priceRuleData = defaultPriceRuleData, + priceSetMoneyAmountsData = defaultPriceSetMoneyAmountsData, + priceSetMoneyAmountRulesData = defaultPriceSetMoneyAmountRulesData, + ruleTypesData = defaultRuleTypesData, + } = {} +) { + await createCurrencies(testManager, currencyData) + await createMoneyAmounts(testManager, moneyAmountsData) + await createPriceSets(testManager, priceSetsData) + await createPriceSetMoneyAmounts(testManager, priceSetMoneyAmountsData) + await createRuleTypes(testManager, ruleTypesData) + await createPriceRules(testManager, priceRuleData) + await createPriceSetMoneyAmountRules( + testManager, + priceSetMoneyAmountRulesData + ) +} diff --git a/packages/pricing/integration-tests/__tests__/services/price-rule/index.spec.ts b/packages/pricing/integration-tests/__tests__/services/price-rule/index.spec.ts index f350895ca1..b67e4ac247 100644 --- a/packages/pricing/integration-tests/__tests__/services/price-rule/index.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/price-rule/index.spec.ts @@ -9,6 +9,7 @@ import { createMoneyAmounts } from "../../../__fixtures__/money-amount" import { createPriceRules } from "../../../__fixtures__/price-rule" import { createPriceSets } from "../../../__fixtures__/price-set" import { createPriceSetMoneyAmounts } from "../../../__fixtures__/price-set-money-amount" +import { createPriceSetMoneyAmountRules } from "../../../__fixtures__/price-set-money-amount-rules" import { createRuleTypes } from "../../../__fixtures__/rule-type" import { MikroOrmWrapper } from "../../../utils" @@ -31,12 +32,12 @@ describe("PriceRule Service", () => { service = new PriceRuleService({ priceRuleRepository: priceRuleRepository, }) - await createCurrencies(testManager) await createMoneyAmounts(testManager) - await createRuleTypes(testManager) await createPriceSets(testManager) + await createRuleTypes(testManager) await createPriceSetMoneyAmounts(testManager) + await createPriceSetMoneyAmountRules(testManager) await createPriceRules(testManager) }) diff --git a/packages/pricing/integration-tests/__tests__/services/price-set-money-amonut-rules/index.ts b/packages/pricing/integration-tests/__tests__/services/price-set-money-amonut-rules/index.spec.ts similarity index 91% rename from packages/pricing/integration-tests/__tests__/services/price-set-money-amonut-rules/index.ts rename to packages/pricing/integration-tests/__tests__/services/price-set-money-amonut-rules/index.spec.ts index c4ee8c78ae..246de82346 100644 --- a/packages/pricing/integration-tests/__tests__/services/price-set-money-amonut-rules/index.ts +++ b/packages/pricing/integration-tests/__tests__/services/price-set-money-amonut-rules/index.spec.ts @@ -3,12 +3,7 @@ import { SqlEntityManager } from "@mikro-orm/postgresql" import { PriceSetMoneyAmountRulesRepository } from "@repositories" import { PriceSetMoneyAmountRulesService } from "@services" -import { createCurrencies } from "../../../__fixtures__/currency" -import { createMoneyAmounts } from "../../../__fixtures__/money-amount" -import { createPriceSets } from "../../../__fixtures__/price-set" -import { createPriceSetMoneyAmounts } from "../../../__fixtures__/price-set-money-amount" -import { createPriceSetMoneyAmountRules } from "../../../__fixtures__/price-set-money-amount-rules" -import { createRuleTypes } from "../../../__fixtures__/rule-type" +import { seedPriceData } from "../../../__fixtures__/seed-price-data" import { MikroOrmWrapper } from "../../../utils" jest.setTimeout(30000) @@ -33,12 +28,7 @@ describe("PriceSetMoneyAmountRules Service", () => { testManager = await MikroOrmWrapper.forkManager() - await createCurrencies(testManager) - await createMoneyAmounts(testManager) - await createPriceSets(testManager) - await createRuleTypes(testManager) - await createPriceSetMoneyAmounts(testManager) - await createPriceSetMoneyAmountRules(testManager) + await seedPriceData(testManager) }) afterEach(async () => { diff --git a/packages/pricing/integration-tests/__tests__/services/price-set/index.spec.ts b/packages/pricing/integration-tests/__tests__/services/price-set/index.spec.ts index bb6cf1a222..eea3953ad3 100644 --- a/packages/pricing/integration-tests/__tests__/services/price-set/index.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/price-set/index.spec.ts @@ -31,15 +31,22 @@ describe("PriceSet Service", () => { const priceSetInputData = [ { id: "price-set-1", - money_amounts: [{ id: "money-amount-USD" }], + prices: [ + { + id: "money-amount-USD", + currency_code: "EUR", + amount: 100, + rules: {}, + }, + ], }, { id: "price-set-2", - money_amounts: [], + prices: [], }, { id: "price-set-3", - money_amounts: [], + prices: [], }, ] diff --git a/packages/pricing/integration-tests/__tests__/services/pricing-module/calculate-price.spec.ts b/packages/pricing/integration-tests/__tests__/services/pricing-module/calculate-price.spec.ts index 75a3b0aea3..3efba8ad60 100644 --- a/packages/pricing/integration-tests/__tests__/services/pricing-module/calculate-price.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/pricing-module/calculate-price.spec.ts @@ -7,53 +7,13 @@ import { SqlEntityManager } from "@mikro-orm/postgresql" import { PriceSet } from "@models" import { initialize } from "../../../../src" -import { - createCurrencies, - defaultCurrencyData, -} from "../../../__fixtures__/currency" -import { - createMoneyAmounts, - defaultMoneyAmountsData, -} from "../../../__fixtures__/money-amount" -import { - createPriceRules, - defaultPriceRuleData, -} from "../../../__fixtures__/price-rule" -import { - createPriceSets, - defaultPriceSetsData, -} from "../../../__fixtures__/price-set" -import { - createPriceSetMoneyAmounts, - defaultPriceSetMoneyAmountsData, -} from "../../../__fixtures__/price-set-money-amount" -import { - createRuleTypes, - defaultRuleTypesData, -} from "../../../__fixtures__/rule-type" import { DB_URL, MikroOrmWrapper } from "../../../utils" +import { seedPriceData } from "../../../__fixtures__/seed-price-data" + jest.setTimeout(30000) -async function seedData({ - moneyAmountsData = defaultMoneyAmountsData, - priceSetsData = defaultPriceSetsData, - currencyData = defaultCurrencyData, - priceRuleData = defaultPriceRuleData, - priceSetMoneyAmountsData = defaultPriceSetMoneyAmountsData, - ruleTypesData = defaultRuleTypesData, -} = {}) { - const testManager = MikroOrmWrapper.forkManager() - - await createCurrencies(testManager, currencyData) - await createMoneyAmounts(testManager, moneyAmountsData) - await createPriceSets(testManager, priceSetsData) - await createPriceSetMoneyAmounts(testManager, priceSetMoneyAmountsData) - await createRuleTypes(testManager, ruleTypesData) - await createPriceRules(testManager, priceRuleData) -} - describe("PricingModule Service - Calculate Price", () => { let service: IPricingModuleService let testManager: SqlEntityManager @@ -95,14 +55,21 @@ describe("PricingModule Service - Calculate Price", () => { const moneyAmountsData = [ { - id: "money-amount-currency_code-EUR", + id: "money-amount-PLN", + currency_code: "PLN", + amount: 1000, + min_quantity: 1, + max_quantity: 10, + }, + { + id: "money-amount-company_id-EUR", currency_code: "EUR", amount: 500, min_quantity: 1, max_quantity: 10, }, { - id: "money-amount-currency_code-PLN", + id: "money-amount-company_id-PLN", currency_code: "PLN", amount: 400, min_quantity: 1, @@ -115,6 +82,20 @@ describe("PricingModule Service - Calculate Price", () => { min_quantity: 1, max_quantity: 4, }, + { + id: "money-amount-region_id+company_id-PLN", + currency_code: "PLN", + amount: 999, + min_quantity: 1, + max_quantity: 10, + }, + { + id: "money-amount-region_id-PLN-5-qty", + currency_code: "PLN", + amount: 250, + min_quantity: 4, + max_quantity: 10, + }, { id: "money-amount-region_id-PL-EUR", currency_code: "EUR", @@ -149,17 +130,24 @@ describe("PricingModule Service - Calculate Price", () => { const priceSetMoneyAmountsData = [ { - id: "psma-currency_code-EUR", - title: "psma EUR - currency_code", + id: "psma-PLN", + title: "psma PLN", + price_set: "price-set-PLN", + money_amount: "money-amount-PLN", + number_rules: 0, + }, + { + id: "psma-company_id-EUR", + title: "psma EUR - company_id", price_set: "price-set-EUR", - money_amount: "money-amount-currency_code-EUR", + money_amount: "money-amount-company_id-EUR", number_rules: 1, }, { - id: "psma-currency_code-PLN", - title: "psma PLN - currency_code", + id: "psma-company_id-PLN", + title: "psma PLN - company_id", price_set: "price-set-PLN", - money_amount: "money-amount-currency_code-PLN", + money_amount: "money-amount-company_id-PLN", number_rules: 1, }, { @@ -170,21 +158,35 @@ describe("PricingModule Service - Calculate Price", () => { number_rules: 1, }, { - id: "psma-region_id_currency_code-PL-EUR", + id: "psma-region_id+company_id-PLN", + title: "psma region_id + company_id", + price_set: "price-set-PLN", + money_amount: "money-amount-region_id+company_id-PLN", + number_rules: 2, + }, + { + id: "psma-region_id-PLN-5-qty", + title: "psma PLN - region_id 5 qty", + price_set: "price-set-PLN", + money_amount: "money-amount-region_id-PLN-5-qty", + number_rules: 1, + }, + { + id: "psma-region_id_company_id-PL-EUR", title: "psma PLN - region_id PL with EUR currency", price_set: "price-set-PLN", money_amount: "money-amount-region_id-PL-EUR", number_rules: 2, }, { - id: "psma-region_id_currency_code-PL-EUR-4-qty", + id: "psma-region_id_company_id-PL-EUR-4-qty", title: "psma PLN - region_id PL with EUR currency for quantity 4", price_set: "price-set-PLN", money_amount: "money-amount-region_id-PL-EUR-4-qty", number_rules: 2, }, { - id: "psma-region_id_currency_code-PL-EUR-customer-group", + id: "psma-region_id_company_id-PL-EUR-customer-group", title: "psma PLN - region_id PL with EUR currency for customer group", price_set: "price-set-PLN", money_amount: "money-amount-region_id-PL-EUR-customer-group", @@ -194,9 +196,9 @@ describe("PricingModule Service - Calculate Price", () => { const ruleTypesData = [ { - id: "rule-type-currency_code", - name: "rule type currency code", - rule_attribute: "currency_code", + id: "rule-type-company_id", + name: "rule type company id", + rule_attribute: "company_id", default_priority: 2, }, { @@ -215,20 +217,20 @@ describe("PricingModule Service - Calculate Price", () => { const priceRuleData = [ { - id: "price-rule-currency_code-EUR", + id: "price-rule-company_id-EUR", price_set_id: "price-set-EUR", - rule_type_id: "rule-type-currency_code", + rule_type_id: "rule-type-company_id", value: "EUR", price_list_id: "test", - price_set_money_amount_id: "psma-currency_code-EUR", + price_set_money_amount_id: "psma-company_id-EUR", }, { - id: "price-rule-currency_code-PLN", + id: "price-rule-company_id-PLN", price_set_id: "price-set-PLN", - rule_type_id: "rule-type-currency_code", - value: "PLN", + rule_type_id: "rule-type-company_id", + value: "medusa-company-id", price_list_id: "test", - price_set_money_amount_id: "psma-currency_code-PLN", + price_set_money_amount_id: "psma-company_id-PLN", }, { id: "price-rule-region_id-PLN", @@ -239,38 +241,60 @@ describe("PricingModule Service - Calculate Price", () => { price_set_money_amount_id: "psma-region_id-PLN", }, { - id: "price-rule-region_id-currency_code-PL", + id: "price-rule-region_id+company_id-PL", price_set_id: "price-set-PLN", rule_type_id: "rule-type-region_id", value: "PL", price_list_id: "test", - price_set_money_amount_id: "psma-region_id_currency_code-PL-EUR", + price_set_money_amount_id: "psma-region_id+company_id-PLN", }, { - id: "price-rule-region_id-currency_code-PLN", + id: "price-rule-region_id+company_id-medusa-company-id", price_set_id: "price-set-PLN", - rule_type_id: "rule-type-currency_code", - value: "EUR", + rule_type_id: "rule-type-company_id", + value: "medusa-company-id", price_list_id: "test", - price_set_money_amount_id: "psma-region_id_currency_code-PL-EUR", + price_set_money_amount_id: "psma-region_id+company_id-PLN", }, { - id: "price-rule-region_id-currency_code-PL-4-qty", + id: "price-rule-region_id-PLN-5-qty", price_set_id: "price-set-PLN", rule_type_id: "rule-type-region_id", value: "PL", price_list_id: "test", - price_set_money_amount_id: - "psma-region_id_currency_code-PL-EUR-4-qty", + price_set_money_amount_id: "psma-region_id-PLN-5-qty", }, { - id: "price-rule-region_id-currency_code-PLN-4-qty", + id: "price-rule-region_id-company_id-PL", price_set_id: "price-set-PLN", - rule_type_id: "rule-type-currency_code", - value: "EUR", + rule_type_id: "rule-type-region_id", + value: "PL", price_list_id: "test", - price_set_money_amount_id: - "psma-region_id_currency_code-PL-EUR-4-qty", + price_set_money_amount_id: "psma-region_id_company_id-PL-EUR", + }, + { + id: "price-rule-region_id-company_id-PLN", + price_set_id: "price-set-PLN", + rule_type_id: "rule-type-company_id", + value: "medusa-company-id", + price_list_id: "test", + price_set_money_amount_id: "psma-region_id_company_id-PL-EUR", + }, + { + id: "price-rule-region_id-company_id-PL-4-qty", + price_set_id: "price-set-PLN", + rule_type_id: "rule-type-region_id", + value: "PL", + price_list_id: "test", + price_set_money_amount_id: "psma-region_id_company_id-PL-EUR-4-qty", + }, + { + id: "price-rule-region_id-company_id-PLN-4-qty", + price_set_id: "price-set-PLN", + rule_type_id: "rule-type-company_id", + value: "medusa-company-id", + price_list_id: "test", + price_set_money_amount_id: "psma-region_id_company_id-PL-EUR-4-qty", }, { id: "price-rule-region_id-currency_customer_group_code-PL", @@ -279,16 +303,16 @@ describe("PricingModule Service - Calculate Price", () => { value: "PL", price_list_id: "test", price_set_money_amount_id: - "psma-region_id_currency_code-PL-EUR-customer-group", + "psma-region_id_company_id-PL-EUR-customer-group", }, { id: "price-rule-region_id-currency_customer_group_code-PLN", price_set_id: "price-set-PLN", - rule_type_id: "rule-type-currency_code", - value: "EUR", + rule_type_id: "rule-type-company_id", + value: "medusa-company-id", price_list_id: "test", price_set_money_amount_id: - "psma-region_id_currency_code-PL-EUR-customer-group", + "psma-region_id_company_id-PL-EUR-customer-group", }, { id: "price-rule-region_id-currency_customer_group_code-test_customer_group", @@ -297,67 +321,64 @@ describe("PricingModule Service - Calculate Price", () => { value: "test-customer-group", price_list_id: "test", price_set_money_amount_id: - "psma-region_id_currency_code-PL-EUR-customer-group", + "psma-region_id_company_id-PL-EUR-customer-group", }, ] as unknown as CreatePriceRuleDTO[] - await seedData({ + await seedPriceData(MikroOrmWrapper.forkManager(), { currencyData, moneyAmountsData, priceSetsData, priceSetMoneyAmountsData, + priceSetMoneyAmountRulesData: [], priceRuleData, ruleTypesData, }) }) - it("should return null values when no context is set", async () => { - const priceSetsResult = await service.calculatePrices( + it("should throw an error when currency code is not set", async () => { + let result = service.calculatePrices( { id: ["price-set-EUR", "price-set-PLN"] }, {} ) - expect(priceSetsResult).toEqual([ - { - id: "price-set-EUR", - amount: null, - currency_code: null, - min_quantity: null, - max_quantity: null, - }, - { - id: "price-set-PLN", - amount: null, - currency_code: null, - min_quantity: null, - max_quantity: null, - }, - ]) + expect(result).rejects.toThrow( + "currency_code is a required input in the pricing context" + ) + + result = service.calculatePrices( + { id: ["price-set-EUR", "price-set-PLN"] }, + { context: { region_id: "DE" } } + ) + + expect(result).rejects.toThrow( + "currency_code is a required input in the pricing context" + ) }) - it("should return filled prices when 1 context is present and price is setup for EUR", async () => { + it("should return filled prices when 1 context is present and price is setup for PLN", async () => { const priceSetsResult = await service.calculatePrices( { id: ["price-set-EUR", "price-set-PLN"] }, { - context: { currency_code: "EUR" }, + context: { currency_code: "PLN" }, } ) expect(priceSetsResult).toEqual([ { id: "price-set-EUR", - amount: "500", - currency_code: "EUR", - min_quantity: "1", - max_quantity: "10", - }, - { - id: "price-set-PLN", amount: null, currency_code: null, min_quantity: null, max_quantity: null, }, + { + id: "price-set-PLN", + amount: "1000", + currency_code: "PLN", + min_quantity: "1", + max_quantity: "10", + }, ]) }) @@ -365,7 +386,7 @@ describe("PricingModule Service - Calculate Price", () => { const priceSetsResult = await service.calculatePrices( { id: ["price-set-EUR", "price-set-PLN"] }, { - context: { region_id: "PL" }, + context: { currency_code: "PLN", region_id: "PL" }, } ) @@ -405,10 +426,10 @@ describe("PricingModule Service - Calculate Price", () => { }, { id: "price-set-PLN", - amount: "400", + amount: "1000", currency_code: "PLN", min_quantity: "1", - max_quantity: "5", + max_quantity: "10", }, ]) }) @@ -417,7 +438,7 @@ describe("PricingModule Service - Calculate Price", () => { const priceSetsResult = await service.calculatePrices( { id: ["price-set-EUR", "price-set-PLN"] }, { - context: { does_not_exist: "EUR" }, + context: { currency_code: "EUR", does_not_exist: "EUR" }, } ) @@ -443,24 +464,50 @@ describe("PricingModule Service - Calculate Price", () => { const priceSetsResult = await service.calculatePrices( { id: ["price-set-EUR", "price-set-PLN"] }, { - context: { currency_code: "EUR", region_id: "PL" }, + context: { currency_code: "PLN", region_id: "PL" }, } ) expect(priceSetsResult).toEqual([ { id: "price-set-EUR", - amount: "500", - currency_code: "EUR", - min_quantity: "1", - max_quantity: "10", + amount: null, + currency_code: null, + min_quantity: null, + max_quantity: null, }, { id: "price-set-PLN", - amount: "200", - currency_code: "EUR", + amount: "300", + currency_code: "PLN", min_quantity: "1", - max_quantity: "3", + max_quantity: "4", + }, + ]) + }) + + it("should return filled prices when 2 contexts are present and price is not setup", async () => { + const priceSetsResult = await service.calculatePrices( + { id: ["price-set-EUR", "price-set-PLN"] }, + { + context: { currency_code: "PLN", company_id: "doesnotexist" }, + } + ) + + expect(priceSetsResult).toEqual([ + { + id: "price-set-EUR", + amount: null, + currency_code: null, + min_quantity: null, + max_quantity: null, + }, + { + id: "price-set-PLN", + amount: "1000", + currency_code: "PLN", + min_quantity: "1", + max_quantity: "10", }, ]) }) @@ -469,15 +516,15 @@ describe("PricingModule Service - Calculate Price", () => { const priceSetsResult = await service.calculatePrices( { id: ["price-set-PLN"] }, { - context: { currency_code: "EUR", region_id: "PL", quantity: 5 }, + context: { currency_code: "PLN", region_id: "PL", quantity: 5 }, } ) expect(priceSetsResult).toEqual([ { id: "price-set-PLN", - amount: "50", - currency_code: "EUR", + amount: "250", + currency_code: "PLN", min_quantity: "4", max_quantity: "10", }, @@ -489,9 +536,10 @@ describe("PricingModule Service - Calculate Price", () => { { id: ["price-set-EUR", "price-set-PLN"] }, { context: { - currency_code: "EUR", + currency_code: "PLN", region_id: "PL", - customer_group_id: "test", + customer_group_id: "test-customer-group", + company_id: "does-not-exist", }, } ) @@ -499,17 +547,17 @@ describe("PricingModule Service - Calculate Price", () => { expect(priceSetsResult).toEqual([ { id: "price-set-EUR", - amount: "500", - currency_code: "EUR", - min_quantity: "1", - max_quantity: "10", + amount: null, + currency_code: null, + min_quantity: null, + max_quantity: null, }, { id: "price-set-PLN", - amount: "200", - currency_code: "EUR", + amount: "300", + currency_code: "PLN", min_quantity: "1", - max_quantity: "3", + max_quantity: "4", }, ]) }) @@ -522,6 +570,7 @@ describe("PricingModule Service - Calculate Price", () => { currency_code: "EUR", region_id: "PL", customer_group_id: "test-customer-group", + company_id: "medusa-company-id", }, } ) @@ -529,10 +578,10 @@ describe("PricingModule Service - Calculate Price", () => { expect(priceSetsResult).toEqual([ { id: "price-set-EUR", - amount: "500", - currency_code: "EUR", - min_quantity: "1", - max_quantity: "10", + amount: null, + currency_code: null, + min_quantity: null, + max_quantity: null, }, // Currency Code + Region value + customer group id { @@ -545,45 +594,15 @@ describe("PricingModule Service - Calculate Price", () => { ]) }) - it("should return filled prices when 3 contexts are present and price is setup for 3, but value is incorrect for 1. It falls back to 2 rule context", async () => { - const priceSetsResult = await service.calculatePrices( - { id: ["price-set-EUR", "price-set-PLN"] }, - { - context: { - currency_code: "EUR", - region_id: "PL", - customer_group_id: "does-not-exist", - }, - } - ) - - expect(priceSetsResult).toEqual([ - { - id: "price-set-EUR", - amount: "500", - currency_code: "EUR", - min_quantity: "1", - max_quantity: "10", - }, - // Currency Code + Region value - { - id: "price-set-PLN", - amount: "200", - currency_code: "EUR", - min_quantity: "1", - max_quantity: "3", - }, - ]) - }) - it("should return filled prices when 3 contexts are present and price is setup for 3, but value is incorrect for 2. It falls back to 1 rule context when 1 rule is not setup", async () => { const priceSetsResult = await service.calculatePrices( { id: ["price-set-EUR", "price-set-PLN"] }, { context: { - currency_code: "does-not-exist", + currency_code: "PLN", region_id: "PL", customer_group_id: "does-not-exist", + company_id: "does-not-exist", }, } ) @@ -607,14 +626,15 @@ describe("PricingModule Service - Calculate Price", () => { ]) }) - it("should return filled prices when 3 contexts are present and price is setup for 3, but value is incorrect for 2. It falls back to 1 rule context when 1 rule is setup", async () => { + it("should return filled prices when 3 contexts are present and price is setup for 3, but value is incorrect for 2. It falls back to default value", async () => { const priceSetsResult = await service.calculatePrices( { id: ["price-set-EUR", "price-set-PLN"] }, { context: { - currency_code: "EUR", + currency_code: "PLN", region_id: "does-not-exist", customer_group_id: "does-not-exist", + company_id: "does-not-exist", }, } ) @@ -622,19 +642,19 @@ describe("PricingModule Service - Calculate Price", () => { expect(priceSetsResult).toEqual([ { id: "price-set-EUR", - amount: "500", - currency_code: "EUR", - min_quantity: "1", - max_quantity: "10", - }, - // PLN price set is not setup for EUR currency_code so it will default to a null set - { - id: "price-set-PLN", amount: null, currency_code: null, min_quantity: null, max_quantity: null, }, + // PLN price set is not setup for EUR currency_code so it will default to a null set + { + id: "price-set-PLN", + amount: "1000", + currency_code: "PLN", + min_quantity: "1", + max_quantity: "10", + }, ]) }) @@ -642,7 +662,7 @@ describe("PricingModule Service - Calculate Price", () => { const priceSetsResult = await service.calculatePrices( { id: ["price-set-EUR", "price-set-PLN"] }, { - context: { does_not_exist: "EUR", does_not_exist_2: "Berlin" }, + context: { currency_code: "EUR", does_not_exist_2: "Berlin" }, } ) @@ -668,25 +688,25 @@ describe("PricingModule Service - Calculate Price", () => { const priceSetsResult = await service.calculatePrices( { id: ["price-set-EUR", "price-set-PLN"] }, { - context: { currency_code: "EUR", does_not_exist: "Berlin" }, + context: { currency_code: "PLN", region_id: "PL", city: "Berlin" }, } ) expect(priceSetsResult).toEqual([ { id: "price-set-EUR", - amount: "500", - currency_code: "EUR", - min_quantity: "1", - max_quantity: "10", - }, - { - id: "price-set-PLN", amount: null, currency_code: null, min_quantity: null, max_quantity: null, }, + { + id: "price-set-PLN", + amount: "300", + currency_code: "PLN", + min_quantity: "1", + max_quantity: "4", + }, ]) }) }) diff --git a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-rule.spec.ts b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-rule.spec.ts index 9e781e2333..09c9216d75 100644 --- a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-rule.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-rule.spec.ts @@ -7,6 +7,7 @@ import { createMoneyAmounts } from "../../../__fixtures__/money-amount" import { createPriceRules } from "../../../__fixtures__/price-rule" import { createPriceSets } from "../../../__fixtures__/price-set" import { createPriceSetMoneyAmounts } from "../../../__fixtures__/price-set-money-amount" +import { createPriceSetMoneyAmountRules } from "../../../__fixtures__/price-set-money-amount-rules" import { createRuleTypes } from "../../../__fixtures__/rule-type" import { DB_URL, MikroOrmWrapper } from "../../../utils" @@ -29,9 +30,10 @@ describe("PricingModule Service - PriceRule", () => { await createCurrencies(testManager) await createMoneyAmounts(testManager) - await createRuleTypes(testManager) await createPriceSets(testManager) + await createRuleTypes(testManager) await createPriceSetMoneyAmounts(testManager) + await createPriceSetMoneyAmountRules(testManager) await createPriceRules(testManager) }) diff --git a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set-money-amount-rules.spec.ts b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set-money-amount-rules.spec.ts index f8aeafed18..ac428f6e61 100644 --- a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set-money-amount-rules.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set-money-amount-rules.spec.ts @@ -256,7 +256,7 @@ describe("PricingModule Service - PriceSetMoneyAmountRules", () => { describe("createPriceSetMoneyAmountRules", () => { it("should create a priceSetMoneyAmountRules successfully", async () => { - const created = await service.createPriceSetMoneyAmountRules([ + await service.createPriceSetMoneyAmountRules([ { price_set_money_amount: "price-set-money-amount-EUR", rule_type: "rule-type-2", @@ -264,19 +264,20 @@ describe("PricingModule Service - PriceSetMoneyAmountRules", () => { }, ]) - const [priceSetMoneyAmountRules] = + const priceSetMoneyAmountRules = await service.listPriceSetMoneyAmountRules( - { - id: [created[0]?.id], - }, + {}, { relations: ["price_set_money_amount", "rule_type"], } ) - expect(priceSetMoneyAmountRules).toEqual( + const created = + priceSetMoneyAmountRules[priceSetMoneyAmountRules.length - 1] + + expect(created).toEqual( expect.objectContaining({ - id: created[0]?.id, + id: expect.any(String), value: "New priceSetMoneyAmountRule", price_set_money_amount: expect.objectContaining({ id: "price-set-money-amount-EUR", diff --git a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts index 808e062079..b13f703219 100644 --- a/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts +++ b/packages/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts @@ -1,45 +1,34 @@ -import { CreatePriceSetDTO, IPricingModuleService } from "@medusajs/types" +import { + CreatePriceSetDTO, + CreatePriceSetRuleTypeDTO, + 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, - defaultMoneyAmountsData, -} from "../../../__fixtures__/money-amount" -import { - createPriceRules, - defaultPriceRuleData, -} from "../../../__fixtures__/price-rule" -import { createPriceSets } from "../../../__fixtures__/price-set" -import { - createPriceSetMoneyAmounts, - defaultPriceSetMoneyAmountsData, -} from "../../../__fixtures__/price-set-money-amount" -import { createRuleTypes } from "../../../__fixtures__/rule-type" +import { PriceSetRuleType, initialize } from "../../../../src" +import { seedPriceData } from "../../../__fixtures__/seed-price-data" import { DB_URL, MikroOrmWrapper } from "../../../utils" jest.setTimeout(30000) -async function seedData({ - moneyAmountsData = defaultMoneyAmountsData, - priceRuleData = defaultPriceRuleData, - priceSetMoneyAmountsData = defaultPriceSetMoneyAmountsData, -} = {}) { - const testManager = MikroOrmWrapper.forkManager() +async function createPriceSetPriceRules( + manager: SqlEntityManager, + priceSetRulesData: CreatePriceSetRuleTypeDTO[] +): Promise { + const priceSetRules: PriceSetRuleType[] = [] - await createCurrencies(testManager) - await createMoneyAmounts(testManager, moneyAmountsData) - await createPriceSets(testManager) - await createPriceSetMoneyAmounts(testManager, priceSetMoneyAmountsData) - await createRuleTypes(testManager) - await createPriceRules(testManager, priceRuleData) + for (let priceSetRuleData of priceSetRulesData) { + const priceRule = manager.create(PriceSetRuleType, priceSetRuleData) + + priceSetRules.push(priceRule) + } + + await manager.persistAndFlush(priceSetRules) } describe("PricingModule Service - PriceSet", () => { let service: IPricingModuleService - let testManager: SqlEntityManager let repositoryManager: SqlEntityManager let data!: PriceSet[] @@ -52,9 +41,21 @@ describe("PricingModule Service - PriceSet", () => { schema: process.env.MEDUSA_PRICING_DB_SCHEMA, }, }) - }) - beforeEach(async () => await seedData()) + const testManager = MikroOrmWrapper.forkManager() + + await seedPriceData(testManager) + await createPriceSetPriceRules(testManager, [ + { + price_set: "price-set-1", + rule_type: "rule-type-1", + }, + { + price_set: "price-set-2", + rule_type: "rule-type-2", + }, + ] as unknown as CreatePriceSetRuleTypeDTO[]) + }) afterEach(async () => { await MikroOrmWrapper.clearDatabase() @@ -287,20 +288,179 @@ describe("PricingModule Service - PriceSet", () => { }) describe("create", () => { - it("should throw an error when a id does not exist", async () => { + it("should throw an error when creating a price set with rule attributes that don't exist", async () => { let error try { - await service.update([ + await service.create([ { - random: "does-not-exist", - } as any, + rules: [{ rule_attribute: "does-not-exist" }], + }, ]) } catch (e) { error = e } - expect(error.message).toEqual('PriceSet with id "undefined" not found') + expect(error.message).toEqual( + "Rule types don't exist for: does-not-exist" + ) + }) + + it("should fail to create a price set with rule types and money amounts with rule types that don't exits", async () => { + let error + + try { + await service.create([ + { + rules: [{ rule_attribute: "region_id" }], + prices: [ + { + amount: 100, + currency_code: "USD", + rules: { + city: "Berlin", + }, + }, + ], + }, + ]) + } catch (e) { + error = e + } + expect(error.message).toEqual( + "Rule types don't exist for money amounts with rule attribute: city" + ) + }) + + it("should create a price set with rule types", async () => { + const [priceSet] = await service.create([ + { + rules: [{ rule_attribute: "region_id" }], + }, + ]) + + expect(priceSet).toEqual( + expect.objectContaining({ + rule_types: [ + expect.objectContaining({ + rule_attribute: "region_id", + }), + ], + }) + ) + }) + + it("should create a price set with rule types and money amounts", async () => { + const [priceSet] = await service.create([ + { + rules: [{ rule_attribute: "region_id" }], + prices: [ + { + amount: 100, + currency_code: "USD", + rules: { + region_id: "1", + }, + }, + ], + }, + ]) + + expect(priceSet).toEqual( + expect.objectContaining({ + rule_types: [ + expect.objectContaining({ + rule_attribute: "region_id", + }), + ], + money_amounts: [ + expect.objectContaining({ + amount: "100", + currency_code: "USD", + }), + ], + }) + ) + }) + + it("should create a price set with money amounts with and without rules", async () => { + const [priceSet] = await service.create([ + { + rules: [{ rule_attribute: "region_id" }], + prices: [ + { + amount: 100, + currency_code: "USD", + rules: { + region_id: "1", + }, + }, + { + amount: 150, + currency_code: "USD", + rules: {}, + }, + ], + }, + ]) + + expect(priceSet).toEqual( + expect.objectContaining({ + rule_types: [ + expect.objectContaining({ + rule_attribute: "region_id", + }), + ], + money_amounts: expect.arrayContaining([ + expect.objectContaining({ + amount: "100", + currency_code: "USD", + }), + expect.objectContaining({ + amount: "150", + currency_code: "USD", + }), + ]), + }) + ) + }) + + it("should create a price set with rule types and money amountss", async () => { + const [priceSet] = await service.create([ + { + rules: [{ rule_attribute: "region_id" }], + prices: [ + { + amount: 100, + currency_code: "USD", + rules: { + region_id: "10", + }, + }, + ], + }, + ]) + + expect(priceSet).toEqual( + expect.objectContaining({ + rule_types: [ + expect.objectContaining({ + rule_attribute: "region_id", + }), + ], + money_amounts: [ + expect.objectContaining({ + amount: "100", + currency_code: "USD", + }), + ], + price_rules: [ + expect.objectContaining({ + value: "10", + }), + ], + }) + ) }) it("should create a priceSet successfully", async () => { @@ -321,4 +481,290 @@ describe("PricingModule Service - PriceSet", () => { ) }) }) + + describe("removeRules", () => { + it("should delete prices for a price set associated to the rules that are deleted", async () => { + const createdPriceSet = await service.create([ + { + rules: [ + { + rule_attribute: "region_id", + }, + { + rule_attribute: "currency_code", + }, + ], + prices: [ + { + currency_code: "EUR", + amount: 100, + rules: { + region_id: "test-region", + currency_code: "test-currency", + }, + }, + { + currency_code: "EUR", + amount: 500, + rules: { + currency_code: "test-currency", + }, + }, + ], + }, + ]) + + await service.removeRules([ + { + id: createdPriceSet[0].id, + rules: ["region_id"], + }, + ]) + + let priceSet = await service.list( + { + id: [createdPriceSet[0].id], + }, + { + relations: ["rule_types", "money_amounts", "price_rules"], + } + ) + + expect( + expect.arrayContaining( + expect.objectContaining({ + id: priceSet[0].id, + price_rules: [ + { + id: expect.any(String), + rule_type: expect.objectContaining({ + rule_attribute: "currency_code", + }), + }, + ], + money_amounts: [ + expect.objectContaining({ + amount: "500", + currency_code: "EUR", + }), + ], + rule_types: [ + expect.objectContaining({ + rule_attribute: "currency_code", + }), + ], + }) + ) + ) + + await service.removeRules([ + { + id: createdPriceSet[0].id, + rules: ["currency_code"], + }, + ]) + + priceSet = await service.list( + { + id: [createdPriceSet[0].id], + }, + { + relations: ["rule_types", "money_amounts", "price_rules"], + } + ) + expect(priceSet).toEqual([ + { + id: expect.any(String), + price_rules: [], + money_amounts: [], + rule_types: [], + }, + ]) + }) + }) + + describe("addPrices", () => { + it("should add prices to existing price set", async () => { + await service.addPrices([ + { + priceSetId: "price-set-1", + prices: [ + { + amount: 100, + currency_code: "USD", + rules: { currency_code: "USD" }, + }, + ], + }, + ]) + + const [priceSet] = await service.list( + { id: ["price-set-1"] }, + { relations: ["money_amounts"] } + ) + + expect(priceSet).toEqual( + expect.objectContaining({ + id: "price-set-1", + money_amounts: expect.arrayContaining([ + expect.objectContaining({ + amount: "100", + currency_code: "USD", + }), + ]), + }) + ) + }) + + it("should add prices to multiple existing price set", async () => { + await service.addPrices([ + { + priceSetId: "price-set-1", + prices: [ + { + amount: 100, + currency_code: "USD", + rules: { currency_code: "USD" }, + }, + ], + }, + { + priceSetId: "price-set-2", + prices: [ + { + amount: 150, + currency_code: "EUR", + rules: { region_id: "region-2" }, + }, + ], + }, + ]) + + const priceSets = await service.list( + { id: ["price-set-1", "price-set-2"] }, + { relations: ["money_amounts"] } + ) + + expect(priceSets).toEqual([ + expect.objectContaining({ + id: "price-set-1", + money_amounts: expect.arrayContaining([ + expect.objectContaining({ + amount: "100", + currency_code: "USD", + }), + ]), + }), + expect.objectContaining({ + id: "price-set-2", + money_amounts: expect.arrayContaining([ + expect.objectContaining({ + amount: "150", + currency_code: "EUR", + }), + ]), + }), + ]) + }) + + it("should fail with an appropriate error when trying to add a price with rule that doesn't exist", async () => { + let error + try { + await service.addPrices({ + priceSetId: "price-set-1", + prices: [ + { + amount: 100, + currency_code: "USD", + rules: { city: "Paris" }, + }, + ], + }) + } catch (e) { + error = e + } + + expect(error.message).toEqual("Rule types don't exist for: city") + }) + }) + + describe("addRules", () => { + it("should add rules to existing price set", async () => { + await service.addRules([ + { + priceSetId: "price-set-1", + rules: [ + { + attribute: "region_id", + }, + ], + }, + ]) + + const [priceSet] = await service.list( + { id: ["price-set-1"] }, + { relations: ["rule_types"] } + ) + + expect(priceSet).toEqual( + expect.objectContaining({ + id: "price-set-1", + rule_types: expect.arrayContaining([ + expect.objectContaining({ + rule_attribute: "currency_code", + }), + expect.objectContaining({ + rule_attribute: "region_id", + }), + ]), + }) + ) + }) + + it("should fail to add rules to non-existent price sets", async () => { + let error + + try { + await service.addRules([ + { + priceSetId: "price-set-doesn't-exist", + rules: [ + { + attribute: "region_id", + }, + ], + }, + ]) + } catch (e) { + error = e + } + + expect(error.message).toEqual( + "PriceSets with ids: price-set-doesn't-exist was not found" + ) + }) + + it("should fail to add rules with non-existent attributes", async () => { + let error + + try { + await service.addRules([ + { + priceSetId: "price-set-1", + rules: [ + { + attribute: "city", + }, + ], + }, + ]) + } catch (e) { + error = e + } + + expect(error.message).toEqual( + "Rule types don't exist for attributes: city" + ) + }) + }) }) diff --git a/packages/pricing/src/loaders/container.ts b/packages/pricing/src/loaders/container.ts index 631b0e7d2a..8d7ba0dc72 100644 --- a/packages/pricing/src/loaders/container.ts +++ b/packages/pricing/src/loaders/container.ts @@ -26,6 +26,8 @@ export default async ({ defaultServices.PriceSetMoneyAmountRulesService ).singleton(), priceRuleService: asClass(defaultServices.PriceRuleService).singleton(), + priceSetRuleTypeService: asClass(defaultServices.PriceSetRuleTypeService).singleton(), + priceSetMoneyAmountService : asClass(defaultServices.PriceSetMoneyAmountService).singleton(), }) if (customRepositories) { @@ -60,5 +62,11 @@ function loadDefaultRepositories({ container }) { priceRuleRepository: asClass( defaultRepositories.PriceRuleRepository ).singleton(), + priceSetRuleTypeRepository: asClass( + defaultRepositories.PriceSetRuleTypeRepository + ).singleton(), + priceSetMoneyAmountRepository: asClass( + defaultRepositories.PriceSetMoneyAmountRepository + ).singleton(), }) } diff --git a/packages/pricing/src/migrations/.snapshot-medusa-pricing.json b/packages/pricing/src/migrations/.snapshot-medusa-pricing.json index cc9253a86e..1d5cad716c 100644 --- a/packages/pricing/src/migrations/.snapshot-medusa-pricing.json +++ b/packages/pricing/src/migrations/.snapshot-medusa-pricing.json @@ -279,6 +279,7 @@ "id" ], "referencedTableName": "public.money_amount", + "deleteRule": "cascade", "updateRule": "cascade" } } @@ -348,6 +349,96 @@ "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": false, + "mappedType": "text" + }, + "rule_type_id": { + "name": "rule_type_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + } + }, + "name": "price_set_rule_type", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "price_set_id" + ], + "composite": false, + "keyName": "IDX_price_set_rule_type_price_set_id", + "primary": false, + "unique": false + }, + { + "columnNames": [ + "rule_type_id" + ], + "composite": false, + "keyName": "IDX_price_set_rule_type_rule_type_id", + "primary": false, + "unique": false + }, + { + "keyName": "price_set_rule_type_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "price_set_rule_type_price_set_id_foreign": { + "constraintName": "price_set_rule_type_price_set_id_foreign", + "columnNames": [ + "price_set_id" + ], + "localTableName": "public.price_set_rule_type", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.price_set", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "price_set_rule_type_rule_type_id_foreign": { + "constraintName": "price_set_rule_type_rule_type_id_foreign", + "columnNames": [ + "rule_type_id" + ], + "localTableName": "public.price_set_rule_type", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.rule_type", + "updateRule": "cascade" + } + } + }, { "columns": { "id": { @@ -430,6 +521,7 @@ "id" ], "referencedTableName": "public.price_set_money_amount", + "deleteRule": "cascade", "updateRule": "cascade" }, "price_set_money_amount_rules_rule_type_id_foreign": { @@ -600,6 +692,7 @@ "id" ], "referencedTableName": "public.price_set_money_amount", + "deleteRule": "cascade", "updateRule": "cascade" } } diff --git a/packages/pricing/src/migrations/Migration20230928154931.ts b/packages/pricing/src/migrations/Migration20230929122253.ts similarity index 80% rename from packages/pricing/src/migrations/Migration20230928154931.ts rename to packages/pricing/src/migrations/Migration20230929122253.ts index 42749093a8..a35ebc4db4 100644 --- a/packages/pricing/src/migrations/Migration20230928154931.ts +++ b/packages/pricing/src/migrations/Migration20230929122253.ts @@ -1,6 +1,6 @@ import { Migration } from "@mikro-orm/migrations" -export class Migration20230928154931 extends Migration { +export class Migration20230929122253 extends Migration { async up(): Promise { 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"));' @@ -34,6 +34,16 @@ export class Migration20230928154931 extends Migration { 'create index "IDX_rule_type_rule_attribute" on "rule_type" ("rule_attribute");' ) + this.addSql( + 'create table "price_set_rule_type" ("id" text not null, "price_set_id" text not null, "rule_type_id" text not null, constraint "price_set_rule_type_pkey" primary key ("id"));' + ) + this.addSql( + 'create index "IDX_price_set_rule_type_price_set_id" on "price_set_rule_type" ("price_set_id");' + ) + this.addSql( + 'create index "IDX_price_set_rule_type_rule_type_id" on "price_set_rule_type" ("rule_type_id");' + ) + this.addSql( 'create table "price_set_money_amount_rules" ("id" text not null, "price_set_money_amount_id" text not null, "rule_type_id" text not null, "value" text not null, constraint "price_set_money_amount_rules_pkey" primary key ("id"));' ) @@ -65,11 +75,18 @@ export class Migration20230928154931 extends Migration { '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 on delete 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;' + '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 on delete cascade;' ) this.addSql( - 'alter table "price_set_money_amount_rules" add constraint "price_set_money_amount_rules_price_set_money_amount_id_foreign" foreign key ("price_set_money_amount_id") references "price_set_money_amount" ("id") on update cascade;' + 'alter table "price_set_rule_type" add constraint "price_set_rule_type_price_set_id_foreign" foreign key ("price_set_id") references "price_set" ("id") on update cascade on delete cascade;' + ) + this.addSql( + 'alter table "price_set_rule_type" add constraint "price_set_rule_type_rule_type_id_foreign" foreign key ("rule_type_id") references "rule_type" ("id") on update cascade;' + ) + + this.addSql( + 'alter table "price_set_money_amount_rules" add constraint "price_set_money_amount_rules_price_set_money_amount_id_foreign" foreign key ("price_set_money_amount_id") references "price_set_money_amount" ("id") on update cascade on delete cascade;' ) this.addSql( 'alter table "price_set_money_amount_rules" add constraint "price_set_money_amount_rules_rule_type_id_foreign" foreign key ("rule_type_id") references "rule_type" ("id") on update cascade;' @@ -82,7 +99,7 @@ export class Migration20230928154931 extends Migration { 'alter table "price_rule" add constraint "price_rule_rule_type_id_foreign" foreign key ("rule_type_id") references "rule_type" ("id") on update cascade;' ) this.addSql( - 'alter table "price_rule" add constraint "price_rule_price_set_money_amount_id_foreign" foreign key ("price_set_money_amount_id") references "price_set_money_amount" ("id") on update cascade;' + 'alter table "price_rule" add constraint "price_rule_price_set_money_amount_id_foreign" foreign key ("price_set_money_amount_id") references "price_set_money_amount" ("id") on update cascade on delete cascade;' ) } } diff --git a/packages/pricing/src/models/index.ts b/packages/pricing/src/models/index.ts index 94f963b5a3..10f5df9460 100644 --- a/packages/pricing/src/models/index.ts +++ b/packages/pricing/src/models/index.ts @@ -4,4 +4,5 @@ export { default as PriceRule } from "./price-rule" export { default as PriceSet } from "./price-set" export { default as PriceSetMoneyAmount } from "./price-set-money-amount" export { default as PriceSetMoneyAmountRules } from "./price-set-money-amount-rules" +export { default as PriceSetRuleType } from "./price-set-rule-type" export { default as RuleType } from "./rule-type" diff --git a/packages/pricing/src/models/price-rule.ts b/packages/pricing/src/models/price-rule.ts index 30a5a3c45b..06c82c7027 100644 --- a/packages/pricing/src/models/price-rule.ts +++ b/packages/pricing/src/models/price-rule.ts @@ -49,6 +49,7 @@ export default class PriceRule { priority: number @ManyToOne({ + onDelete: "cascade", entity: () => PriceSetMoneyAmount, fieldName: "price_set_money_amount_id", name: "price_set_money_amount_id_unique", @@ -63,6 +64,6 @@ export default class PriceRule { @BeforeCreate() beforeCreate() { - this.id = generateEntityId(this.id, "pset") + this.id = generateEntityId(this.id, "prule") } } diff --git a/packages/pricing/src/models/price-set-money-amount-rules.ts b/packages/pricing/src/models/price-set-money-amount-rules.ts index 373a25b08e..c4040d5ce0 100644 --- a/packages/pricing/src/models/price-set-money-amount-rules.ts +++ b/packages/pricing/src/models/price-set-money-amount-rules.ts @@ -16,6 +16,7 @@ export default class PriceSetMoneyAmountRules { id!: string @ManyToOne(() => PriceSetMoneyAmount, { + onDelete: "cascade", index: "IDX_price_set_money_amount_rules_price_set_money_amount_id", }) price_set_money_amount?: PriceSetMoneyAmount | string diff --git a/packages/pricing/src/models/price-set-money-amount.ts b/packages/pricing/src/models/price-set-money-amount.ts index dd4354e001..b9cfc0926e 100644 --- a/packages/pricing/src/models/price-set-money-amount.ts +++ b/packages/pricing/src/models/price-set-money-amount.ts @@ -30,6 +30,7 @@ export default class PriceSetMoneyAmount { price_set?: PriceSet @ManyToOne(() => MoneyAmount, { + onDelete: "cascade", index: "IDX_price_set_money_amount_money_amount_id", }) money_amount?: MoneyAmount diff --git a/packages/pricing/src/models/price-set-rule-type.ts b/packages/pricing/src/models/price-set-rule-type.ts new file mode 100644 index 0000000000..f1921f5d58 --- /dev/null +++ b/packages/pricing/src/models/price-set-rule-type.ts @@ -0,0 +1,33 @@ +import { + BeforeCreate, + Entity, + ManyToOne, + PrimaryKey, + PrimaryKeyType, +} from "@mikro-orm/core" + +import PriceSet from "./price-set" +import RuleType from "./rule-type" +import { generateEntityId } from "@medusajs/utils" + +@Entity() +export default class PriceSetRuleType { + @PrimaryKey({ columnType: "text" }) + id!: string + + @ManyToOne(() => PriceSet, { + onDelete: "cascade", + index: "IDX_price_set_rule_type_price_set_id", + }) + price_set: PriceSet + + @ManyToOne(() => RuleType, { + index: "IDX_price_set_rule_type_rule_type_id", + }) + rule_type: RuleType + + @BeforeCreate() + onCreate() { + this.id = generateEntityId(this.id, "psrt") + } +} diff --git a/packages/pricing/src/models/price-set.ts b/packages/pricing/src/models/price-set.ts index fc95ea9577..20c62aeeb7 100644 --- a/packages/pricing/src/models/price-set.ts +++ b/packages/pricing/src/models/price-set.ts @@ -13,10 +13,12 @@ import { import MoneyAmount from "./money-amount" import PriceRule from "./price-rule" import PriceSetMoneyAmount from "./price-set-money-amount" +import PriceSetRuleType from "./price-set-rule-type" +import RuleType from "./rule-type" @Entity() export default class PriceSet { - [OptionalProps]?: "price_set_money_amounts" + [OptionalProps]?: "price_set_money_amounts" | "rule_types" @PrimaryKey({ columnType: "text" }) id!: string @@ -37,6 +39,13 @@ export default class PriceSet { }) money_amounts = new Collection(this) + @ManyToMany({ + entity: () => RuleType, + pivotEntity: () => PriceSetRuleType, + cascade: [Cascade.REMOVE], + }) + rule_types = new Collection(this) + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "pset") diff --git a/packages/pricing/src/models/rule-type.ts b/packages/pricing/src/models/rule-type.ts index 0093919158..d393faada6 100644 --- a/packages/pricing/src/models/rule-type.ts +++ b/packages/pricing/src/models/rule-type.ts @@ -1,11 +1,14 @@ import { generateEntityId } from "@medusajs/utils" import { BeforeCreate, + Collection, Entity, + ManyToMany, OptionalProps, PrimaryKey, Property, } from "@mikro-orm/core" +import PriceSet from "./price-set" type OptionalFields = "default_priority" @@ -25,6 +28,9 @@ class RuleType { @Property({ columnType: "integer", default: 0 }) default_priority: number + @ManyToMany(() => PriceSet, priceSet => priceSet.rule_types) + price_sets = new Collection(this); + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "rul-typ") diff --git a/packages/pricing/src/repositories/index.ts b/packages/pricing/src/repositories/index.ts index 02593f70ef..7e34dce023 100644 --- a/packages/pricing/src/repositories/index.ts +++ b/packages/pricing/src/repositories/index.ts @@ -3,5 +3,7 @@ export { CurrencyRepository } from "./currency" export { MoneyAmountRepository } from "./money-amount" export { PriceRuleRepository } from "./price-rule" export { PriceSetRepository } from "./price-set" +export { PriceSetMoneyAmountRepository } from "./price-set-money-amount" export { PriceSetMoneyAmountRulesRepository } from "./price-set-money-amount-rules" +export { PriceSetRuleTypeRepository } from "./price-set-rule-type" export { RuleTypeRepository } from "./rule-type" diff --git a/packages/pricing/src/repositories/price-rule.ts b/packages/pricing/src/repositories/price-rule.ts index 13d2268afa..c031b71fd3 100644 --- a/packages/pricing/src/repositories/price-rule.ts +++ b/packages/pricing/src/repositories/price-rule.ts @@ -77,21 +77,18 @@ export class PriceRuleRepository extends DALUtils.MikroOrmBaseRepository { this.getActiveManager(context) const toCreate = data.map((ruleData) => { - const ruleDataClone = { ...ruleData } as CreatePriceRuleDTO & { - rule_type: string - price_set: string - price_set_money_amount: string - } + const ruleDataClone = { ...ruleData } as any - ruleDataClone.rule_type = ruleData.rule_type_id - ruleDataClone.price_set = ruleData.price_set_id - ruleDataClone.price_set_money_amount = ruleData.price_set_money_amount_id + ruleDataClone.rule_type ??= ruleData.rule_type_id + ruleDataClone.price_set ??= ruleData.price_set_id + ruleDataClone.price_set_money_amount ??= + ruleData.price_set_money_amount_id return ruleDataClone }) const priceRules = toCreate.map((ruleData) => { - return manager.create(PriceRule, ruleData as CreatePriceRuleDTO) + return manager.create(PriceRule, ruleData) }) manager.persist(priceRules) diff --git a/packages/pricing/src/repositories/price-set-money-amount-rules.ts b/packages/pricing/src/repositories/price-set-money-amount-rules.ts index b57b61c4de..a540d566a7 100644 --- a/packages/pricing/src/repositories/price-set-money-amount-rules.ts +++ b/packages/pricing/src/repositories/price-set-money-amount-rules.ts @@ -78,7 +78,7 @@ export class PriceSetMoneyAmountRulesRepository extends DALUtils.MikroOrmBaseRep return manager.create(PriceSetMoneyAmountRules, psmarData) }) - manager.persistAndFlush(psmar) + manager.persist(psmar) return psmar } diff --git a/packages/pricing/src/repositories/price-set-money-amount.ts b/packages/pricing/src/repositories/price-set-money-amount.ts new file mode 100644 index 0000000000..06e4e2d1ad --- /dev/null +++ b/packages/pricing/src/repositories/price-set-money-amount.ts @@ -0,0 +1,127 @@ +import { + Context, + CreatePriceSetMoneyAmountDTO, + DAL, + UpdatePriceSetMoneyAmountDTO, +} 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 { PriceSetMoneyAmount } from "@models" + +export class PriceSetMoneyAmountRepository 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 = { where: {} }, + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.find( + PriceSetMoneyAmount, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async findAndCount( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise<[PriceSetMoneyAmount[], number]> { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.findAndCount( + PriceSetMoneyAmount, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async delete(ids: string[], context: Context = {}): Promise { + const manager = this.getActiveManager(context) + await manager.nativeDelete(PriceSetMoneyAmount, { id: { $in: ids } }, {}) + } + + async create( + data: CreatePriceSetMoneyAmountDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const currencies = data.map((currencyData) => { + return manager.create(PriceSetMoneyAmount, currencyData as PriceSetMoneyAmount) + }) + + manager.persist(currencies) + + return currencies + } + + async update( + data: UpdatePriceSetMoneyAmountDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + const ids = data.map((psmaData) => psmaData.id) + const existingPriceSetMoneyAmounts = await this.find( + { + where: { + id: { + $in: ids, + }, + }, + }, + context + ) + + const existingPSMAMap = new Map( + existingPriceSetMoneyAmounts.map<[string, PriceSetMoneyAmount]>((psma) => [ + psma.id, + psma, + ]) + ) + + const priceSetMoneyAmounts = data.map((currencyData) => { + const existingPSMA = existingPSMAMap.get(currencyData.id) + + if (!existingPSMA) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `PriceSetMoneyAmount with id "${currencyData.id}" not found` + ) + } + + return manager.assign(existingPSMA, currencyData) + }) + + manager.persist(priceSetMoneyAmounts) + + return priceSetMoneyAmounts + } +} diff --git a/packages/pricing/src/repositories/price-set-rule-type.ts b/packages/pricing/src/repositories/price-set-rule-type.ts new file mode 100644 index 0000000000..9e6ba16fd2 --- /dev/null +++ b/packages/pricing/src/repositories/price-set-rule-type.ts @@ -0,0 +1,127 @@ +import { + Context, + DAL, + UpdatePriceSetDTO, + CreatePriceSetRuleTypeDTO, +} 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, PriceSetRuleType, RuleType } from "@models" + +export class PriceSetRuleTypeRepository 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 = { where: {} }, + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.find( + PriceSetRuleType, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async findAndCount( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise<[PriceSetRuleType[], number]> { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.findAndCount( + PriceSetRuleType, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async delete(ids: string[], context: Context = {}): Promise { + const manager = this.getActiveManager(context) + await manager.nativeDelete(PriceSetRuleType, { id: { $in: ids } }, {}) + } + + async create( + data: CreatePriceSetRuleTypeDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const priceSets = data.map((priceSetData) => { + return manager.create(PriceSetRuleType, priceSetData) + }) + + manager.persist(priceSets) + + return priceSets + } + + async update( + data: UpdatePriceSetDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(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, PriceSetRuleType]>((priceSet) => [ + priceSet.id, + priceSet, + ]) + ) + + const priceSets = data.map((priceSetData) => { + const existingPriceSet = existingPriceSetMap.get(priceSetData.id) + + if (!existingPriceSet) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `PriceSetRuleType with id "${priceSetData.id}" not found` + ) + } + + return manager.assign(existingPriceSet, priceSetData) + }) + + manager.persist(priceSets) + + return priceSets + } +} diff --git a/packages/pricing/src/repositories/price-set.ts b/packages/pricing/src/repositories/price-set.ts index eb856e102e..fd4b9442b3 100644 --- a/packages/pricing/src/repositories/price-set.ts +++ b/packages/pricing/src/repositories/price-set.ts @@ -1,8 +1,8 @@ import { Context, - CreatePriceSetDTO, DAL, UpdatePriceSetDTO, + CreatePriceSetDTO, } from "@medusajs/types" import { DALUtils, MedusaError } from "@medusajs/utils" import { @@ -69,7 +69,7 @@ export class PriceSetRepository extends DALUtils.MikroOrmBaseRepository { } async create( - data: CreatePriceSetDTO[], + data: Omit[], context: Context = {} ): Promise { const manager = this.getActiveManager(context) diff --git a/packages/pricing/src/services/index.ts b/packages/pricing/src/services/index.ts index f97d09665b..e4b65c5288 100644 --- a/packages/pricing/src/services/index.ts +++ b/packages/pricing/src/services/index.ts @@ -2,6 +2,8 @@ export { default as CurrencyService } from "./currency" export { default as MoneyAmountService } from "./money-amount" export { default as PriceRuleService } from "./price-rule" export { default as PriceSetService } from "./price-set" +export { default as PriceSetMoneyAmountService } from "./price-set-money-amount" export { default as PriceSetMoneyAmountRulesService } from "./price-set-money-amount-rules" +export { default as PriceSetRuleTypeService } from "./price-set-rule-type" export { default as PricingModuleService } from "./pricing-module" export { default as RuleTypeService } from "./rule-type" diff --git a/packages/pricing/src/services/price-set-money-amount.ts b/packages/pricing/src/services/price-set-money-amount.ts new file mode 100644 index 0000000000..de1a45aa32 --- /dev/null +++ b/packages/pricing/src/services/price-set-money-amount.ts @@ -0,0 +1,118 @@ +import { Context, DAL, FindConfig, PricingTypes } from "@medusajs/types" +import { + InjectManager, + InjectTransactionManager, + MedusaContext, + ModulesSdkUtils, + doNotForceTransaction, + retrieveEntity, + shouldForceTransaction, +} from "@medusajs/utils" +import { PriceSet, PriceSetMoneyAmount } from "@models" +import { PriceSetMoneyAmountRepository } from "@repositories" + +type InjectedDependencies = { + priceSetMoneyAmountRepository: DAL.RepositoryService +} + +export default class PriceSetMoneyAmountService< + TEntity extends PriceSetMoneyAmount = PriceSetMoneyAmount +> { + protected readonly priceSetMoneyAmountRepository_: DAL.RepositoryService + + constructor({ priceSetMoneyAmountRepository }: InjectedDependencies) { + this.priceSetMoneyAmountRepository_ = priceSetMoneyAmountRepository + } + + @InjectManager("priceSetMoneyAmountRepository_") + async retrieve( + priceSetId: string, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await retrieveEntity< + PriceSetMoneyAmount, + PricingTypes.PriceSetRuleTypeDTO + >({ + id: priceSetId, + entityName: PriceSet.name, + repository: this.priceSetMoneyAmountRepository_, + config, + sharedContext, + })) as TEntity + } + + @InjectManager("priceSetMoneyAmountRepository_") + async list( + filters: PricingTypes.FilterablePriceSetRuleTypeProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await this.priceSetMoneyAmountRepository_.find( + this.buildQueryForList(filters, config), + sharedContext + )) as TEntity[] + } + + @InjectManager("priceSetMoneyAmountRepository_") + async listAndCount( + filters: PricingTypes.FilterablePriceSetRuleTypeProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise<[TEntity[], number]> { + return (await this.priceSetMoneyAmountRepository_.findAndCount( + this.buildQueryForList(filters, config), + sharedContext + )) as [TEntity[], number] + } + + private buildQueryForList( + filters: PricingTypes.FilterablePriceSetRuleTypeProps = {}, + config: FindConfig = {} + ) { + const queryOptions = ModulesSdkUtils.buildQuery(filters, config) + + if (filters.id) { + queryOptions.where.id = { $in: filters.id } + } + + return queryOptions + } + + @InjectTransactionManager( + shouldForceTransaction, + "priceSetMoneyAmountRepository_" + ) + async create( + data: PricingTypes.CreatePriceSetMoneyAmountDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await ( + this.priceSetMoneyAmountRepository_ as unknown as PriceSetMoneyAmountRepository + ).create(data, sharedContext)) as TEntity[] + } + + @InjectTransactionManager( + shouldForceTransaction, + "priceSetMoneyAmountRepository_" + ) + async update( + data: PricingTypes.UpdatePriceSetMoneyAmountDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await ( + this.priceSetMoneyAmountRepository_ as unknown as PriceSetMoneyAmountRepository + ).update(data, sharedContext)) as TEntity[] + } + + @InjectTransactionManager( + doNotForceTransaction, + "priceSetMoneyAmountRepository_" + ) + async delete( + ids: string[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + await this.priceSetMoneyAmountRepository_.delete(ids, sharedContext) + } +} diff --git a/packages/pricing/src/services/price-set-rule-type.ts b/packages/pricing/src/services/price-set-rule-type.ts new file mode 100644 index 0000000000..fbe719ca90 --- /dev/null +++ b/packages/pricing/src/services/price-set-rule-type.ts @@ -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, PriceSetRuleType } from "@models" +import { PriceSetRuleTypeRepository } from "src/repositories/price-set-rule-type" + +type InjectedDependencies = { + priceSetRuleTypeRepository: DAL.RepositoryService +} + +export default class PriceSetRuleTypeService { + protected readonly priceSetRuleTypeRepository_: DAL.RepositoryService + + constructor({ priceSetRuleTypeRepository }: InjectedDependencies) { + this.priceSetRuleTypeRepository_ = priceSetRuleTypeRepository + } + + @InjectManager("priceSetRuleTypeRepository_") + async retrieve( + priceSetId: string, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await retrieveEntity({ + id: priceSetId, + entityName: PriceSet.name, + repository: this.priceSetRuleTypeRepository_, + config, + sharedContext, + })) as TEntity + } + + @InjectManager("priceSetRuleTypeRepository_") + async list( + filters: PricingTypes.FilterablePriceSetRuleTypeProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await this.priceSetRuleTypeRepository_.find( + this.buildQueryForList(filters, config), + sharedContext + )) as TEntity[] + } + + @InjectManager("priceSetRuleTypeRepository_") + async listAndCount( + filters: PricingTypes.FilterablePriceSetRuleTypeProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise<[TEntity[], number]> { + return (await this.priceSetRuleTypeRepository_.findAndCount( + this.buildQueryForList(filters, config), + sharedContext + )) as [TEntity[], number] + } + + private buildQueryForList( + filters: PricingTypes.FilterablePriceSetRuleTypeProps = {}, + config: FindConfig = {} + ) { + const queryOptions = ModulesSdkUtils.buildQuery(filters, config) + + if (filters.id) { + queryOptions.where.id = { $in: filters.id } + } + + return queryOptions + } + + @InjectTransactionManager(shouldForceTransaction, "priceSetRuleTypeRepository_") + async create( + data: PricingTypes.CreatePriceSetRuleTypeDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await (this.priceSetRuleTypeRepository_ as PriceSetRuleTypeRepository).create( + data, + sharedContext + )) as TEntity[] + } + + @InjectTransactionManager(shouldForceTransaction, "priceSetRuleTypeRepository_") + async update( + data: PricingTypes.UpdatePriceSetRuleTypeDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await (this.priceSetRuleTypeRepository_ as PriceSetRuleTypeRepository).update( + data, + sharedContext + )) as TEntity[] + } + + @InjectTransactionManager(doNotForceTransaction, "priceSetRuleTypeRepository_") + async delete( + ids: string[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + await this.priceSetRuleTypeRepository_.delete(ids, sharedContext) + } +} diff --git a/packages/pricing/src/services/price-set.ts b/packages/pricing/src/services/price-set.ts index 7edc65df11..b3fe10189f 100644 --- a/packages/pricing/src/services/price-set.ts +++ b/packages/pricing/src/services/price-set.ts @@ -67,7 +67,7 @@ export default class PriceSetService { @InjectTransactionManager(shouldForceTransaction, "priceSetRepository_") async create( - data: PricingTypes.CreatePriceSetDTO[], + data: Omit[], @MedusaContext() sharedContext: Context = {} ): Promise { return (await (this.priceSetRepository_ as PriceSetRepository).create( @@ -78,7 +78,7 @@ export default class PriceSetService { @InjectTransactionManager(shouldForceTransaction, "priceSetRepository_") async update( - data: PricingTypes.UpdatePriceSetDTO[], + data: Omit[], @MedusaContext() sharedContext: Context = {} ): Promise { return (await (this.priceSetRepository_ as PriceSetRepository).update( diff --git a/packages/pricing/src/services/pricing-module.ts b/packages/pricing/src/services/pricing-module.ts index d413207b25..daa782b92b 100644 --- a/packages/pricing/src/services/pricing-module.ts +++ b/packages/pricing/src/services/pricing-module.ts @@ -1,26 +1,35 @@ import { Context, + CreateMoneyAmountDTO, DAL, FindConfig, InternalModuleDeclaration, ModuleJoinerConfig, + PriceSetDTO, PricingContext, PricingFilters, PricingTypes, + RuleTypeDTO, } from "@medusajs/types" + import { Currency, MoneyAmount, PriceRule, PriceSet, + PriceSetMoneyAmount, PriceSetMoneyAmountRules, + PriceSetRuleType, RuleType, } from "@models" + import { CurrencyService, MoneyAmountService, PriceRuleService, PriceSetMoneyAmountRulesService, + PriceSetMoneyAmountService, + PriceSetRuleTypeService, PriceSetService, RuleTypeService, } from "@services" @@ -31,10 +40,12 @@ import { InjectManager, InjectTransactionManager, MedusaContext, + MedusaError, groupBy, shouldForceTransaction, } from "@medusajs/utils" +import { AddPricesDTO } from "@medusajs/types" import { joinerConfig } from "../joiner-config" type InjectedDependencies = { @@ -45,6 +56,8 @@ type InjectedDependencies = { priceSetMoneyAmountRulesService: PriceSetMoneyAmountRulesService ruleTypeService: RuleTypeService priceRuleService: PriceRuleService + priceSetRuleTypeService: PriceSetRuleTypeService + priceSetMoneyAmountService: PriceSetMoneyAmountService } export default class PricingModuleService< @@ -53,7 +66,9 @@ export default class PricingModuleService< TCurrency extends Currency = Currency, TRuleType extends RuleType = RuleType, TPriceSetMoneyAmountRules extends PriceSetMoneyAmountRules = PriceSetMoneyAmountRules, - TPriceRule extends PriceRule = PriceRule + TPriceRule extends PriceRule = PriceRule, + TPriceSetRuleType extends PriceSetRuleType = PriceSetRuleType, + TPriceSetMoneyAmount extends PriceSetMoneyAmount = PriceSetMoneyAmount > implements PricingTypes.IPricingModuleService { protected baseRepository_: DAL.RepositoryService @@ -63,6 +78,8 @@ export default class PricingModuleService< protected readonly priceSetService_: PriceSetService protected readonly priceSetMoneyAmountRulesService_: PriceSetMoneyAmountRulesService protected readonly priceRuleService_: PriceRuleService + protected readonly priceSetRuleTypeService_: PriceSetRuleTypeService + protected readonly priceSetMoneyAmountService_: PriceSetMoneyAmountService constructor( { @@ -73,6 +90,8 @@ export default class PricingModuleService< priceSetService, priceSetMoneyAmountRulesService, priceRuleService, + priceSetRuleTypeService, + priceSetMoneyAmountService, }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { @@ -84,6 +103,8 @@ export default class PricingModuleService< this.priceSetMoneyAmountRulesService_ = priceSetMoneyAmountRulesService this.ruleTypeService_ = ruleTypeService this.priceRuleService_ = priceRuleService + this.priceSetRuleTypeService_ = priceSetRuleTypeService + this.priceSetMoneyAmountService_ = priceSetMoneyAmountService } __joinerConfig(): ModuleJoinerConfig { @@ -110,6 +131,17 @@ export default class PricingModuleService< const quantity = context.quantity delete context.quantity + // Currency code here is a required param. + const currencyCode = context.currency_code + delete context.currency_code + + if (!currencyCode) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `currency_code is a required input in the pricing context` + ) + } + // Gets all the price set money amounts where rules match for each of the contexts // that the price set is configured for const psmaSubQueryKnex = knex({ @@ -121,9 +153,13 @@ export default class PricingModuleService< money_amount_id: "psma.money_amount_id", number_rules: "psma.number_rules", }) - .join("price_rule as pr", "pr.price_set_money_amount_id", "psma.id") - .join("rule_type as rt", "rt.id", "pr.rule_type_id") + .leftJoin("price_set_money_amount as psma1", "psma1.id", "psma.id") + .leftJoin("price_rule as pr", "pr.price_set_money_amount_id", "psma.id") + .leftJoin("rule_type as rt", "rt.id", "pr.rule_type_id") .orderBy("number_rules", "desc") + .orWhere("psma1.number_rules", "=", 0) + .groupBy("psma.id") + .having(knex.raw("count(DISTINCT rt.rule_attribute) = psma.number_rules")) for (const [key, value] of Object.entries(context)) { psmaSubQueryKnex.orWhere({ @@ -132,10 +168,6 @@ export default class PricingModuleService< }) } - psmaSubQueryKnex - .groupBy("psma.id") - .having(knex.raw("count(DISTINCT rt.rule_attribute) = psma.number_rules")) - const priceSetQueryKnex = knex({ ps: "price_set", }) @@ -150,9 +182,10 @@ export default class PricingModuleService< }) .join(psmaSubQueryKnex.as("psma"), "psma.price_set_id", "ps.id") .join("money_amount as ma", "ma.id", "psma.money_amount_id") - .join("price_rule as pr", "pr.price_set_money_amount_id", "psma.id") - .join("rule_type as rt", "rt.id", "pr.rule_type_id") + .leftJoin("price_rule as pr", "pr.price_set_money_amount_id", "psma.id") + .leftJoin("rule_type as rt", "rt.id", "pr.rule_type_id") .whereIn("ps.id", pricingFilters.id) + .andWhere("ma.currency_code", "=", currencyCode) .orderBy([ { column: "number_rules", order: "desc" }, { column: "default_priority", order: "desc" }, @@ -163,7 +196,7 @@ export default class PricingModuleService< priceSetQueryKnex.andWhere("ma.max_quantity", ">=", quantity) } - const isContextPresent = Object.entries(context).length + const isContextPresent = Object.entries(context).length || !!currencyCode // Only if the context is present do we need to query the database. const queryBuilderResults = isContextPresent ? await priceSetQueryKnex : [] const pricesSetPricesMap = groupBy(queryBuilderResults, "id") @@ -248,12 +281,257 @@ export default class PricingModuleService< ] } - @InjectTransactionManager(shouldForceTransaction, "baseRepository_") async create( + data: PricingTypes.CreatePriceSetDTO, + sharedContext?: Context + ): Promise + + async create( + data: PricingTypes.CreatePriceSetDTO[], + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async create( + data: PricingTypes.CreatePriceSetDTO | PricingTypes.CreatePriceSetDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const input = Array.isArray(data) ? data : [data] + + const priceSets = await this.create_(input, sharedContext) + + const dbPriceSets = await this.list( + { id: priceSets.filter((p) => !!p).map((p) => p!.id) }, + { + relations: ["rule_types", "money_amounts", "price_rules"], + } + ) + + return (Array.isArray(data) ? dbPriceSets : dbPriceSets[0]) as unknown as + | PriceSetDTO + | PriceSetDTO[] + } + + @InjectTransactionManager(shouldForceTransaction, "baseRepository_") + protected async create_( data: PricingTypes.CreatePriceSetDTO[], @MedusaContext() sharedContext: Context = {} ) { - const priceSets = await this.priceSetService_.create(data, sharedContext) + const ruleAttributes = data + .map((d) => d.rules?.map((r) => r.rule_attribute) ?? []) + .flat() + + const ruleTypes = await this.ruleTypeService_.list( + { + rule_attribute: ruleAttributes, + }, + {}, + sharedContext + ) + + const ruleTypeMap = ruleTypes.reduce((acc, curr) => { + acc.set(curr.rule_attribute, curr) + return acc + }, new Map()) + + const invalidRuleAttributes = ruleAttributes.filter( + (r) => !ruleTypeMap.has(r) + ) + + if (invalidRuleAttributes.length > 0) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Rule types don't exist for: ${invalidRuleAttributes.join(", ")}` + ) + } + + const invalidMoneyAmountRule = data + .map((d) => d.prices?.map((ma) => Object.keys(ma.rules)).flat() ?? []) + .flat() + .filter((r) => !ruleTypeMap.has(r)) + + if (invalidMoneyAmountRule.length > 0) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Rule types don't exist for money amounts with rule attribute: ${invalidMoneyAmountRule.join( + ", " + )}` + ) + } + + const priceSets = await Promise.all( + data.map(async (d) => { + const { rules, prices, ...rest } = d + const [priceSet] = await this.priceSetService_.create( + [rest], + sharedContext + ) + + if (rules?.length) { + const priceSetRuleTypesCreate = rules!.map((r) => ({ + rule_type: ruleTypeMap.get(r.rule_attribute), + price_set: priceSet, + })) + + await this.priceSetRuleTypeService_.create( + priceSetRuleTypesCreate as unknown as PricingTypes.CreatePriceSetRuleTypeDTO[], + sharedContext + ) + } + + if (prices?.length) { + for (const ma of prices) { + const [moneyAmount] = await this.moneyAmountService_.create( + [ma] as unknown as CreateMoneyAmountDTO[], + sharedContext + ) + + const numberOfRules = ma.rules ? Object.entries(ma.rules).length : 0 + + const [priceSetMoneyAmount] = + await this.priceSetMoneyAmountService_.create( + [ + { + price_set: priceSet, + money_amount: moneyAmount, + title: "test", + number_rules: numberOfRules, + }, + ] as unknown as PricingTypes.CreatePriceSetMoneyAmountDTO[], + sharedContext + ) + + if (numberOfRules) { + const priceSetRulesCreate = Object.entries(ma.rules).map( + ([k, v]) => ({ + price_set_money_amount: priceSetMoneyAmount, + rule_type: ruleTypeMap.get(k), + price_set: priceSet, + value: v, + price_list_id: "test", + }) + ) + + await this.priceRuleService_.create( + priceSetRulesCreate as unknown as PricingTypes.CreatePriceRuleDTO[], + sharedContext + ) + } + } + } + + return priceSet + }) + ) + + return priceSets + } + + async addRules( + data: PricingTypes.AddRulesDTO, + sharedContext?: Context + ): Promise + + async addRules( + data: PricingTypes.AddRulesDTO[], + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async addRules( + data: PricingTypes.AddRulesDTO | PricingTypes.AddRulesDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const inputs = Array.isArray(data) ? data : [data] + + const priceSets = await this.addRules_(inputs, sharedContext) + + return (Array.isArray(data) ? priceSets : priceSets[0]) as unknown as + | PricingTypes.PriceSetDTO[] + | PricingTypes.PriceSetDTO + } + + @InjectTransactionManager(shouldForceTransaction, "baseRepository_") + protected async addRules_( + inputs: PricingTypes.AddRulesDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const priceSets = await this.priceSetService_.list( + { id: inputs.map((d) => d.priceSetId) }, + { relations: ["rule_types"] } + ) + + const priceSetRuleTypeMap: Map> = new Map( + priceSets.map((priceSet) => [ + priceSet.id, + new Map([...priceSet.rule_types].map((rt) => [rt.rule_attribute, rt])), + ]) + ) + + const priceSetMap = new Map(priceSets.map((p) => [p.id, p])) + + const invalidPriceSetInputs = inputs.filter( + (d) => !priceSetMap.has(d.priceSetId) + ) + + if (invalidPriceSetInputs.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `PriceSets with ids: ${invalidPriceSetInputs + .map((d) => d.priceSetId) + .join(", ")} was not found` + ) + } + + const ruleTypes = await this.ruleTypeService_.list( + { + rule_attribute: inputs + .map((d) => d.rules.map((r) => r.attribute)) + .flat(), + }, + {}, + sharedContext + ) + + const ruleTypeMap: Map = new Map( + ruleTypes.map((rt) => [rt.rule_attribute, rt]) + ) + + const invalidRuleAttributeInputs = inputs + .map((d) => d.rules.map((r) => r.attribute)) + .flat() + .filter((r) => !ruleTypeMap.has(r)) + + if (invalidRuleAttributeInputs.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Rule types don't exist for attributes: ${[ + ...new Set(invalidRuleAttributeInputs), + ].join(", ")}` + ) + } + + const priceSetRuleTypesCreate: PricingTypes.CreatePriceSetRuleTypeDTO[] = [] + + inputs.forEach((d) => { + const priceSet = priceSetMap.get(d.priceSetId) + + for (const r of d.rules) { + if (priceSetRuleTypeMap.get(d.priceSetId)!.has(r.attribute)) { + continue + } + + priceSetRuleTypesCreate.push({ + rule_type: ruleTypeMap.get(r.attribute) as RuleTypeDTO, + price_set: priceSet as unknown as PriceSetDTO, + }) + } + }) + + await this.priceSetRuleTypeService_.create( + priceSetRuleTypesCreate as unknown as PricingTypes.CreatePriceSetRuleTypeDTO[], + sharedContext + ) return this.baseRepository_.serialize( priceSets, @@ -263,6 +541,168 @@ export default class PricingModuleService< ) } + async addPrices( + data: AddPricesDTO, + sharedContext?: Context + ): Promise + + async addPrices( + data: AddPricesDTO[], + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async addPrices( + data: AddPricesDTO | AddPricesDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const input = Array.isArray(data) ? data : [data] + + await this.addPrices_(input, sharedContext) + + return (await this.list( + { id: input.map((d) => d.priceSetId) }, + { relations: ["money_amounts"] } + )) as unknown as PricingTypes.PriceSetDTO[] | PricingTypes.PriceSetDTO + } + + @InjectTransactionManager(shouldForceTransaction, "baseRepository_") + protected async addPrices_( + input: AddPricesDTO[], + @MedusaContext() sharedContext: Context = {} + ) { + const priceSets = await this.list( + { id: input.map((d) => d.priceSetId) }, + { relations: ["rule_types"] }, + sharedContext + ) + + const priceSetMap = new Map(priceSets.map((p) => [p.id, p])) + + const ruleTypeMap: Map> = new Map( + priceSets.map((priceSet) => [ + priceSet.id, + new Map( + priceSet.rule_types?.map((rt) => [rt.rule_attribute, rt]) ?? [] + ), + ]) + ) + + input.forEach(({ priceSetId, prices }) => { + const priceSet = priceSetMap.get(priceSetId) + + if (!priceSet) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Price set with id: ${priceSetId} not found` + ) + } + + const ruleAttributes = prices + .map((d) => (d.rules ? Object.keys(d.rules) : [])) + .flat() + + const invalidRuleAttributes = ruleAttributes.filter( + (r) => !ruleTypeMap.get(priceSetId)!.has(r) + ) + + if (invalidRuleAttributes.length > 0) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Rule types don't exist for: ${invalidRuleAttributes.join(", ")}` + ) + } + }) + + for (const { priceSetId, prices } of input) { + await Promise.all( + prices.map(async (ma) => { + const [moneyAmount] = await this.moneyAmountService_.create( + [ma] as unknown as CreateMoneyAmountDTO[], + sharedContext + ) + + const numberOfRules = Object.entries(ma?.rules ?? {}).length + + const [priceSetMoneyAmount] = + await this.priceSetMoneyAmountService_.create( + [ + { + price_set: priceSetId, + money_amount: moneyAmount, + title: "test", + number_rules: numberOfRules, + }, + ] as unknown as PricingTypes.CreatePriceSetMoneyAmountDTO[], + sharedContext + ) + + if (numberOfRules) { + const priceSetRulesCreate = Object.entries(ma.rules!).map( + ([k, v]) => ({ + price_set_money_amount: priceSetMoneyAmount, + rule_type: ruleTypeMap.get(priceSetId)!.get(k), + price_set: priceSetId, + value: v, + price_list_id: "test", + }) + ) + + await this.priceRuleService_.create( + priceSetRulesCreate as unknown as PricingTypes.CreatePriceRuleDTO[], + sharedContext + ) + } + + return moneyAmount + }) + ) + } + + return priceSets + } + + @InjectTransactionManager(shouldForceTransaction, "baseRepository_") + async removeRules( + data: PricingTypes.RemovePriceSetRulesDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const priceSets = await this.priceSetService_.list({ + id: data.map((d) => d.id), + }) + const priceSetIds = priceSets.map((ps) => ps.id) + + const ruleTypes = await this.ruleTypeService_.list({ + rule_attribute: data.map((d) => d.rules || []).flat(), + }) + const ruleTypeIds = ruleTypes.map((rt) => rt.id) + + const priceSetRuleTypes = await this.priceSetRuleTypeService_.list({ + price_set_id: priceSetIds, + rule_type_id: ruleTypeIds, + }) + + const priceRules = await this.priceRuleService_.list( + { + price_set_id: priceSetIds, + rule_type_id: ruleTypeIds, + }, + { + select: ["price_set_money_amount"], + } + ) + + await this.priceSetRuleTypeService_.delete( + priceSetRuleTypes.map((psrt) => psrt.id), + sharedContext + ) + + await this.priceSetMoneyAmountService_.delete( + priceRules.map((pr) => pr.price_set_money_amount.id), + sharedContext + ) + } + @InjectTransactionManager(shouldForceTransaction, "baseRepository_") async update( data: PricingTypes.UpdatePriceSetDTO[], @@ -583,10 +1023,10 @@ export default class PricingModuleService< @InjectTransactionManager(shouldForceTransaction, "baseRepository_") async deleteRuleTypes( - ruleTypes: string[], + ruleTypeIds: string[], @MedusaContext() sharedContext: Context = {} ): Promise { - await this.ruleTypeService_.delete(ruleTypes, sharedContext) + await this.ruleTypeService_.delete(ruleTypeIds, sharedContext) } @InjectManager("baseRepository_") diff --git a/packages/types/src/pricing/common/index.ts b/packages/types/src/pricing/common/index.ts index bc6e09c7d3..4a7578a1b3 100644 --- a/packages/types/src/pricing/common/index.ts +++ b/packages/types/src/pricing/common/index.ts @@ -1,7 +1,8 @@ export * from "./currency" export * from "./money-amount" +export * from "./price-rule" export * from "./price-set" export * from "./price-set-money-amount" export * from "./price-set-money-amount-rules" +export * from "./price-set-rule-type" export * from "./rule-type" -export * from './price-rule' diff --git a/packages/types/src/pricing/common/money-amount.ts b/packages/types/src/pricing/common/money-amount.ts index 33ee099776..b63b24fb60 100644 --- a/packages/types/src/pricing/common/money-amount.ts +++ b/packages/types/src/pricing/common/money-amount.ts @@ -11,7 +11,7 @@ export interface MoneyAmountDTO { } export interface CreateMoneyAmountDTO { - id: string + id?: string currency_code: string currency?: CreateCurrencyDTO amount?: number diff --git a/packages/types/src/pricing/common/price-rule.ts b/packages/types/src/pricing/common/price-rule.ts index 6ed9858568..0d29a1a4dc 100644 --- a/packages/types/src/pricing/common/price-rule.ts +++ b/packages/types/src/pricing/common/price-rule.ts @@ -22,7 +22,7 @@ export interface CreatePriceRuleDTO { is_dynamic?: boolean value: string priority?: number - price_set_money_amount_id: string + price_set_money_amount_id: string price_list_id: string } @@ -41,4 +41,6 @@ export interface FilterablePriceRuleProps extends BaseFilterable { id?: string[] name?: string[] + price_set_id?: string[] + rule_type_id?: string[] } diff --git a/packages/types/src/pricing/common/price-set-money-amount.ts b/packages/types/src/pricing/common/price-set-money-amount.ts index a041d90f77..d07b1ea9fe 100644 --- a/packages/types/src/pricing/common/price-set-money-amount.ts +++ b/packages/types/src/pricing/common/price-set-money-amount.ts @@ -5,5 +5,18 @@ export interface PriceSetMoneyAmountDTO { id: string title?: string price_set?: PriceSetDTO - rule_type?: MoneyAmountDTO + money_amount?: MoneyAmountDTO +} + +export interface UpdatePriceSetMoneyAmountDTO { + id: string + title?: string + price_set?: PriceSetDTO + money_amount?: MoneyAmountDTO +} + +export interface CreatePriceSetMoneyAmountDTO { + title?: string + price_set?: PriceSetDTO | string + money_amount?: MoneyAmountDTO | string } diff --git a/packages/types/src/pricing/common/price-set-rule-type.ts b/packages/types/src/pricing/common/price-set-rule-type.ts new file mode 100644 index 0000000000..83a216efdb --- /dev/null +++ b/packages/types/src/pricing/common/price-set-rule-type.ts @@ -0,0 +1,29 @@ +import { BaseFilterable } from "../../dal" +import { PriceSetDTO } from "./price-set" +import { PriceSetMoneyAmountDTO } from "./price-set-money-amount" +import { RuleTypeDTO } from "./rule-type" + +export interface PriceSetRuleTypeDTO { + id: string + price_set: PriceSetDTO + rule_type: RuleTypeDTO + value: string +} + +export interface CreatePriceSetRuleTypeDTO { + price_set: PriceSetDTO + rule_type: RuleTypeDTO +} + +export interface UpdatePriceSetRuleTypeDTO { + id: string + price_set?: string + rule_type?: string +} + +export interface FilterablePriceSetRuleTypeProps + extends BaseFilterable { + id?: string[] + rule_type_id?: string[] + price_set_id?: string[] +} diff --git a/packages/types/src/pricing/common/price-set.ts b/packages/types/src/pricing/common/price-set.ts index 47d084080e..0bb48a7e69 100644 --- a/packages/types/src/pricing/common/price-set.ts +++ b/packages/types/src/pricing/common/price-set.ts @@ -1,5 +1,10 @@ import { BaseFilterable } from "../../dal" -import { FilterableMoneyAmountProps, MoneyAmountDTO } from "./money-amount" +import { + CreateMoneyAmountDTO, + FilterableMoneyAmountProps, + MoneyAmountDTO, +} from "./money-amount" +import { RuleTypeDTO } from "./rule-type" export interface PricingContext { context?: Record @@ -12,6 +17,7 @@ export interface PricingFilters { export interface PriceSetDTO { id: string money_amounts?: MoneyAmountDTO[] + rule_types?: RuleTypeDTO[] } export interface CalculatedPriceSetDTO { @@ -22,8 +28,25 @@ export interface CalculatedPriceSetDTO { max_quantity: number | null } +export interface AddRulesDTO { + priceSetId: string + rules: { attribute: string }[] +} + +export interface AddPricesDTO { + priceSetId: string + prices: (CreateMoneyAmountDTO & { + rules?: Record + })[] +} +export interface RemovePriceSetRulesDTO { + id: string + rules: string[] +} + export interface CreatePriceSetDTO { - money_amounts?: MoneyAmountDTO[] + rules?: { rule_attribute: string }[] + prices?: (CreateMoneyAmountDTO & { rules: Record })[] } export interface UpdatePriceSetDTO { diff --git a/packages/types/src/pricing/common/rule-type.ts b/packages/types/src/pricing/common/rule-type.ts index dbc9619f3a..44619428c5 100644 --- a/packages/types/src/pricing/common/rule-type.ts +++ b/packages/types/src/pricing/common/rule-type.ts @@ -25,4 +25,5 @@ export interface FilterableRuleTypeProps extends BaseFilterable { id?: string[] name?: string[] + rule_attribute?: string[] } diff --git a/packages/types/src/pricing/service.ts b/packages/types/src/pricing/service.ts index 0eefffcad8..e3e35302ec 100644 --- a/packages/types/src/pricing/service.ts +++ b/packages/types/src/pricing/service.ts @@ -1,4 +1,6 @@ import { + AddPricesDTO, + AddRulesDTO, CalculatedPriceSetDTO, CreateCurrencyDTO, CreateMoneyAmountDTO, @@ -19,6 +21,7 @@ import { PriceSetMoneyAmountRulesDTO, PricingContext, PricingFilters, + RemovePriceSetRulesDTO, RuleTypeDTO, UpdateCurrencyDTO, UpdateMoneyAmountDTO, @@ -35,195 +38,532 @@ import { Context } from "../shared-context" export interface IPricingModuleService { __joinerConfig(): ModuleJoinerConfig + /** + * Calculates prices based on the provided filters and context. + * + * @async + * @param {PricingFilters} filters - PriceSet filters + * @param {PricingContext=} context - Optional pricing context to select prices. + * @param {Context=} sharedContext - Optional shared context. + * @returns {Promise} A promise that resolves to the calculated prices. + */ calculatePrices( filters: PricingFilters, context?: PricingContext, sharedContext?: Context ): Promise + /** + * Retrieves a priceSet by its ID. + * + * @async + * @param {string} id - The ID of the priceSet to retrieve. + * @param {FindConfig=} config - Optional configuration for the retrieval. + * @param {Context=} sharedContext - Optional shared context. + * @returns {Promise} A promise that resolves to a PriceSetDTO. + */ retrieve( id: string, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists price sets based on optional filters and configuration. + * + * @async + * @param {FilterablePriceSetProps=} filters - Optional filters to narrow down the list. + * @param {FindConfig=} config - Optional configuration. + * @param {Context=} sharedContext - Optional shared context. + * @returns {Promise} A promise that resolves to an array of PriceSetDTOs. + */ list( filters?: FilterablePriceSetProps, config?: FindConfig, sharedContext?: Context ): Promise + /** + * List priceSets and provide the total count. + * @param filters - Optional filters for listing. + * @param config - Optional configuration. + * @param sharedContext - Optional shared context. + * @returns A promise resolving to an array of PriceSetDTOs and a count. + */ listAndCount( filters?: FilterablePriceSetProps, config?: FindConfig, sharedContext?: Context ): Promise<[PriceSetDTO[], number]> + /** + * Create a new priceSet. + * @param data - Data for creating a priceSet. + * @param sharedContext - Optional shared context. + * @returns A promise resolving to the created PriceSetDTO. + */ + create(data: CreatePriceSetDTO, sharedContext?: Context): Promise + + /** + * Create multiple new priceSets. + * @param data - Array of data for creating priceSets. + * @param sharedContext - Optional shared context. + * @returns A promise resolving to an array of created PriceSetDTOs. + */ create( data: CreatePriceSetDTO[], sharedContext?: Context ): Promise + /** + * Update existing priceSets. + * @param data - Array of data for updating priceSets. + * @param sharedContext - Optional shared context. + * @returns A promise resolving to an array of updated PriceSetDTOs. + */ update( data: UpdatePriceSetDTO[], sharedContext?: Context ): Promise + /** + * Remove rules from priceSet. + * @param data - Array of data for removing priceSet rules. + * @param sharedContext - Optional shared context. + * @returns A promise that resolves when rules are successfully removed. + */ + removeRules( + data: RemovePriceSetRulesDTO[], + sharedContext?: Context + ): Promise + + /** + * Delete priceSets by their IDs. + * + * @param ids - An array of IDs for priceSets to delete. + * @param sharedContext - Optional shared context. + * @returns A promise that resolves when the price sets are successfully deleted. + */ delete(ids: string[], sharedContext?: Context): Promise + /** + * Add prices to a price set. + * + * @param data - Data for adding prices to a price set. + * @param sharedContext - Optional shared context. + * @returns A promise resolving to the updated PriceSetDTO. + */ + addPrices(data: AddPricesDTO, sharedContext?: Context): Promise + + /** + * Add prices to multiple price sets. + * + * @param data - An array of data for adding prices to price sets. + * @param sharedContext - Optional shared context. + * @returns A promise resolving to an array of updated PriceSetDTOs. + */ + addPrices( + data: AddPricesDTO[], + sharedContext?: Context + ): Promise + + /** + * Add rules to a price set. + * + * @param data - Data for adding rules to a price set. + * @param sharedContext - Optional shared context. + * @returns A promise resolving to the updated PriceSetDTO. + */ + addRules(data: AddRulesDTO, sharedContext?: Context): Promise + + /** + * Add rules to multiple price sets. + * + * @param data - An array of data for adding rules to price sets. + * @param sharedContext - Optional shared context. + * @returns A promise resolving to an array of updated PriceSetDTOs. + */ + addRules(data: AddRulesDTO[], sharedContext?: Context): Promise + + /** + * Retrieves a money amount by its ID. + * + * @param {string} id - The ID of the money amount to retrieve. + * @param {FindConfig=} config - Optional configuration for the retrieval operation. + * @param sharedContext - Optional shared context. + * @returns {Promise} A promise that resolves to a MoneyAmountDTO. + */ retrieveMoneyAmount( id: string, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists money amounts based on optional filters and configuration. + * + * @param {FilterableMoneyAmountProps=} filters - Optional filters to narrow down the list. + * @param {FindConfig=} config - Optional configuration for the listing operation. + * @param sharedContext - Optional shared context. + * @returns {Promise} A promise that resolves to an array of MoneyAmountDTOs. + */ listMoneyAmounts( filters?: FilterableMoneyAmountProps, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists money amounts based on optional filters and configuration and provides the total count. + * + * @param {FilterableMoneyAmountProps=} filters - Optional filters to narrow down the list. + * @param {FindConfig=} config - Optional configuration for the listing operation. + * @param sharedContext - Optional shared context. + * @returns {Promise<[MoneyAmountDTO[], number]>} A promise that resolves to an array of MoneyAmountDTOs and the total count. + */ listAndCountMoneyAmounts( filters?: FilterableMoneyAmountProps, config?: FindConfig, sharedContext?: Context ): Promise<[MoneyAmountDTO[], number]> + /** + * Creates new money amounts based on the provided data. + * + * @param {CreateMoneyAmountDTO[]} data - An array of data objects for creating money amounts. + * @param sharedContext - Optional shared context. + * @returns {Promise} A promise that resolves to an array of created MoneyAmountDTOs. + */ createMoneyAmounts( data: CreateMoneyAmountDTO[], sharedContext?: Context ): Promise + /** + * Updates existing money amounts based on the provided data. + * + * @param {UpdateMoneyAmountDTO[]} data - An array of data objects for updating money amounts. + * @param sharedContext - Optional shared context. + * @returns {Promise} A promise that resolves to an array of updated MoneyAmountDTOs. + */ updateMoneyAmounts( data: UpdateMoneyAmountDTO[], sharedContext?: Context ): Promise + /** + * Deletes money amounts by their IDs. + * + * @param {string[]} ids - An array of IDs for money amounts to delete. + * @param sharedContext - Optional shared context. + * @returns {Promise} A promise that resolves when the money amounts are successfully deleted. + */ deleteMoneyAmounts(ids: string[], sharedContext?: Context): Promise + /** + * Retrieves a currency by its code based on the provided configuration. + * + * @param {string} code - The code of the currency to retrieve. + * @param {FindConfig} [config={}] - Optional configuration for retrieving the currency. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to the retrieved currency object. + */ retrieveCurrency( code: string, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists currencies based on the provided filters and configuration. + * + * @param {PricingTypes.FilterableCurrencyProps} [filters={}] - Optional filters to apply when listing currencies. + * @param {FindConfig} [config={}] - Optional configuration for listing currencies. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array containing the list of currency objects. + */ listCurrencies( filters?: FilterableCurrencyProps, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists and counts currencies based on the provided filters and configuration. + * + * @param {PricingTypes.FilterableCurrencyProps} [filters={}] - Optional filters to apply when listing currencies. + * @param {FindConfig} [config={}] - Optional configuration for listing currencies. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise<[PricingTypes.CurrencyDTO[], number]>} A Promise that resolves to an array containing the list of currency objects and the total count. + */ listAndCountCurrencies( filters?: FilterableCurrencyProps, config?: FindConfig, sharedContext?: Context ): Promise<[CurrencyDTO[], number]> + /** + * Creates new currencies based on the provided data. + * + * @param {PricingTypes.CreateCurrencyDTO[]} data - An array of objects containing the data to create new currencies. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array of newly created currency objects. + */ createCurrencies( data: CreateCurrencyDTO[], sharedContext?: Context ): Promise + /** + * Updates currencies with the provided data. + * + * @param {PricingTypes.UpdateCurrencyDTO[]} data - An array of objects containing the data to update the currencies. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array of updated currency objects. + */ updateCurrencies( data: UpdateCurrencyDTO[], sharedContext?: Context ): Promise + /** + * Deletes currencies with the specified codes. + * + * @param {string[]} currencyCodes - An array of string codes representing the currencies to delete. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves once the currencies are deleted. + */ deleteCurrencies( currencyCodes: string[], sharedContext?: Context ): Promise + + /** + * Retrieves a rule type by its ID based on the provided configuration. + * + * @param {string} id - The ID of the rule type to retrieve. + * @param {FindConfig} [config={}] - Optional configuration for retrieving the rule type. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to the retrieved rule type object. + */ retrieveRuleType( code: string, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists rule types based on the provided filters and configuration. + * + * @param {PricingTypes.FilterableRuleTypeProps} [filters={}] - Optional filters to apply when listing rule types. + * @param {FindConfig} [config={}] - Optional configuration for listing rule types. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array containing the list of rule type objects. + */ listRuleTypes( filters?: FilterableRuleTypeProps, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists and counts rule types based on the provided filters and configuration. + * + * @param {PricingTypes.FilterableRuleTypeProps} [filters={}] - Optional filters to apply when listing rule types. + * @param {FindConfig} [config={}] - Optional configuration for listing rule types. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise<[PricingTypes.RuleTypeDTO[], number]>} A Promise that resolves to an array containing the list of rule type objects and the total count. + */ listAndCountRuleTypes( filters?: FilterableRuleTypeProps, config?: FindConfig, sharedContext?: Context ): Promise<[RuleTypeDTO[], number]> + /** + * Creates new rule types based on the provided data. + * + * @param {PricingTypes.CreateRuleTypeDTO[]} data - An array of objects containing the data to create new rule types. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array of newly created rule type objects. + */ createRuleTypes( data: CreateRuleTypeDTO[], sharedContext?: Context ): Promise + /** + * Updates rule types with the provided data. + * + * @param {PricingTypes.UpdateRuleTypeDTO[]} data - An array of objects containing the data to update the rule types. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array of updated rule type objects. + */ updateRuleTypes( data: UpdateRuleTypeDTO[], sharedContext?: Context ): Promise + /** + * Deletes rule types with the specified IDs. + * + * @param {string[]} ruleTypeIds - An array of string IDs representing the rule types to delete. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves once the rule types are deleted. + */ deleteRuleTypes(ruleTypeIds: string[], sharedContext?: Context): Promise + /** + * Retrieves a price set money amount rule by its ID based on the provided configuration. + * + * @param {string} id - The ID of the price set money amount rule to retrieve. + * @param {FindConfig} [config={}] - Optional configuration for retrieving the price set money amount rule. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to the retrieved price set money amount rule object. + */ retrievePriceSetMoneyAmountRules( id: string, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists price set money amount rules based on the provided filters and configuration. + * + * @param {PricingTypes.FilterablePriceSetMoneyAmountRulesProps} [filters={}] - Optional filters to apply when listing price set money amount rules. + * @param {FindConfig} [config={}] - Optional configuration for listing price set money amount rules. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array containing the list of price set money amount rule objects. + */ listPriceSetMoneyAmountRules( filters?: FilterablePriceSetMoneyAmountRulesProps, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists and counts price set money amount rules based on the provided filters and configuration. + * + * @param {PricingTypes.FilterablePriceSetMoneyAmountRulesProps} [filters={}] - Optional filters to apply when listing price set money amount rules. + * @param {FindConfig} [config={}] - Optional configuration for listing price set money amount rules. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise<[PricingTypes.PriceSetMoneyAmountRulesDTO[], number]>} A Promise that resolves to an array containing the list of price set money amount rule objects and the total count. + */ listAndCountPriceSetMoneyAmountRules( filters?: FilterablePriceSetMoneyAmountRulesProps, config?: FindConfig, sharedContext?: Context ): Promise<[PriceSetMoneyAmountRulesDTO[], number]> + /** + * Creates new price set money amount rules based on the provided data. + * + * @param {PricingTypes.CreatePriceSetMoneyAmountRulesDTO[]} data - An array of objects containing the data to create new price set money amount rules. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array of newly created price set money amount rule objects. + */ createPriceSetMoneyAmountRules( data: CreatePriceSetMoneyAmountRulesDTO[], sharedContext?: Context ): Promise + /** + * Updates price set money amount rules with the provided data. + * + * @param {PricingTypes.UpdatePriceSetMoneyAmountRulesDTO[]} data - An array of objects containing the data to update the price set money amount rules. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array of updated price set money amount rule objects. + */ updatePriceSetMoneyAmountRules( data: UpdatePriceSetMoneyAmountRulesDTO[], sharedContext?: Context ): Promise + /** + * Deletes price set money amount rules with the specified IDs. + * + * @param {string[]} ids - An array of string IDs representing the price set money amount rules to delete. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves once the price set money amount rules are deleted. + */ deletePriceSetMoneyAmountRules( ids: string[], sharedContext?: Context ): Promise + /** + * Retrieves a price rule by its ID + * + * @param {string} id - The ID of the price rule to retrieve. + * @param {FindConfig} [config={}] - Optional configuration for retrieving the price rule. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to the retrieved price rule object. + */ retrievePriceRule( id: string, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists price rules based on the provided filters and configuration. + * + * @param {PricingTypes.FilterablePriceRuleProps} [filters={}] - Optional filters to apply when listing price rules. + * @param {FindConfig} [config={}] - Optional configuration for listing price rules. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array containing the list of price rule objects. + */ listPriceRules( filters?: FilterablePriceRuleProps, config?: FindConfig, sharedContext?: Context ): Promise + /** + * Lists and counts price rules based on the provided filters and configuration. + * + * @param {PricingTypes.FilterablePriceRuleProps} [filters={}] - Optional filters to apply when listing price rules. + * @param {FindConfig} [config={}] - Optional configuration for listing price rules. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise<[PricingTypes.PriceRuleDTO[], number]>} A Promise that resolves to an array containing the list of price rule objects and the total count. + */ listAndCountPriceRules( filters?: FilterablePriceRuleProps, config?: FindConfig, sharedContext?: Context ): Promise<[PriceRuleDTO[], number]> + /** + * Creates new price rules based on the provided data. + * + * @param {PricingTypes.CreatePriceRuleDTO[]} data - An array of objects containing the data to create new price rules. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array of newly created price rule objects. + */ createPriceRules( data: CreatePriceRuleDTO[], sharedContext?: Context ): Promise + /** + * Updates price rules with the provided data. + * + * @param {PricingTypes.UpdatePriceRuleDTO[]} data - An array of objects containing the data to update the price rules. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves to an array of updated price rule objects. + */ updatePriceRules( data: UpdatePriceRuleDTO[], sharedContext?: Context ): Promise + /** + * Deletes price rules with the specified IDs. + * + * @param {string[]} priceRuleIds - An array of string IDs representing the price rules to delete. + * @param {Context} [sharedContext={}] - An optional shared MedusaContext object. + * @returns {Promise} A Promise that resolves once the price rules are deleted. + */ deletePriceRules( priceRuleIds: string[], sharedContext?: Context