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.
This commit is contained in:
Sebastian Rindom
2024-02-20 17:50:19 +01:00
committed by GitHub
parent 43ed6a990d
commit 137cc0ebf8
7 changed files with 197 additions and 6 deletions

View File

@@ -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,
}),
}),
])
)
})
})

View File

@@ -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"

View File

@@ -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<string, unknown> | 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
}

View File

@@ -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<any>
taxRegionService: ModulesSdkTypes.InternalModuleService<any>
taxRateRuleService: ModulesSdkTypes.InternalModuleService<any>
}
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<TTaxRate>
protected taxRegionService_: ModulesSdkTypes.InternalModuleService<TTaxRegion>
protected taxRateRuleService_: ModulesSdkTypes.InternalModuleService<TTaxRateRule>
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<TaxTypes.TaxRateRuleDTO[]> {
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)
}
}

View File

@@ -73,3 +73,29 @@ export interface FilterableTaxRegionProps
updated_at?: OperatorMap<string>
created_by?: string | string[] | OperatorMap<string>
}
export interface TaxRateRuleDTO {
reference: string
reference_id: string
tax_rate_id: string
tax_rate?: TaxRateDTO
metadata?: Record<string, unknown> | null
created_at: string | Date
updated_at: string | Date
created_by: string | null
}
export interface FilterableTaxRateRuleProps
extends BaseFilterable<FilterableTaxRateRuleProps> {
reference?: string | string[] | OperatorMap<string>
reference_id?: string | string[] | OperatorMap<string>
tax_rate_id?: string | string[] | OperatorMap<string>
tax_rate?: FilterableTaxRateProps
metadata?:
| Record<string, unknown>
| Record<string, unknown>[]
| OperatorMap<Record<string, unknown>>
created_at?: OperatorMap<string>
updated_at?: OperatorMap<string>
created_by?: string | string[] | OperatorMap<string>
}

View File

@@ -21,3 +21,11 @@ export interface CreateTaxRegionDTO {
metadata?: Record<string, unknown>
}
}
export interface CreateTaxRateRuleDTO {
reference: string
reference_id: string
tax_rate_id: string
metadata?: Record<string, unknown>
created_by?: string
}

View File

@@ -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<TaxRegionDTO>,
sharedContext?: Context
): Promise<TaxRegionDTO[]>
createTaxRateRules(
data: CreateTaxRateRuleDTO[],
sharedContext?: Context
): Promise<TaxRateRuleDTO[]>
listTaxRateRules(
filters?: FilterableTaxRateRuleProps,
config?: FindConfig<TaxRateRuleDTO>,
sharedContext?: Context
): Promise<TaxRateRuleDTO[]>
}