diff --git a/integration-tests/http/__tests__/product/admin/product.spec.ts b/integration-tests/http/__tests__/product/admin/product.spec.ts index a294d5955e..bd479932cd 100644 --- a/integration-tests/http/__tests__/product/admin/product.spec.ts +++ b/integration-tests/http/__tests__/product/admin/product.spec.ts @@ -1227,6 +1227,64 @@ medusaIntegrationTestRunner({ ) }) + it("creates a product variant with price rules", async () => { + await api.post( + `/admin/pricing/rule-types`, + { + name: "Region", + rule_attribute: "region_id", + default_priority: 1, + }, + adminHeaders + ) + + const response = await api.post( + "/admin/products", + { + title: "Test create", + variants: [ + { + title: "Price with rules", + prices: [ + { + currency_code: "usd", + amount: 100, + rules: { region_id: "eur" }, + }, + ], + }, + ], + }, + adminHeaders + ) + + const priceIdSelector = /^price_*/ + + expect(response.status).toEqual(200) + expect(response.data.product).toEqual( + expect.objectContaining({ + id: expect.stringMatching(/^prod_*/), + variants: expect.arrayContaining([ + expect.objectContaining({ + id: expect.stringMatching(/^variant_*/), + title: "Price with rules", + prices: expect.arrayContaining([ + expect.objectContaining({ + id: expect.stringMatching(priceIdSelector), + currency_code: "usd", + amount: 100, + created_at: expect.any(String), + updated_at: expect.any(String), + variant_id: expect.stringMatching(/^variant_*/), + rules: { region_id: "eur" }, + }), + ]), + }), + ]), + }) + ) + }) + it("creates a product that is not discountable", async () => { const payload = { title: "Test", diff --git a/integration-tests/http/__tests__/product/admin/variant.spec.ts b/integration-tests/http/__tests__/product/admin/variant.spec.ts index 24d7023b6e..bbd73863de 100644 --- a/integration-tests/http/__tests__/product/admin/variant.spec.ts +++ b/integration-tests/http/__tests__/product/admin/variant.spec.ts @@ -96,11 +96,20 @@ medusaIntegrationTestRunner({ describe("updates a variant's default prices (ignores prices associated with a Price List)", () => { it("successfully updates a variant's default prices by changing an existing price (currency_code)", async () => { + await api.post( + `/admin/pricing/rule-types`, + { name: "Region", rule_attribute: "region_id", default_priority: 1 }, + adminHeaders + ) + const data = { prices: [ { currency_code: "usd", amount: 1500, + rules: { + region_id: "na", + }, }, ], } @@ -115,6 +124,7 @@ medusaIntegrationTestRunner({ baseProduct.variants[0].prices.find((p) => p.currency_code === "usd") .amount ).toEqual(100) + expect(response.status).toEqual(200) expect(response.data).toEqual({ product: expect.objectContaining({ @@ -126,6 +136,7 @@ medusaIntegrationTestRunner({ expect.objectContaining({ amount: 1500, currency_code: "usd", + rules: { region_id: "na" }, }), ]), }), diff --git a/packages/core/core-flows/src/pricing/steps/update-price-sets.ts b/packages/core/core-flows/src/pricing/steps/update-price-sets.ts index 9344ac00af..ba7ab14814 100644 --- a/packages/core/core-flows/src/pricing/steps/update-price-sets.ts +++ b/packages/core/core-flows/src/pricing/steps/update-price-sets.ts @@ -43,9 +43,10 @@ export const updatePriceSetsStep = createStep( return new StepResponse([], null) } - const { selects, relations } = getSelectsAndRelationsFromObjectArray([ - data.update, - ]) + const { selects, relations } = getSelectsAndRelationsFromObjectArray( + [data.update], + { objectFields: ["rules"] } + ) const dataBeforeUpdate = await pricingModule.listPriceSets(data.selector, { select: selects, diff --git a/packages/core/types/src/pricing/service.ts b/packages/core/types/src/pricing/service.ts index 37fec68cab..ef6a2edeae 100644 --- a/packages/core/types/src/pricing/service.ts +++ b/packages/core/types/src/pricing/service.ts @@ -5,7 +5,6 @@ import { Context } from "../shared-context" import { AddPriceListPricesDTO, AddPricesDTO, - AddRulesDTO, CalculatedPriceSet, CreatePriceListDTO, CreatePriceRuleDTO, @@ -25,7 +24,6 @@ import { PricingContext, PricingFilters, RemovePriceListRulesDTO, - RemovePriceSetRulesDTO, RuleTypeDTO, SetPriceListRulesDTO, UpdatePriceListDTO, @@ -481,26 +479,6 @@ export interface IPricingModuleService extends IModuleService { sharedContext?: Context ): Promise - /** - * This method remove rules from a price set. - * - * @param {RemovePriceSetRulesDTO[]} data - The rules to remove per price set. - * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} Resolves when rules are successfully removed. - * - * @example - * await pricingModuleService.removeRules([ - * { - * id: "pset_123", - * rules: ["region_id"], - * }, - * ]) - */ - removeRules( - data: RemovePriceSetRulesDTO[], - sharedContext?: Context - ): Promise - /** * This method deletes price sets by their IDs. * @@ -615,54 +593,6 @@ export interface IPricingModuleService extends IModuleService { sharedContext?: Context ): Promise - /** - * This method adds rules to a price set. - * - * @param {AddRulesDTO} data - The data defining the price set to add the rules to, along with the rules to add. - * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} The price set that the rules were added to. - * - * @example - * const priceSet = await pricingModuleService.addRules({ - * priceSetId: "pset_123", - * rules: [ - * { - * attribute: "region_id", - * }, - * ], - * }) - */ - addRules(data: AddRulesDTO, sharedContext?: Context): Promise - - /** - * This method adds rules to multiple price sets. - * - * @param {AddRulesDTO[]} data - The data defining the rules to add per price set. - * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} The list of the price sets that the rules were added to. - * - * @example - * const priceSets = await pricingModuleService.addRules([ - * { - * priceSetId: "pset_123", - * rules: [ - * { - * attribute: "region_id", - * }, - * ], - * }, - * { - * priceSetId: "pset_321", - * rules: [ - * { - * attribute: "customer_group_id", - * }, - * ], - * }, - * ]) - */ - addRules(data: AddRulesDTO[], sharedContext?: Context): Promise - /** * This method is used to retrieve a rule type by its ID and and optionally based on the provided configurations. * diff --git a/packages/medusa/src/api/admin/products/[id]/variants/[variant_id]/route.ts b/packages/medusa/src/api/admin/products/[id]/variants/[variant_id]/route.ts index cacc145204..06791cb42b 100644 --- a/packages/medusa/src/api/admin/products/[id]/variants/[variant_id]/route.ts +++ b/packages/medusa/src/api/admin/products/[id]/variants/[variant_id]/route.ts @@ -1,20 +1,20 @@ -import { - AuthenticatedMedusaRequest, - MedusaResponse, -} from "../../../../../../types/routing" import { deleteProductVariantsWorkflow, updateProductVariantsWorkflow, } from "@medusajs/core-flows" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../../types/routing" +import { HttpTypes } from "@medusajs/types" +import { refetchEntity } from "../../../../../utils/refetch-entity" import { remapKeysForProduct, remapKeysForVariant, remapProductResponse, remapVariantResponse, } from "../../../helpers" -import { HttpTypes } from "@medusajs/types" -import { refetchEntity } from "../../../../../utils/refetch-entity" export const GET = async ( req: AuthenticatedMedusaRequest, @@ -54,6 +54,7 @@ export const POST = async ( req.scope, remapKeysForProduct(req.remoteQueryConfig.fields ?? []) ) + res.status(200).json({ product: remapProductResponse(product) }) } diff --git a/packages/medusa/src/api/admin/products/helpers.ts b/packages/medusa/src/api/admin/products/helpers.ts index 52f973542a..98ee6aae8e 100644 --- a/packages/medusa/src/api/admin/products/helpers.ts +++ b/packages/medusa/src/api/admin/products/helpers.ts @@ -3,6 +3,7 @@ import { BatchMethodResponse, HttpTypes, MedusaContainer, + PriceDTO, ProductDTO, ProductVariantDTO, } from "@medusajs/types" @@ -25,6 +26,7 @@ export const remapKeysForProduct = (selectFields: string[]) => { const productFields = selectFields.filter( (fieldName: string) => !isPricing(fieldName) ) + const pricingFields = selectFields .filter((fieldName: string) => isPricing(fieldName)) .map((fieldName: string) => @@ -38,6 +40,7 @@ export const remapKeysForVariant = (selectFields: string[]) => { const variantFields = selectFields.filter( (fieldName: string) => !isPricing(fieldName) ) + const pricingFields = selectFields .filter((fieldName: string) => isPricing(fieldName)) .map((fieldName: string) => @@ -75,14 +78,30 @@ export const remapVariantResponse = ( variant_id: variant.id, created_at: price.created_at, updated_at: price.updated_at, + rules: buildRules(price), })), } delete (resp as any).price_set + // TODO: Remove any once all typings are cleaned up return resp as any } +export const buildRules = (price: PriceDTO) => { + const rules: Record = {} + + for (const priceRule of price.price_rules || []) { + const ruleAttribute = priceRule.rule_type?.rule_attribute + + if (ruleAttribute) { + rules[ruleAttribute] = priceRule.value + } + } + + return rules +} + export const refetchVariant = async ( variantId: string, scope: MedusaContainer, diff --git a/packages/medusa/src/api/admin/products/query-config.ts b/packages/medusa/src/api/admin/products/query-config.ts index 47ad0120d7..6155530199 100644 --- a/packages/medusa/src/api/admin/products/query-config.ts +++ b/packages/medusa/src/api/admin/products/query-config.ts @@ -22,6 +22,8 @@ export const defaultAdminProductsVariantFields = [ "upc", "barcode", "*prices", + "prices.price_rules.value", + "prices.price_rules.rule_type.rule_attribute", "*options", ] @@ -82,6 +84,8 @@ export const defaultAdminProductFields = [ "*images", "*variants", "*variants.prices", + "variants.prices.price_rules.value", + "variants.prices.price_rules.rule_type.rule_attribute", "*variants.options", "*sales_channels", ] diff --git a/packages/medusa/src/api/admin/products/validators.ts b/packages/medusa/src/api/admin/products/validators.ts index 9a27adb798..33c2f4d3ee 100644 --- a/packages/medusa/src/api/admin/products/validators.ts +++ b/packages/medusa/src/api/admin/products/validators.ts @@ -106,6 +106,7 @@ export const AdminCreateVariantPrice = z.object({ amount: z.number(), min_quantity: z.number().nullish(), max_quantity: z.number().nullish(), + rules: z.record(z.string(), z.string()).optional(), }) // TODO: Add support for rules @@ -118,6 +119,7 @@ export const AdminUpdateVariantPrice = z.object({ amount: z.number().optional(), min_quantity: z.number().nullish(), max_quantity: z.number().nullish(), + rules: z.record(z.string(), z.string()).optional(), }) export type AdminCreateProductTypeType = z.infer diff --git a/packages/modules/pricing/integration-tests/__tests__/services/pricing-module/price-list.spec.ts b/packages/modules/pricing/integration-tests/__tests__/services/pricing-module/price-list.spec.ts index 2025709256..860479210d 100644 --- a/packages/modules/pricing/integration-tests/__tests__/services/pricing-module/price-list.spec.ts +++ b/packages/modules/pricing/integration-tests/__tests__/services/pricing-module/price-list.spec.ts @@ -1,16 +1,16 @@ import { IPricingModuleService } from "@medusajs/types" -import { - MockEventBusService, - moduleIntegrationTestRunner, -} from "medusa-test-utils" -import { createPriceLists } from "../../../__fixtures__/price-list" -import { createPriceSets } from "../../../__fixtures__/price-set" import { CommonEvents, composeMessage, Modules, PricingEvents, } from "@medusajs/utils" +import { + MockEventBusService, + moduleIntegrationTestRunner, +} from "medusa-test-utils" +import { createPriceLists } from "../../../__fixtures__/price-list" +import { createPriceSets } from "../../../__fixtures__/price-set" jest.setTimeout(30000) @@ -745,41 +745,6 @@ moduleIntegrationTestRunner({ ) }) - it("should fail to add a price with non-existing rule-types in the price-set to a priceList", async () => { - await service.createRuleTypes([ - { - name: "twitter_handle", - rule_attribute: "twitter_handle", - }, - ]) - - let error - try { - await service.addPriceListPrices([ - { - price_list_id: "price-list-1", - prices: [ - { - amount: 123, - currency_code: "EUR", - price_set_id: "price-set-1", - rules: { - twitter_handle: "owjuhl", - }, - }, - ], - }, - ]) - } catch (err) { - error = err - } - - expect(error.message).toEqual( - "" + - `Invalid rule type configuration: Price set rules doesn't exist for rule_attribute "twitter_handle" in price set price-set-1` - ) - }) - it("should add a price with rules to a priceList successfully", async () => { await service.createRuleTypes([ { @@ -788,15 +753,6 @@ moduleIntegrationTestRunner({ }, ]) - const r = await service.addRules([ - { - priceSetId: "price-set-1", - rules: [{ attribute: "region_id" }], - }, - ]) - - jest.clearAllMocks() - await service.addPriceListPrices([ { price_list_id: "price-list-1", @@ -886,14 +842,7 @@ moduleIntegrationTestRunner({ describe("updatePriceListPrices", () => { it("should update a price to a priceList successfully", async () => { - const [priceSet] = await service.createPriceSets([ - { - rules: [ - { rule_attribute: "region_id" }, - { rule_attribute: "customer_group_id" }, - ], - }, - ]) + const [priceSet] = await service.createPriceSets([{}]) await service.addPriceListPrices([ { @@ -907,7 +856,7 @@ moduleIntegrationTestRunner({ rules: { region_id: "test", }, - } as any, + }, ], }, ]) @@ -981,52 +930,6 @@ moduleIntegrationTestRunner({ }) ) }) - - it("should fail to add a price with non-existing rule-types in the price-set to a priceList", async () => { - await service.createRuleTypes([ - { name: "twitter_handle", rule_attribute: "twitter_handle" }, - { name: "region_id", rule_attribute: "region_id" }, - ]) - - const [priceSet] = await service.createPriceSets([ - { rules: [{ rule_attribute: "region_id" }] }, - ]) - - await service.addPriceListPrices([ - { - price_list_id: "price-list-1", - prices: [ - { - id: "test-price-id", - amount: 123, - currency_code: "EUR", - price_set_id: priceSet.id, - rules: { region_id: "test" }, - } as any, - ], - }, - ]) - - const error = await service - .updatePriceListPrices([ - { - price_list_id: "price-list-1", - prices: [ - { - id: "test-price-id", - amount: 123, - price_set_id: priceSet.id, - rules: { twitter_handle: "owjuhl" }, - }, - ], - }, - ]) - .catch((e) => e) - - expect(error.message).toEqual( - `Invalid rule type configuration: Price set rules doesn't exist for rule_attribute "twitter_handle" in price set ${priceSet.id}` - ) - }) }) describe("removePrices", () => { diff --git a/packages/modules/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts b/packages/modules/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts index 323a939ec6..8230a3a4ef 100644 --- a/packages/modules/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts +++ b/packages/modules/pricing/integration-tests/__tests__/services/pricing-module/price-set.spec.ts @@ -3,6 +3,12 @@ import { CreatePriceSetRuleTypeDTO, IPricingModuleService, } from "@medusajs/types" +import { + CommonEvents, + composeMessage, + Modules, + PricingEvents, +} from "@medusajs/utils" import { SqlEntityManager } from "@mikro-orm/postgresql" import { MockEventBusService, @@ -10,12 +16,6 @@ import { } from "medusa-test-utils" import { PriceSetRuleType } from "../../../../src/models" import { seedPriceData } from "../../../__fixtures__/seed-price-data" -import { - CommonEvents, - composeMessage, - Modules, - PricingEvents, -} from "@medusajs/utils" jest.setTimeout(30000) @@ -346,24 +346,6 @@ moduleIntegrationTestRunner({ }) describe("create", () => { - it("should throw an error when creating a price set with rule attributes that don't exist", async () => { - let error - - try { - await service.createPriceSets([ - { - rules: [{ rule_attribute: "does-not-exist" }], - }, - ]) - } catch (e) { - error = e - } - - 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 @@ -386,32 +368,13 @@ moduleIntegrationTestRunner({ 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.createPriceSets([ - { - rules: [{ rule_attribute: "region_id" }], - }, - ]) - - expect(priceSet).toEqual( - expect.objectContaining({ - rule_types: [ - expect.objectContaining({ - rule_attribute: "region_id", - }), - ], - }) + "Rule types don't exist for prices with rule attribute: city" ) }) it("should create a price set with rule types and money amounts", async () => { const [priceSet] = await service.createPriceSets([ { - rules: [{ rule_attribute: "region_id" }], prices: [ { amount: 100, @@ -426,11 +389,6 @@ moduleIntegrationTestRunner({ expect(priceSet).toEqual( expect.objectContaining({ - rule_types: [ - expect.objectContaining({ - rule_attribute: "region_id", - }), - ], prices: [ expect.objectContaining({ amount: 100, @@ -477,10 +435,9 @@ moduleIntegrationTestRunner({ ) }) - it("should create a price set with money amounts with and without rules", async () => { + it("should create a price set with prices", async () => { const [priceSet] = await service.createPriceSets([ { - rules: [{ rule_attribute: "region_id" }], prices: [ { amount: 100, @@ -499,11 +456,6 @@ moduleIntegrationTestRunner({ expect(priceSet).toEqual( expect.objectContaining({ - rule_types: [ - expect.objectContaining({ - rule_attribute: "region_id", - }), - ], prices: expect.arrayContaining([ expect.objectContaining({ amount: 100, @@ -518,44 +470,6 @@ moduleIntegrationTestRunner({ ) }) - it("should create a price set with rule types and money amounts", async () => { - const [priceSet] = await service.createPriceSets([ - { - 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", - }), - ], - prices: [ - expect.objectContaining({ - amount: 100, - currency_code: "USD", - }), - ], - price_rules: [ - expect.objectContaining({ - value: "10", - }), - ], - }) - ) - }) - it("should create a priceSet successfully", async () => { await service.createPriceSets([ { @@ -575,92 +489,6 @@ moduleIntegrationTestRunner({ }) }) - describe("removeRules", () => { - it("should delete prices for a price set associated to the rules that are deleted", async () => { - const createdPriceSet = await service.createPriceSets([ - { - 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.listPriceSets( - { id: [createdPriceSet[0].id] }, - { relations: ["rule_types", "prices", "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", - }), - }, - ], - prices: [ - 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.listPriceSets( - { id: [createdPriceSet[0].id] }, - { relations: ["rule_types", "prices", "price_rules"] } - ) - expect(priceSet).toEqual([ - { - id: expect.any(String), - price_rules: [], - prices: [], - rule_types: [], - created_at: expect.any(Date), - updated_at: expect.any(Date), - deleted_at: null, - }, - ]) - }) - }) - describe("addPrices", () => { it("should add prices to existing price set", async () => { await service.addPrices([ @@ -784,71 +612,6 @@ moduleIntegrationTestRunner({ 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.listPriceSets( - { 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/modules/pricing/src/services/pricing-module.ts b/packages/modules/pricing/src/services/pricing-module.ts index e5a70b9fb5..6bd22451b8 100644 --- a/packages/modules/pricing/src/services/pricing-module.ts +++ b/packages/modules/pricing/src/services/pricing-module.ts @@ -43,16 +43,15 @@ import { PriceListRuleValue, PriceRule, PriceSet, - PriceSetRuleType, RuleType, } from "@models" import { PriceListService, RuleTypeService } from "@services" +import { ServiceTypes } from "@types" import { eventBuilders, validatePriceListDates } from "@utils" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { PriceListIdPrefix } from "../models/price-list" import { PriceSetIdPrefix } from "../models/price-set" -import { ServiceTypes } from "@types" type InjectedDependencies = { baseRepository: DAL.RepositoryService @@ -74,7 +73,6 @@ const generateMethodForModels = { PriceListRuleValue, PriceRule, Price, - PriceSetRuleType, RuleType, } @@ -102,7 +100,6 @@ export default class PricingModuleService protected readonly ruleTypeService_: RuleTypeService protected readonly priceSetService_: ModulesSdkTypes.IMedusaInternalService protected readonly priceRuleService_: ModulesSdkTypes.IMedusaInternalService - protected readonly priceSetRuleTypeService_: ModulesSdkTypes.IMedusaInternalService protected readonly priceService_: ModulesSdkTypes.IMedusaInternalService protected readonly priceListService_: PriceListService protected readonly priceListRuleService_: ModulesSdkTypes.IMedusaInternalService @@ -115,7 +112,6 @@ export default class PricingModuleService ruleTypeService, priceSetService, priceRuleService, - priceSetRuleTypeService, priceService, priceListService, priceListRuleService, @@ -132,7 +128,6 @@ export default class PricingModuleService this.priceSetService_ = priceSetService this.ruleTypeService_ = ruleTypeService this.priceRuleService_ = priceRuleService - this.priceSetRuleTypeService_ = priceSetRuleTypeService this.priceService_ = priceService this.priceListService_ = priceListService this.priceListRuleService_ = priceListRuleService @@ -497,9 +492,7 @@ export default class PricingModuleService const { entities: upsertedPrices } = await this.priceService_.upsertWithReplace( prices, - { - relations: ["price_rules"], - }, + { relations: ["price_rules"] }, sharedContext ) @@ -527,37 +520,6 @@ export default class PricingModuleService 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) - - const dbPriceSets = await this.listPriceSets( - { id: priceSets.map(({ id }) => id) }, - { relations: ["rule_types"] } - ) - - const orderedPriceSets = priceSets.map((priceSet) => { - return dbPriceSets.find((p) => p.id === priceSet.id)! - }) - - return Array.isArray(data) ? orderedPriceSets : orderedPriceSets[0] - } - async addPrices( data: AddPricesDTO, sharedContext?: Context @@ -591,50 +553,6 @@ export default class PricingModuleService return Array.isArray(data) ? orderedPriceSets : orderedPriceSets[0] } - @InjectTransactionManager("baseRepository_") - async removeRules( - data: PricingTypes.RemovePriceSetRulesDTO[], - @MedusaContext() sharedContext: Context = {} - ): Promise { - const priceSets = await this.priceSetService_.list( - { id: data.map((d) => d.id) }, - {}, - sharedContext - ) - - const priceSetIds = priceSets.map((ps) => ps.id) - const ruleTypes = await this.ruleTypeService_.list( - { - rule_attribute: data.map((d) => d.rules || []).flat(), - }, - { take: null }, - sharedContext - ) - - const ruleTypeIds = ruleTypes.map((rt) => rt.id) - const priceSetRuleTypes = await this.priceSetRuleTypeService_.list( - { price_set_id: priceSetIds, rule_type_id: ruleTypeIds }, - { take: null }, - sharedContext - ) - - const priceRules = await this.priceRuleService_.list( - { price_set_id: priceSetIds, rule_type_id: ruleTypeIds }, - { select: ["price"], take: null }, - sharedContext - ) - - await this.priceSetRuleTypeService_.delete( - priceSetRuleTypes.map((psrt) => psrt.id), - sharedContext - ) - - await this.priceService_.delete( - priceRules.map((pr) => pr.price.id), - sharedContext - ) - } - @InjectManager("baseRepository_") @EmitEvents() // @ts-ignore @@ -723,7 +641,12 @@ export default class PricingModuleService const input = Array.isArray(data) ? data : [data] const ruleAttributes = deduplicate( - data.map((d) => d.rules?.map((r) => r.rule_attribute) ?? []).flat() + data + .map( + (d) => + d.prices?.map((ma) => Object.keys(ma?.rules ?? {})).flat() ?? [] + ) + .flat() ) const ruleTypes = await this.ruleTypeService_.list( @@ -744,56 +667,27 @@ export default class PricingModuleService 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( + `Rule types don't exist for prices with rule attribute: ${invalidRuleAttributes.join( ", " )}` ) } - const ruleSetRuleTypeToCreateMap: Map = new Map() - const toCreate = input.map((inputData) => { const id = generateEntityId( (inputData as unknown as PriceSet).id, PriceSetIdPrefix ) - const { prices, rules = [], ...rest } = inputData + const { prices, ...rest } = inputData let pricesData: CreatePricesDTO[] = [] - rules.forEach((rule) => { - const priceSetRuleType = { - rule_type_id: ruleTypeMap.get(rule.rule_attribute).id, - price_set_id: id, - } as PriceSetRuleType - - ruleSetRuleTypeToCreateMap.set( - JSON.stringify(priceSetRuleType), - priceSetRuleType - ) - }) - if (inputData.prices) { pricesData = inputData.prices.map((price) => { let { rules: priceRules = {}, ...rest } = price const cleanRules = priceRules ? removeNullish(priceRules) : {} const numberOfRules = Object.keys(cleanRules).length - const rulesDataMap = new Map() Object.entries(priceRules).map(([attribute, value]) => { @@ -803,16 +697,6 @@ export default class PricingModuleService value, } rulesDataMap.set(JSON.stringify(rule), rule) - - const priceSetRuleType = { - rule_type_id: ruleTypeMap.get(attribute).id, - price_set_id: id, - } as PriceSetRuleType - - ruleSetRuleTypeToCreateMap.set( - JSON.stringify(priceSetRuleType), - priceSetRuleType - ) }) return { @@ -880,99 +764,9 @@ export default class PricingModuleService sharedContext, }) - if (ruleSetRuleTypeToCreateMap.size) { - await this.priceSetRuleTypeService_.create( - Array.from(ruleSetRuleTypeToCreateMap.values()), - sharedContext - ) - } - return createdPriceSets } - @InjectTransactionManager("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"] }, - sharedContext - ) - - 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((data) => data.rules.map((r) => r.attribute)) - .flat(), - }, - { take: null }, - 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((data) => { - for (const rule of data.rules) { - if (priceSetRuleTypeMap.get(data.priceSetId)!.has(rule.attribute)) { - continue - } - - priceSetRuleTypesCreate.push({ - rule_type_id: ruleTypeMap.get(rule.attribute)!.id, - price_set_id: priceSetMap.get(data.priceSetId)!.id, - }) - } - }) - - await this.priceSetRuleTypeService_.create( - priceSetRuleTypesCreate, - sharedContext - ) - - return priceSets - } - @InjectTransactionManager("baseRepository_") protected async addPrices_( input: AddPricesDTO[], @@ -1365,9 +1159,6 @@ export default class PricingModuleService const ruleTypeAttributes: string[] = [] const priceListIds: string[] = [] const priceIds: string[] = [] - const priceSetIds = data - .map((d) => d.prices.map((price) => price.price_set_id)) - .flat() for (const priceListData of data) { priceListIds.push(priceListData.price_list_id) @@ -1398,53 +1189,6 @@ export default class PricingModuleService ruleTypes.map((rt) => [rt.rule_attribute, rt]) ) - const priceSets = await this.listPriceSets( - { id: priceSetIds }, - { relations: ["rule_types"] }, - sharedContext - ) - - const priceSetRuleTypeMap: Map> = priceSets.reduce( - (acc, curr) => { - const priceSetRuleAttributeSet: Set = - acc.get(curr.id) || new Set() - - for (const rt of curr.rule_types ?? []) { - priceSetRuleAttributeSet.add(rt.rule_attribute) - } - - acc.set(curr.id, priceSetRuleAttributeSet) - - return acc - }, - new Map() - ) - - const ruleTypeErrors: string[] = [] - - for (const priceListData of data) { - for (const price of priceListData.prices) { - for (const ruleAttribute of Object.keys(price.rules ?? {})) { - if ( - !priceSetRuleTypeMap.get(price.price_set_id)?.has(ruleAttribute) - ) { - ruleTypeErrors.push( - `rule_attribute "${ruleAttribute}" in price set ${price.price_set_id}` - ) - } - } - } - } - - if (ruleTypeErrors.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Invalid rule type configuration: Price set rules doesn't exist for ${ruleTypeErrors.join( - ", " - )}` - ) - } - const priceLists = await this.listPriceLists( { id: priceListIds }, { take: null }, @@ -1532,52 +1276,6 @@ export default class PricingModuleService sharedContext ) - const priceSets = await this.listPriceSets( - { id: priceSetIds }, - { relations: ["rule_types"] }, - sharedContext - ) - - const priceSetRuleTypeMap: Map> = priceSets.reduce( - (acc, curr) => { - const priceSetRuleAttributeSet: Set = - acc.get(curr.id) || new Set() - - for (const rt of curr.rule_types ?? []) { - priceSetRuleAttributeSet.add(rt.rule_attribute) - } - - acc.set(curr.id, priceSetRuleAttributeSet) - return acc - }, - new Map() - ) - - const ruleTypeErrors: string[] = [] - - for (const priceListData of data) { - for (const price of priceListData.prices) { - for (const rule_attribute of Object.keys(price.rules ?? {})) { - if ( - !priceSetRuleTypeMap.get(price.price_set_id)?.has(rule_attribute) - ) { - ruleTypeErrors.push( - `rule_attribute "${rule_attribute}" in price set ${price.price_set_id}` - ) - } - } - } - } - - if (ruleTypeErrors.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Invalid rule type configuration: Price set rules doesn't exist for ${ruleTypeErrors.join( - ", " - )}` - ) - } - const ruleTypeMap: Map = new Map( ruleTypes.map((rt) => [rt.rule_attribute, rt]) )