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 TaxRate } from "./tax-rate"
|
||||||
export { default as TaxRegion } from "./tax-region"
|
export { default as TaxRegion } from "./tax-region"
|
||||||
|
export { default as TaxRateRule } from "./tax-rate-rule"
|
||||||
|
|||||||
@@ -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,
|
MedusaContext,
|
||||||
ModulesSdkUtils,
|
ModulesSdkUtils,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { TaxRate, TaxRegion } from "@models"
|
import { TaxRate, TaxRegion, TaxRateRule } from "@models"
|
||||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||||
|
import { TaxRegionDTO } from "@medusajs/types"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
baseRepository: DAL.RepositoryService
|
baseRepository: DAL.RepositoryService
|
||||||
taxRateService: ModulesSdkTypes.InternalModuleService<any>
|
taxRateService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
taxRegionService: ModulesSdkTypes.InternalModuleService<any>
|
taxRegionService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
|
taxRateRuleService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const generateForModels = [TaxRegion, TaxRateRule]
|
||||||
|
|
||||||
export default class TaxModuleService<
|
export default class TaxModuleService<
|
||||||
TTaxRate extends TaxRate = TaxRate,
|
TTaxRate extends TaxRate = TaxRate,
|
||||||
TTaxRegion extends TaxRegion = TaxRegion
|
TTaxRegion extends TaxRegion = TaxRegion,
|
||||||
|
TTaxRateRule extends TaxRateRule = TaxRateRule
|
||||||
>
|
>
|
||||||
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
||||||
InjectedDependencies,
|
InjectedDependencies,
|
||||||
TaxTypes.TaxRateDTO,
|
TaxTypes.TaxRateDTO,
|
||||||
{ TaxRegion: { dto: TaxTypes.TaxRegionDTO } }
|
{
|
||||||
>(TaxRate, [TaxRegion], entityNameToLinkableKeysMap)
|
TaxRegion: { dto: TaxTypes.TaxRegionDTO }
|
||||||
|
TaxRateRule: { dto: TaxTypes.TaxRateRuleDTO }
|
||||||
|
}
|
||||||
|
>(TaxRate, generateForModels, entityNameToLinkableKeysMap)
|
||||||
implements ITaxModuleService
|
implements ITaxModuleService
|
||||||
{
|
{
|
||||||
protected baseRepository_: DAL.RepositoryService
|
protected baseRepository_: DAL.RepositoryService
|
||||||
protected taxRateService_: ModulesSdkTypes.InternalModuleService<TTaxRate>
|
protected taxRateService_: ModulesSdkTypes.InternalModuleService<TTaxRate>
|
||||||
protected taxRegionService_: ModulesSdkTypes.InternalModuleService<TTaxRegion>
|
protected taxRegionService_: ModulesSdkTypes.InternalModuleService<TTaxRegion>
|
||||||
|
protected taxRateRuleService_: ModulesSdkTypes.InternalModuleService<TTaxRateRule>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ baseRepository, taxRateService, taxRegionService }: InjectedDependencies,
|
{
|
||||||
|
baseRepository,
|
||||||
|
taxRateService,
|
||||||
|
taxRegionService,
|
||||||
|
taxRateRuleService,
|
||||||
|
}: InjectedDependencies,
|
||||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -47,6 +61,7 @@ export default class TaxModuleService<
|
|||||||
this.baseRepository_ = baseRepository
|
this.baseRepository_ = baseRepository
|
||||||
this.taxRateService_ = taxRateService
|
this.taxRateService_ = taxRateService
|
||||||
this.taxRegionService_ = taxRegionService
|
this.taxRegionService_ = taxRegionService
|
||||||
|
this.taxRateRuleService_ = taxRateRuleService
|
||||||
}
|
}
|
||||||
|
|
||||||
__joinerConfig(): ModuleJoinerConfig {
|
__joinerConfig(): ModuleJoinerConfig {
|
||||||
@@ -85,6 +100,7 @@ export default class TaxModuleService<
|
|||||||
return await this.taxRateService_.create(data, sharedContext)
|
return await this.taxRateService_.create(data, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
async createTaxRegions(
|
async createTaxRegions(
|
||||||
data: TaxTypes.CreateTaxRegionDTO[],
|
data: TaxTypes.CreateTaxRegionDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@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>
|
updated_at?: OperatorMap<string>
|
||||||
created_by?: string | string[] | 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>
|
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,
|
FilterableTaxRateProps,
|
||||||
TaxRateDTO,
|
TaxRateDTO,
|
||||||
TaxRegionDTO,
|
TaxRegionDTO,
|
||||||
|
TaxRateRuleDTO,
|
||||||
|
FilterableTaxRateRuleProps,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
import { CreateTaxRateDTO, CreateTaxRegionDTO } from "./mutations"
|
import {
|
||||||
|
CreateTaxRateRuleDTO,
|
||||||
|
CreateTaxRateDTO,
|
||||||
|
CreateTaxRegionDTO,
|
||||||
|
} from "./mutations"
|
||||||
|
|
||||||
export interface ITaxModuleService extends IModuleService {
|
export interface ITaxModuleService extends IModuleService {
|
||||||
retrieve(
|
retrieve(
|
||||||
@@ -47,4 +53,15 @@ export interface ITaxModuleService extends IModuleService {
|
|||||||
config?: FindConfig<TaxRegionDTO>,
|
config?: FindConfig<TaxRegionDTO>,
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<TaxRegionDTO[]>
|
): 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