Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tax): soft deletes #6486

Merged
merged 2 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 238 additions & 0 deletions packages/tax/integration-tests/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,33 @@ moduleIntegrationTestRunner({
expect(rates).toEqual([])
})

it("should soft delete tax rate", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})

const taxRate = await service.create({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
})

await service.softDelete([taxRate.id])

const rates = await service.list(
{ tax_region_id: region.id },
{ withDeleted: true }
)

expect(rates).toEqual([
expect.objectContaining({
id: taxRate.id,
deleted_at: expect.any(Date),
}),
])
})

it("should delete a tax region and its rates", async () => {
const region = await service.createTaxRegions({
country_code: "US",
Expand All @@ -342,6 +369,47 @@ moduleIntegrationTestRunner({
expect(rates).toEqual([])
})

it("should soft delete a tax region and its rates", async () => {
const region = await service.createTaxRegions({
country_code: "US",
default_tax_rate: {
value: 2,
code: "test",
name: "default test",
},
})

await service.create({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
})

await service.softDeleteTaxRegions([region.id])

const taxRegions = await service.listTaxRegions(
{},
{ withDeleted: true }
)
const rates = await service.list({}, { withDeleted: true })

expect(taxRegions).toEqual([
expect.objectContaining({
id: region.id,
deleted_at: expect.any(Date),
}),
])
expect(rates).toEqual([
expect.objectContaining({
deleted_at: expect.any(Date),
}),
expect.objectContaining({
deleted_at: expect.any(Date),
}),
])
})

it("should delete a tax rate and its rules", async () => {
const region = await service.createTaxRegions({
country_code: "US",
Expand Down Expand Up @@ -369,6 +437,130 @@ moduleIntegrationTestRunner({
expect(rules).toEqual([])
})

it("should soft delete a tax rate and its rules", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})

const rate = await service.create({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
rules: [
{ reference: "product", reference_id: "product_id_1" },
{ reference: "product_type", reference_id: "product_type_id" },
],
})

await service.softDelete(rate.id)

const taxRegions = await service.listTaxRegions(
{},
{ withDeleted: true }
)
const rates = await service.list({}, { withDeleted: true })
const rules = await service.listTaxRateRules({}, { withDeleted: true })

expect(taxRegions).toEqual([
expect.objectContaining({ id: region.id, deleted_at: null }),
])
expect(rates).toEqual([
expect.objectContaining({
id: rate.id,
deleted_at: expect.any(Date),
}),
])
expect(rules).toEqual([
expect.objectContaining({
tax_rate_id: rate.id,
deleted_at: expect.any(Date),
}),
expect.objectContaining({
tax_rate_id: rate.id,
deleted_at: expect.any(Date),
}),
])
})

it("should soft delete a tax rule", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})

const rate = await service.create({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
})

const [ruleOne, ruleTwo] = await service.createTaxRateRules([
{
tax_rate_id: rate.id,
reference: "product",
reference_id: "product_id_1",
},
{
tax_rate_id: rate.id,
reference: "product_type",
reference_id: "product_type_id",
},
])

await service.softDeleteTaxRateRules([ruleOne.id])

const rules = await service.listTaxRateRules({}, { withDeleted: true })
expect(rules).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: ruleOne.id,
deleted_at: expect.any(Date),
}),
expect.objectContaining({
id: ruleTwo.id,
deleted_at: null,
}),
])
)

const rateWithRules = await service.retrieve(rate.id, {
relations: ["rules"],
})
expect(rateWithRules.rules.length).toBe(1)

// should be possible to add the rule back again
await service.createTaxRateRules({
tax_rate_id: rate.id,
reference: ruleOne.reference,
reference_id: ruleOne.reference_id,
})

const rateWithRulesAfterReAdd = await service.retrieve(rate.id, {
relations: ["rules"],
})
expect(rateWithRulesAfterReAdd.rules.length).toBe(2)
})

it("should fail on duplicate rules", async () => {
const region = await service.createTaxRegions({
country_code: "US",
})

await expect(
service.create({
tax_region_id: region.id,
value: 10,
code: "test",
name: "test",
rules: [
{ reference: "product", reference_id: "product_id_1" },
{ reference: "product", reference_id: "product_id_1" },
],
})
).rejects.toThrowError()
})

