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:
@@ -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,
|
||||
}),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
|
||||
55
packages/tax/src/models/tax-rate-rule.ts
Normal file
55
packages/tax/src/models/tax-rate-rule.ts
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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[]>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user