From adad66e13f2a1643688d3da26801dcfe8b87b793 Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Wed, 28 Feb 2024 15:40:35 +0100 Subject: [PATCH] feat(tax): migration file (#6523) **What** - Tax Module Migration file. - Skeleton for API routes and integrations tests for tax API in v2 --- .../plugins/__tests__/tax/admin/tax.spec.ts | 77 +++ integration-tests/plugins/medusa-config.js | 5 + integration-tests/plugins/package.json | 1 + .../src/api-v2/admin/tax-rates/[id]/route.ts | 26 + .../src/api-v2/admin/tax-rates/middlewares.ts | 25 + .../api-v2/admin/tax-rates/query-config.ts | 26 + .../src/api-v2/admin/tax-rates/validators.ts | 3 + packages/medusa/src/api-v2/middlewares.ts | 2 + packages/tax/src/joiner-config.ts | 36 +- .../src/migrations/.snapshot-medusa-tax.json | 558 ++++++++++++++++++ .../src/migrations/Migration20240227090331.ts | 114 ++++ packages/tax/src/models/index.ts | 1 + .../tax/src/services/tax-module-service.ts | 36 +- packages/types/src/tax/common.ts | 5 + yarn.lock | 3 +- 15 files changed, 912 insertions(+), 6 deletions(-) create mode 100644 integration-tests/plugins/__tests__/tax/admin/tax.spec.ts create mode 100644 packages/medusa/src/api-v2/admin/tax-rates/[id]/route.ts create mode 100644 packages/medusa/src/api-v2/admin/tax-rates/middlewares.ts create mode 100644 packages/medusa/src/api-v2/admin/tax-rates/query-config.ts create mode 100644 packages/medusa/src/api-v2/admin/tax-rates/validators.ts create mode 100644 packages/tax/src/migrations/.snapshot-medusa-tax.json create mode 100644 packages/tax/src/migrations/Migration20240227090331.ts diff --git a/integration-tests/plugins/__tests__/tax/admin/tax.spec.ts b/integration-tests/plugins/__tests__/tax/admin/tax.spec.ts new file mode 100644 index 0000000000000..ac21b2d8803cc --- /dev/null +++ b/integration-tests/plugins/__tests__/tax/admin/tax.spec.ts @@ -0,0 +1,77 @@ +import path from "path" +import { ITaxModuleService } from "@medusajs/types" +import { ModuleRegistrationName } from "@medusajs/modules-sdk" + +import { createAdminUser } from "../../../helpers/create-admin-user" +import { initDb, useDb } from "../../../../environment-helpers/use-db" +import { getContainer } from "../../../../environment-helpers/use-container" +import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app" +import { useApi } from "../../../../environment-helpers/use-api" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } +const adminHeaders = { + headers: { "x-medusa-access-token": "test_token" }, +} + +describe("Taxes - Admin", () => { + let dbConnection + let appContainer + let shutdownServer + let service: ITaxModuleService + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..", "..")) + dbConnection = await initDb({ cwd, env } as any) + shutdownServer = await startBootstrapApp({ cwd, env }) + appContainer = getContainer() + service = appContainer.resolve(ModuleRegistrationName.TAX) + }) + + beforeEach(async () => { + await createAdminUser(dbConnection, adminHeaders) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + await shutdownServer() + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("can retrieve a tax rate", async () => { + const region = await service.createTaxRegions({ + country_code: "us", + }) + const rate = await service.create({ + tax_region_id: region.id, + code: "test", + rate: 2.5, + name: "Test Rate", + }) + + const api = useApi() as any + const response = await api.get(`/admin/tax-rates/${rate.id}`, adminHeaders) + + expect(response.status).toEqual(200) + expect(response.data).toEqual({ + tax_rate: { + id: rate.id, + code: "test", + rate: 2.5, + name: "Test Rate", + metadata: null, + tax_region_id: region.id, + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + created_by: null, + }, + }) + }) +}) diff --git a/integration-tests/plugins/medusa-config.js b/integration-tests/plugins/medusa-config.js index 5084890f1f3a6..f5b3bd4c7fba9 100644 --- a/integration-tests/plugins/medusa-config.js +++ b/integration-tests/plugins/medusa-config.js @@ -123,5 +123,10 @@ module.exports = { resources: "shared", resolve: "@medusajs/store", }, + [Modules.TAX]: { + scope: "internal", + resources: "shared", + resolve: "@medusajs/tax", + }, }, } diff --git a/integration-tests/plugins/package.json b/integration-tests/plugins/package.json index 0ce8a04699093..8805929b36baa 100644 --- a/integration-tests/plugins/package.json +++ b/integration-tests/plugins/package.json @@ -22,6 +22,7 @@ "@medusajs/promotion": "workspace:^", "@medusajs/region": "workspace:^", "@medusajs/store": "workspace:^", + "@medusajs/tax": "workspace:^", "@medusajs/user": "workspace:^", "@medusajs/utils": "workspace:^", "@medusajs/workflow-engine-inmemory": "workspace:*", diff --git a/packages/medusa/src/api-v2/admin/tax-rates/[id]/route.ts b/packages/medusa/src/api-v2/admin/tax-rates/[id]/route.ts new file mode 100644 index 0000000000000..3b2c4d45b1054 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/tax-rates/[id]/route.ts @@ -0,0 +1,26 @@ +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" + +import { defaultAdminTaxRateFields } from "../query-config" +import { remoteQueryObjectFromString } from "@medusajs/utils" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve("remoteQuery") + + const variables = { id: req.params.id } + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "tax_rate", + variables, + fields: defaultAdminTaxRateFields, + }) + + const [taxRate] = await remoteQuery(queryObject) + + res.status(200).json({ tax_rate: taxRate }) +} diff --git a/packages/medusa/src/api-v2/admin/tax-rates/middlewares.ts b/packages/medusa/src/api-v2/admin/tax-rates/middlewares.ts new file mode 100644 index 0000000000000..1d0ebee7bcf74 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/tax-rates/middlewares.ts @@ -0,0 +1,25 @@ +import * as QueryConfig from "./query-config" + +import { AdminGetTaxRatesTaxRateParams } from "./validators" +import { transformQuery } from "../../../api/middlewares" + +import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { authenticate } from "../../../utils/authenticate-middleware" + +export const adminTaxRateRoutesMiddlewares: MiddlewareRoute[] = [ + { + method: ["ALL"], + matcher: "/admin/tax-rates*", + middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + }, + { + method: ["GET"], + matcher: "/admin/tax-rates/:id", + middlewares: [ + transformQuery( + AdminGetTaxRatesTaxRateParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, +] diff --git a/packages/medusa/src/api-v2/admin/tax-rates/query-config.ts b/packages/medusa/src/api-v2/admin/tax-rates/query-config.ts new file mode 100644 index 0000000000000..33acb41be00af --- /dev/null +++ b/packages/medusa/src/api-v2/admin/tax-rates/query-config.ts @@ -0,0 +1,26 @@ +export const defaultAdminTaxRateRelations = [] +export const allowedAdminTaxRateRelations = [] +export const defaultAdminTaxRateFields = [ + "id", + "name", + "code", + "rate", + "tax_region_id", + "created_by", + "created_at", + "updated_at", + "deleted_at", + "metadata", +] + +export const retrieveTransformQueryConfig = { + defaultFields: defaultAdminTaxRateFields, + defaultRelations: defaultAdminTaxRateRelations, + allowedRelations: allowedAdminTaxRateRelations, + isList: false, +} + +export const listTransformQueryConfig = { + defaultLimit: 20, + isList: true, +} diff --git a/packages/medusa/src/api-v2/admin/tax-rates/validators.ts b/packages/medusa/src/api-v2/admin/tax-rates/validators.ts new file mode 100644 index 0000000000000..1ad2229ccdedf --- /dev/null +++ b/packages/medusa/src/api-v2/admin/tax-rates/validators.ts @@ -0,0 +1,3 @@ +import { FindParams } from "../../../types/common" + +export class AdminGetTaxRatesTaxRateParams extends FindParams {} diff --git a/packages/medusa/src/api-v2/middlewares.ts b/packages/medusa/src/api-v2/middlewares.ts index 43c2f55675d43..145a8dda5a713 100644 --- a/packages/medusa/src/api-v2/middlewares.ts +++ b/packages/medusa/src/api-v2/middlewares.ts @@ -7,6 +7,7 @@ import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares" import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares" import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares" import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares" +import { adminTaxRateRoutesMiddlewares } from "./admin/tax-rates/middlewares" import { adminUserRoutesMiddlewares } from "./admin/users/middlewares" import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-executions/middlewares" import { authRoutesMiddlewares } from "./auth/middlewares" @@ -30,6 +31,7 @@ export const config: MiddlewaresConfig = { ...adminRegionRoutesMiddlewares, ...adminUserRoutesMiddlewares, ...adminInviteRoutesMiddlewares, + ...adminTaxRateRoutesMiddlewares, ...adminApiKeyRoutesMiddlewares, ...hooksRoutesMiddlewares, ...adminStoreRoutesMiddlewares, diff --git a/packages/tax/src/joiner-config.ts b/packages/tax/src/joiner-config.ts index 7ffc18e24e41e..d89a3bda3e249 100644 --- a/packages/tax/src/joiner-config.ts +++ b/packages/tax/src/joiner-config.ts @@ -1,9 +1,13 @@ import { Modules } from "@medusajs/modules-sdk" import { ModuleJoinerConfig } from "@medusajs/types" import { MapToConfig } from "@medusajs/utils" +import { TaxRate, TaxRegion, TaxRateRule, TaxProvider } from "@models" export const LinkableKeys: Record = { - tax_rate_id: "TaxRate", + tax_rate_id: TaxRate.name, + tax_region_id: TaxRegion.name, + tax_rate_rule_id: TaxRateRule.name, + tax_provider_id: TaxProvider.name, } const entityLinkableKeysMap: MapToConfig = {} @@ -21,5 +25,33 @@ export const joinerConfig: ModuleJoinerConfig = { serviceName: Modules.TAX, primaryKeys: ["id"], linkableKeys: LinkableKeys, - alias: [], + alias: [ + { + name: ["tax_rate", "tax_rates"], + args: { + entity: TaxRate.name, + }, + }, + { + name: ["tax_region", "tax_regions"], + args: { + entity: TaxRegion.name, + methodSuffix: "TaxRegions", + }, + }, + { + name: ["tax_rate_rule", "tax_rate_rules"], + args: { + entity: TaxRateRule.name, + methodSuffix: "TaxRateRules", + }, + }, + { + name: ["tax_provider", "tax_providers"], + args: { + entity: TaxProvider.name, + methodSuffix: "TaxProviders", + }, + }, + ], } as ModuleJoinerConfig diff --git a/packages/tax/src/migrations/.snapshot-medusa-tax.json b/packages/tax/src/migrations/.snapshot-medusa-tax.json new file mode 100644 index 0000000000000..5c37511474439 --- /dev/null +++ b/packages/tax/src/migrations/.snapshot-medusa-tax.json @@ -0,0 +1,558 @@ +{ + "namespaces": [ + "public" + ], + "name": "public", + "tables": [ + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "true", + "mappedType": "boolean" + } + }, + "name": "tax_provider", + "schema": "public", + "indexes": [ + { + "keyName": "tax_provider_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "country_code": { + "name": "country_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "province_code": { + "name": "province_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "created_by": { + "name": "created_by", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "tax_region", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "parent_id" + ], + "composite": false, + "keyName": "IDX_tax_region_parent_id", + "primary": false, + "unique": false + }, + { + "keyName": "IDX_tax_region_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_tax_region_deleted_at\" ON \"tax_region\" (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "IDX_tax_region_unique_country_province", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_tax_region_unique_country_province\" ON \"tax_region\" (country_code, province_code)" + }, + { + "keyName": "tax_region_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [ + { + "name": "CK_tax_region_country_top_level", + "expression": "parent_id IS NULL OR province_code IS NOT NULL", + "definition": "check ((parent_id IS NULL OR province_code IS NOT NULL))" + }, + { + "name": "CK_tax_region_provider_top_level", + "expression": "parent_id IS NULL OR provider_id IS NULL", + "definition": "check ((parent_id IS NULL OR provider_id IS NULL))" + } + ], + "foreignKeys": { + "tax_region_provider_id_foreign": { + "constraintName": "tax_region_provider_id_foreign", + "columnNames": [ + "provider_id" + ], + "localTableName": "public.tax_region", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.tax_provider", + "deleteRule": "set null", + "updateRule": "cascade" + }, + "tax_region_parent_id_foreign": { + "constraintName": "tax_region_parent_id_foreign", + "columnNames": [ + "parent_id" + ], + "localTableName": "public.tax_region", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.tax_region", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "rate": { + "name": "rate", + "type": "real", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "float" + }, + "code": { + "name": "code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "name": { + "name": "name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "is_default": { + "name": "is_default", + "type": "bool", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "is_combinable": { + "name": "is_combinable", + "type": "bool", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "tax_region_id": { + "name": "tax_region_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "created_by": { + "name": "created_by", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "tax_rate", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_tax_rate_tax_region_id", + "columnNames": [ + "tax_region_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_tax_rate_tax_region_id\" ON \"tax_rate\" (tax_region_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_tax_rate_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_tax_rate_deleted_at\" ON \"tax_rate\" (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "IDX_single_default_region", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_single_default_region\" ON \"tax_rate\" (tax_region_id) WHERE is_default = true AND deleted_at IS NULL" + }, + { + "keyName": "tax_rate_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "tax_rate_tax_region_id_foreign": { + "constraintName": "tax_rate_tax_region_id_foreign", + "columnNames": [ + "tax_region_id" + ], + "localTableName": "public.tax_rate", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.tax_region", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "tax_rate_id": { + "name": "tax_rate_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "reference": { + "name": "reference", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "created_by": { + "name": "created_by", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "tax_rate_rule", + "schema": "public", + "indexes": [ + { + "keyName": "IDX_tax_rate_rule_tax_rate_id", + "columnNames": [ + "tax_rate_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_tax_rate_rule_tax_rate_id\" ON \"tax_rate_rule\" (tax_rate_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_tax_rate_rule_reference_id", + "columnNames": [ + "reference_id" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_tax_rate_rule_reference_id\" ON \"tax_rate_rule\" (reference_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "IDX_tax_rate_rule_deleted_at", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE INDEX IF NOT EXISTS \"IDX_tax_rate_rule_deleted_at\" ON \"tax_rate_rule\" (deleted_at) WHERE deleted_at IS NOT NULL" + }, + { + "keyName": "IDX_tax_rate_rule_unique_rate_reference", + "columnNames": [], + "composite": false, + "primary": false, + "unique": false, + "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_tax_rate_rule_unique_rate_reference\" ON \"tax_rate_rule\" (tax_rate_id, reference_id) WHERE deleted_at IS NULL" + }, + { + "keyName": "tax_rate_rule_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "tax_rate_rule_tax_rate_id_foreign": { + "constraintName": "tax_rate_rule_tax_rate_id_foreign", + "columnNames": [ + "tax_rate_id" + ], + "localTableName": "public.tax_rate_rule", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.tax_rate", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + } + ] +} diff --git a/packages/tax/src/migrations/Migration20240227090331.ts b/packages/tax/src/migrations/Migration20240227090331.ts new file mode 100644 index 0000000000000..e7383472e6853 --- /dev/null +++ b/packages/tax/src/migrations/Migration20240227090331.ts @@ -0,0 +1,114 @@ +import { generatePostgresAlterColummnIfExistStatement } from "@medusajs/utils" +import { Migration } from "@mikro-orm/migrations" + +export class Migration20240227090331 extends Migration { + async up(): Promise { + // Adjust tax_provider table + this.addSql( + `ALTER TABLE IF EXISTS "tax_provider" ADD COLUMN IF NOT EXISTS "is_enabled" bool not null default true;` + ) + + this.addSql( + `CREATE TABLE IF NOT EXISTS "tax_provider" ("id" text not null, "is_enabled" boolean not null default true, CONSTRAINT "tax_provider_pkey" PRIMARY KEY ("id"));` + ) + + // Create or update tax_region table + this.addSql( + `CREATE TABLE IF NOT EXISTS "tax_region" ("id" text not null, "provider_id" text null, "country_code" text not null, "province_code" text null, "parent_id" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "created_by" text null, "deleted_at" timestamptz null, CONSTRAINT "tax_region_pkey" PRIMARY KEY ("id"), CONSTRAINT "CK_tax_region_country_top_level" CHECK (parent_id IS NULL OR province_code IS NOT NULL), CONSTRAINT "CK_tax_region_provider_top_level" CHECK (parent_id IS NULL OR provider_id IS NULL));` + ) + // Indexes for tax_region + this.addSql( + `CREATE INDEX IF NOT EXISTS "IDX_tax_region_parent_id" ON "tax_region" ("parent_id");` + ) + this.addSql( + `CREATE INDEX IF NOT EXISTS "IDX_tax_region_deleted_at" ON "tax_region" ("deleted_at") WHERE deleted_at IS NOT NULL;` + ) + this.addSql( + `CREATE UNIQUE INDEX IF NOT EXISTS "IDX_tax_region_unique_country_province" ON "tax_region" ("country_code", "province_code");` + ) + + // Old foreign key on region_id + this.addSql( + generatePostgresAlterColummnIfExistStatement( + "tax_rate", + ["region_id"], + "DROP NOT NULL" + ) + ) + this.addSql( + `ALTER TABLE IF EXISTS "tax_rate" DROP CONSTRAINT IF EXISTS "FK_b95a1e03b051993d208366cb960";` + ) + this.addSql( + `ALTER TABLE IF EXISTS "tax_rate" ADD COLUMN IF NOT EXISTS "tax_region_id" text not null;` + ) + this.addSql( + `ALTER TABLE IF EXISTS "tax_rate" ADD COLUMN IF NOT EXISTS "deleted_at" timestamptz null;` + ) + this.addSql( + `ALTER TABLE IF EXISTS "tax_rate" ADD COLUMN IF NOT EXISTS "created_by" text null;` + ) + this.addSql( + `ALTER TABLE IF EXISTS "tax_rate" ADD COLUMN IF NOT EXISTS "is_default" bool not null default false;` + ) + this.addSql( + `ALTER TABLE IF EXISTS "tax_rate" ADD COLUMN IF NOT EXISTS "is_combinable" bool not null default false;` + ) + this.addSql( + `create table if not exists "tax_rate" ("id" text not null, "rate" real null, "code" text null, "name" text not null, "is_default" bool not null default false, "is_combinable" bool not null default false, "tax_region_id" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "created_by" text null, "deleted_at" timestamptz null, constraint "tax_rate_pkey" primary key ("id"));` + ) + + // Indexes for tax_rate + this.addSql( + `CREATE INDEX IF NOT EXISTS "IDX_tax_rate_tax_region_id" ON "tax_rate" ("tax_region_id") WHERE deleted_at IS NULL;` + ) + this.addSql( + `CREATE INDEX IF NOT EXISTS "IDX_tax_rate_deleted_at" ON "tax_rate" ("deleted_at") WHERE deleted_at IS NOT NULL;` + ) + this.addSql( + `CREATE UNIQUE INDEX IF NOT EXISTS "IDX_single_default_region" ON "tax_rate" ("tax_region_id") WHERE is_default = true AND deleted_at IS NULL;` + ) + + // Adjust or create tax_rate_rule table + this.addSql( + `CREATE TABLE IF NOT EXISTS "tax_rate_rule" ("id" text not null, "tax_rate_id" text not null, "reference_id" text not null, "reference" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "created_by" text null, "deleted_at" timestamptz null, CONSTRAINT "tax_rate_rule_pkey" PRIMARY KEY ("id"));` + ) + // Indexes for tax_rate_rule + this.addSql( + `CREATE INDEX IF NOT EXISTS "IDX_tax_rate_rule_tax_rate_id" ON "tax_rate_rule" ("tax_rate_id") WHERE deleted_at IS NULL;` + ) + this.addSql( + `CREATE INDEX IF NOT EXISTS "IDX_tax_rate_rule_reference_id" ON "tax_rate_rule" ("reference_id") WHERE deleted_at IS NULL;` + ) + this.addSql( + `CREATE INDEX IF NOT EXISTS "IDX_tax_rate_rule_deleted_at" ON "tax_rate_rule" ("deleted_at") WHERE deleted_at IS NOT NULL;` + ) + this.addSql( + `CREATE UNIQUE INDEX IF NOT EXISTS "IDX_tax_rate_rule_unique_rate_reference" ON "tax_rate_rule" ("tax_rate_id", "reference_id") WHERE deleted_at IS NULL;` + ) + + // Foreign keys adjustments + this.addSql( + `ALTER TABLE "tax_region" ADD CONSTRAINT "FK_tax_region_provider_id" FOREIGN KEY ("provider_id") REFERENCES "tax_provider" ("id") ON DELETE SET NULL;` + ) + this.addSql( + `ALTER TABLE "tax_region" ADD CONSTRAINT "FK_tax_region_parent_id" FOREIGN KEY ("parent_id") REFERENCES "tax_region" ("id") ON DELETE CASCADE;` + ) + this.addSql( + `ALTER TABLE "tax_rate" ADD CONSTRAINT "FK_tax_rate_tax_region_id" FOREIGN KEY ("tax_region_id") REFERENCES "tax_region" ("id") ON DELETE CASCADE;` + ) + this.addSql( + `ALTER TABLE "tax_rate_rule" ADD CONSTRAINT "FK_tax_rate_rule_tax_rate_id" FOREIGN KEY ("tax_rate_id") REFERENCES "tax_rate" ("id") ON DELETE CASCADE;` + ) + + // remove old tax related foreign key constraints + this.addSql( + `ALTER TABLE IF EXISTS "product_tax_rate" DROP CONSTRAINT IF EXISTS "FK_346e0016cf045b9980747747645";` + ) + this.addSql( + `ALTER TABLE IF EXISTS "product_type_tax_rate" DROP CONSTRAINT IF EXISTS "FK_ece65a774192b34253abc4cd672";` + ) + this.addSql( + `ALTER TABLE IF EXISTS "shipping_tax_rate" DROP CONSTRAINT IF EXISTS "FK_346e0016cf045b9980747747645";` + ) + } +} diff --git a/packages/tax/src/models/index.ts b/packages/tax/src/models/index.ts index d741fc53bab8b..047c0cf0e37ab 100644 --- a/packages/tax/src/models/index.ts +++ b/packages/tax/src/models/index.ts @@ -1,3 +1,4 @@ export { default as TaxRate } from "./tax-rate" export { default as TaxRegion } from "./tax-region" export { default as TaxRateRule } from "./tax-rate-rule" +export { default as TaxProvider } from "./tax-provider" diff --git a/packages/tax/src/services/tax-module-service.ts b/packages/tax/src/services/tax-module-service.ts index 7f643db9d6690..8ba5faed86d22 100644 --- a/packages/tax/src/services/tax-module-service.ts +++ b/packages/tax/src/services/tax-module-service.ts @@ -14,11 +14,12 @@ import { MedusaContext, MedusaError, ModulesSdkUtils, + arrayDifference, isDefined, isString, promiseAll, } from "@medusajs/utils" -import { TaxRate, TaxRegion, TaxRateRule } from "@models" +import { TaxProvider, TaxRate, TaxRegion, TaxRateRule } from "@models" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { TaxRegionDTO } from "@medusajs/types" @@ -27,10 +28,11 @@ type InjectedDependencies = { taxRateService: ModulesSdkTypes.InternalModuleService taxRegionService: ModulesSdkTypes.InternalModuleService taxRateRuleService: ModulesSdkTypes.InternalModuleService + taxProviderService: ModulesSdkTypes.InternalModuleService [key: `tp_${string}`]: ITaxProvider } -const generateForModels = [TaxRegion, TaxRateRule] +const generateForModels = [TaxRegion, TaxRateRule, TaxProvider] type ItemWithRates = { rates: TaxRate[] @@ -40,7 +42,8 @@ type ItemWithRates = { export default class TaxModuleService< TTaxRate extends TaxRate = TaxRate, TTaxRegion extends TaxRegion = TaxRegion, - TTaxRateRule extends TaxRateRule = TaxRateRule + TTaxRateRule extends TaxRateRule = TaxRateRule, + TTaxProvider extends TaxProvider = TaxProvider > extends ModulesSdkUtils.abstractModuleServiceFactory< InjectedDependencies, @@ -48,6 +51,7 @@ export default class TaxModuleService< { TaxRegion: { dto: TaxTypes.TaxRegionDTO } TaxRateRule: { dto: TaxTypes.TaxRateRuleDTO } + TaxProvider: { dto: TaxTypes.TaxProviderDTO } } >(TaxRate, generateForModels, entityNameToLinkableKeysMap) implements ITaxModuleService @@ -57,6 +61,7 @@ export default class TaxModuleService< protected taxRateService_: ModulesSdkTypes.InternalModuleService protected taxRegionService_: ModulesSdkTypes.InternalModuleService protected taxRateRuleService_: ModulesSdkTypes.InternalModuleService + protected taxProviderService_: ModulesSdkTypes.InternalModuleService constructor( { @@ -64,6 +69,7 @@ export default class TaxModuleService< taxRateService, taxRegionService, taxRateRuleService, + taxProviderService, }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { @@ -75,6 +81,7 @@ export default class TaxModuleService< this.taxRateService_ = taxRateService this.taxRegionService_ = taxRegionService this.taxRateRuleService_ = taxRateRuleService + this.taxProviderService_ = taxProviderService } __joinerConfig(): ModuleJoinerConfig { @@ -613,4 +620,27 @@ export default class TaxModuleService< private normalizeRegionCodes(code: string) { return code.toLowerCase() } + + // @InjectTransactionManager("baseRepository_") + // async createProvidersOnLoad(@MedusaContext() sharedContext: Context = {}) { + // const providersToLoad = this.container_["tax_providers"] as ITaxProvider[] + + // const ids = providersToLoad.map((p) => p.getIdentifier()) + + // const existing = await this.taxProviderService_.update( + // { selector: { id: { $in: ids } }, data: { is_enabled: true } }, + // sharedContext + // ) + + // const existingIds = existing.map((p) => p.id) + // const diff = arrayDifference(ids, existingIds) + // await this.taxProviderService_.create( + // diff.map((id) => ({ id, is_enabled: true })) + // ) + + // await this.taxProviderService_.update({ + // selector: { id: { $nin: ids } }, + // data: { is_enabled: false }, + // }) + // } } diff --git a/packages/types/src/tax/common.ts b/packages/types/src/tax/common.ts index 761038664958e..e346f9af920fd 100644 --- a/packages/types/src/tax/common.ts +++ b/packages/types/src/tax/common.ts @@ -36,6 +36,11 @@ export interface TaxRateDTO { created_by: string | null } +export interface TaxProviderDTO { + id: string + is_enabled: boolean +} + export interface FilterableTaxRateProps extends BaseFilterable { id?: string | string[] diff --git a/yarn.lock b/yarn.lock index 773632e6a307a..534d26285c923 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8779,7 +8779,7 @@ __metadata: languageName: unknown linkType: soft -"@medusajs/tax@workspace:packages/tax": +"@medusajs/tax@workspace:^, @medusajs/tax@workspace:packages/tax": version: 0.0.0-use.local resolution: "@medusajs/tax@workspace:packages/tax" dependencies: @@ -31720,6 +31720,7 @@ __metadata: "@medusajs/promotion": "workspace:^" "@medusajs/region": "workspace:^" "@medusajs/store": "workspace:^" + "@medusajs/tax": "workspace:^" "@medusajs/types": "workspace:^" "@medusajs/user": "workspace:^" "@medusajs/utils": "workspace:^"