it("should fail to create province region belonging to a parent with non-matching country", async () => {
const caRegion = await service.createTaxRegions({
country_code: "CA",
Expand All @@ -391,6 +583,52 @@ moduleIntegrationTestRunner({
})
).rejects.toThrowError()
})

it("should delete all child regions when parent region is deleted", async () => {
const region = await service.createTaxRegions({
country_code: "CA",
})
const provinceRegion = await service.createTaxRegions({
parent_id: region.id,
country_code: "CA",
province_code: "QC",
})

await service.deleteTaxRegions(region.id)

const taxRegions = await service.listTaxRegions({
id: provinceRegion.id,
})

expect(taxRegions).toEqual([])
})

it("it should soft delete all child regions when parent region is deleted", async () => {
const region = await service.createTaxRegions({
country_code: "CA",
})
const provinceRegion = await service.createTaxRegions({
parent_id: region.id,
country_code: "CA",
province_code: "QC",
})

await service.softDeleteTaxRegions([region.id])

const taxRegions = await service.listTaxRegions(
{
id: provinceRegion.id,
},
{ withDeleted: true }
)

expect(taxRegions).toEqual([
expect.objectContaining({
id: provinceRegion.id,
deleted_at: expect.any(Date),
}),
])
})
})
},
})
Expand Down
2 changes: 1 addition & 1 deletion packages/tax/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = {
"^.+\\.[jt]s?$": [
"ts-jest",
{
tsConfig: "tsconfig.spec.json",
tsconfig: "tsconfig.spec.json",
isolatedModules: true,
},
],
Expand Down
80 changes: 70 additions & 10 deletions packages/tax/src/models/tax-rate-rule.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,77 @@
import { DAL } from "@medusajs/types"
import {
DALUtils,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import {
Cascade,
Entity,
ManyToOne,
PrimaryKey,
PrimaryKeyProp,
Property,
Filter,
OptionalProps,
BeforeCreate,
OnInit,
} from "@mikro-orm/core"
import TaxRate from "./tax-rate"

const TABLE_NAME = "tax_rate_rule"

type OptionalRuleProps = DAL.SoftDeletableEntityDateColumns

const taxRateIdIndexName = "IDX_tax_rate_rule_tax_rate_id"
const taxRateIdIndexStatement = createPsqlIndexStatementHelper({
name: taxRateIdIndexName,
tableName: TABLE_NAME,
columns: "tax_rate_id",
where: "deleted_at IS NULL",
})

const referenceIdIndexName = "IDX_tax_rate_rule_reference_id"
const referenceIdIndexStatement = createPsqlIndexStatementHelper({
name: referenceIdIndexName,
tableName: TABLE_NAME,
columns: "reference_id",
where: "deleted_at IS NULL",
})

const uniqueRateReferenceIndexName = "IDX_tax_rate_rule_unique_rate_reference"
const uniqueRateReferenceIndexStatement = createPsqlIndexStatementHelper({
name: uniqueRateReferenceIndexName,
tableName: TABLE_NAME,
columns: ["tax_rate_id", "reference_id"],
unique: true,
where: "deleted_at IS NULL",
})

@Entity({ tableName: TABLE_NAME })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
@uniqueRateReferenceIndexStatement.MikroORMIndex()
export default class TaxRateRule {
@PrimaryKey({ columnType: "text" })
tax_rate_id!: string
[OptionalProps]?: OptionalRuleProps

@PrimaryKey({ columnType: "text" })
reference_id!: string;
id!: string

@ManyToOne(() => TaxRate, {
type: "text",
fieldName: "tax_rate_id",
mapToPk: true,
cascade: [Cascade.REMOVE],
})
@taxRateIdIndexStatement.MikroORMIndex()
tax_rate_id: string

[PrimaryKeyProp]?: ["tax_rate_id", "reference_id"]
@Property({ columnType: "text" })
@referenceIdIndexStatement.MikroORMIndex()
reference_id: string

@Property({ columnType: "text" })
reference: string

@ManyToOne(() => TaxRate, {
fieldName: "tax_rate_id",
index: taxRateIdIndexName,
cascade: [Cascade.REMOVE, Cascade.PERSIST],
})
@ManyToOne(() => TaxRate, { persist: false })
tax_rate: TaxRate

@Property({ columnType: "jsonb", nullable: true })
Expand All @@ -52,4 +94,22 @@ export default class TaxRateRule {

@Property({ columnType: "text", nullable: true })
created_by: string | null = null

@createPsqlIndexStatementHelper({
tableName: TABLE_NAME,
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
}).MikroORMIndex()
@Property({ columnType: "timestamptz", nullable: true })
deleted_at: Date | null = null

@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "txr")
}

@OnInit()
onInit() {
this.id = generateEntityId(this.id, "txr")
}
}
Loading
Loading