From 137cc0ebf862ffe743dcb981da7c580e18d8f433 Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Tue, 20 Feb 2024 17:50:19 +0100 Subject: [PATCH] feat(tax): introduce tax override data models (#6422) **What** - Adds Tax rules to allow overrides of tax rates for certain products, product types or shipping options. **Punted to future PR** - Currently, the creation methods only support bulk operations. A later PR will include support for singular operations, too. - It should be possible to add products, types, and shipping options to a tax rate after creating it. Add, remove, update will come in a later PR. --- .../tax/integration-tests/__tests__/index.ts | 46 ++++++++++++++++ packages/tax/src/models/index.ts | 1 + packages/tax/src/models/tax-rate-rule.ts | 55 +++++++++++++++++++ .../tax/src/services/tax-module-service.ts | 48 ++++++++++++++-- packages/types/src/tax/common.ts | 26 +++++++++ packages/types/src/tax/mutations.ts | 8 +++ packages/types/src/tax/service.ts | 19 ++++++- 7 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 packages/tax/src/models/tax-rate-rule.ts diff --git a/packages/tax/integration-tests/__tests__/index.ts b/packages/tax/integration-tests/__tests__/index.ts index 7632b8b28b..fab2f16566 100644 --- a/packages/tax/integration-tests/__tests__/index.ts +++ b/packages/tax/integration-tests/__tests__/index.ts @@ -92,4 +92,50 @@ describe("TaxModuleService", function () { ]) ) }) + + it("should create a tax rate rule", async () => { + const [region] = await service.createTaxRegions([ + { + country_code: "US", + default_tax_rate: { + name: "Test Rate", + rate: 0.2, + }, + }, + ]) + + const rate = await service.create({ + tax_region_id: region.id, + name: "Shipping Rate", + rate: 8.23, + }) + + await service.createTaxRateRules([ + { + tax_rate_id: rate.id, + reference: "product", + reference_id: "prod_1234", + }, + ]) + + const listedRules = await service.listTaxRateRules( + {}, + { + relations: ["tax_rate"], + } + ) + expect(listedRules).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + reference: "product", + reference_id: "prod_1234", + tax_rate: expect.objectContaining({ + tax_region_id: region.id, + name: "Shipping Rate", + rate: 8.23, + }), + }), + ]) + ) + }) }) diff --git a/packages/tax/src/models/index.ts b/packages/tax/src/models/index.ts index 03c4263f3b..d741fc53ba 100644 --- a/packages/tax/src/models/index.ts +++ b/packages/tax/src/models/index.ts @@ -1,2 +1,3 @@ export { default as TaxRate } from "./tax-rate" export { default as TaxRegion } from "./tax-region" +export { default as TaxRateRule } from "./tax-rate-rule" diff --git a/packages/tax/src/models/tax-rate-rule.ts b/packages/tax/src/models/tax-rate-rule.ts new file mode 100644 index 0000000000..0069548e73 --- /dev/null +++ b/packages/tax/src/models/tax-rate-rule.ts @@ -0,0 +1,55 @@ +import { + Cascade, + Entity, + ManyToOne, + PrimaryKey, + PrimaryKeyProp, + Property, +} from "@mikro-orm/core" +import TaxRate from "./tax-rate" + +const TABLE_NAME = "tax_rate_rule" + +const taxRateIdIndexName = "IDX_tax_rate_rule_tax_rate_id" + +@Entity({ tableName: TABLE_NAME }) +export default class TaxRateRule { + @PrimaryKey({ columnType: "text" }) + tax_rate_id!: string + + @PrimaryKey({ columnType: "text" }) + reference_id!: string; + + [PrimaryKeyProp]?: ["tax_rate_id", "reference_id"] + + @Property({ columnType: "text" }) + reference: string + + @ManyToOne(() => TaxRate, { + fieldName: "tax_rate_id", + index: taxRateIdIndexName, + cascade: [Cascade.REMOVE, Cascade.PERSIST], + }) + tax_rate: TaxRate + + @Property({ columnType: "jsonb", nullable: true }) + metadata: Record | null = null + + @Property({ + onCreate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + created_at: Date + + @Property({ + onCreate: () => new Date(), + onUpdate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + updated_at: Date + + @Property({ columnType: "text", nullable: true }) + created_by: string | null = null +} diff --git a/packages/tax/src/services/tax-module-service.ts b/packages/tax/src/services/tax-module-service.ts index 668226fbb5..0fa882e10b 100644 --- a/packages/tax/src/services/tax-module-service.ts +++ b/packages/tax/src/services/tax-module-service.ts @@ -13,32 +13,46 @@ import { MedusaContext, ModulesSdkUtils, } from "@medusajs/utils" -import { TaxRate, TaxRegion } from "@models" +import { TaxRate, TaxRegion, TaxRateRule } from "@models" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" +import { TaxRegionDTO } from "@medusajs/types" type InjectedDependencies = { baseRepository: DAL.RepositoryService taxRateService: ModulesSdkTypes.InternalModuleService taxRegionService: ModulesSdkTypes.InternalModuleService + taxRateRuleService: ModulesSdkTypes.InternalModuleService } +const generateForModels = [TaxRegion, TaxRateRule] + export default class TaxModuleService< TTaxRate extends TaxRate = TaxRate, - TTaxRegion extends TaxRegion = TaxRegion + TTaxRegion extends TaxRegion = TaxRegion, + TTaxRateRule extends TaxRateRule = TaxRateRule > extends ModulesSdkUtils.abstractModuleServiceFactory< InjectedDependencies, TaxTypes.TaxRateDTO, - { TaxRegion: { dto: TaxTypes.TaxRegionDTO } } - >(TaxRate, [TaxRegion], entityNameToLinkableKeysMap) + { + TaxRegion: { dto: TaxTypes.TaxRegionDTO } + TaxRateRule: { dto: TaxTypes.TaxRateRuleDTO } + } + >(TaxRate, generateForModels, entityNameToLinkableKeysMap) implements ITaxModuleService { protected baseRepository_: DAL.RepositoryService protected taxRateService_: ModulesSdkTypes.InternalModuleService protected taxRegionService_: ModulesSdkTypes.InternalModuleService + protected taxRateRuleService_: ModulesSdkTypes.InternalModuleService constructor( - { baseRepository, taxRateService, taxRegionService }: InjectedDependencies, + { + baseRepository, + taxRateService, + taxRegionService, + taxRateRuleService, + }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { // @ts-ignore @@ -47,6 +61,7 @@ export default class TaxModuleService< this.baseRepository_ = baseRepository this.taxRateService_ = taxRateService this.taxRegionService_ = taxRegionService + this.taxRateRuleService_ = taxRateRuleService } __joinerConfig(): ModuleJoinerConfig { @@ -85,6 +100,7 @@ export default class TaxModuleService< return await this.taxRateService_.create(data, sharedContext) } + @InjectManager("baseRepository_") async createTaxRegions( data: TaxTypes.CreateTaxRegionDTO[], @MedusaContext() sharedContext: Context = {} @@ -128,4 +144,26 @@ export default class TaxModuleService< } ) } + + @InjectManager("baseRepository_") + async createTaxRateRules( + data: TaxTypes.CreateTaxRateRuleDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const rules = await this.taxRateRuleService_.create(data, sharedContext) + const result = await this.baseRepository_.serialize< + TaxTypes.TaxRateRuleDTO[] + >(rules, { + populate: true, + }) + return result + } + + @InjectTransactionManager("baseRepository_") + async createTaxRateRules_( + data: TaxTypes.CreateTaxRateRuleDTO[], + @MedusaContext() sharedContext: Context = {} + ) { + return await this.taxRateRuleService_.create(data, sharedContext) + } } diff --git a/packages/types/src/tax/common.ts b/packages/types/src/tax/common.ts index 672e6b524e..af2e7a059b 100644 --- a/packages/types/src/tax/common.ts +++ b/packages/types/src/tax/common.ts @@ -73,3 +73,29 @@ export interface FilterableTaxRegionProps updated_at?: OperatorMap created_by?: string | string[] | OperatorMap } + +export interface TaxRateRuleDTO { + reference: string + reference_id: string + tax_rate_id: string + tax_rate?: TaxRateDTO + metadata?: Record | null + created_at: string | Date + updated_at: string | Date + created_by: string | null +} + +export interface FilterableTaxRateRuleProps + extends BaseFilterable { + reference?: string | string[] | OperatorMap + reference_id?: string | string[] | OperatorMap + tax_rate_id?: string | string[] | OperatorMap + tax_rate?: FilterableTaxRateProps + metadata?: + | Record + | Record[] + | OperatorMap> + created_at?: OperatorMap + updated_at?: OperatorMap + created_by?: string | string[] | OperatorMap +} diff --git a/packages/types/src/tax/mutations.ts b/packages/types/src/tax/mutations.ts index 7df24a4fb5..dfbb680484 100644 --- a/packages/types/src/tax/mutations.ts +++ b/packages/types/src/tax/mutations.ts @@ -21,3 +21,11 @@ export interface CreateTaxRegionDTO { metadata?: Record } } + +export interface CreateTaxRateRuleDTO { + reference: string + reference_id: string + tax_rate_id: string + metadata?: Record + created_by?: string +} diff --git a/packages/types/src/tax/service.ts b/packages/types/src/tax/service.ts index e59c40b390..749eeebdde 100644 --- a/packages/types/src/tax/service.ts +++ b/packages/types/src/tax/service.ts @@ -6,8 +6,14 @@ import { FilterableTaxRateProps, TaxRateDTO, TaxRegionDTO, + TaxRateRuleDTO, + FilterableTaxRateRuleProps, } from "./common" -import { CreateTaxRateDTO, CreateTaxRegionDTO } from "./mutations" +import { + CreateTaxRateRuleDTO, + CreateTaxRateDTO, + CreateTaxRegionDTO, +} from "./mutations" export interface ITaxModuleService extends IModuleService { retrieve( @@ -47,4 +53,15 @@ export interface ITaxModuleService extends IModuleService { config?: FindConfig, sharedContext?: Context ): Promise + + createTaxRateRules( + data: CreateTaxRateRuleDTO[], + sharedContext?: Context + ): Promise + + listTaxRateRules( + filters?: FilterableTaxRateRuleProps, + config?: FindConfig, + sharedContext?: Context + ): Promise }